Files
AgentHub/docs/plans/server-mode/README.md
T
nntrivi2001 118eaee2ec docs(server-mode): plan moved in from the deleted AgentHub-Server repo; P6 spike result; Express patch only on 4.x
- docs/plans/server-mode/README.md: the original multi-user design, now kept
  here (AgentHub is the one source); desktop-parity and S3-PLAN link to it.
- S3-PLAN P6: managed-settings spike run on agh-test1 — the managed
  marketplace/plugin arrive at the first interactive session (onboarding
  seeded), never via the CLI alone, and no user-scope record is written.
- server/index.js: the Layer.handle_request patch applies only on Express 4
  (Express 5 handles async rejections itself and has no lib/router/layer).
2026-10-09 15:18:57 +07:00

99 lines
8.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Multi-user server — AgentHub as a shared AI-tools host
_Status: the original design, written 2026-10-02. It lived for a while in a
separate AgentHub-Server repo, which has since been deleted; since 2026-10-09
it is kept here again, and AgentHub is the only source. Kept as the design
record. For what was built and what is still open, see
[`S3-PLAN.md`](S3-PLAN.md) and [`CHECKLIST.md`](CHECKLIST.md). The operator
runbook is in [`docs/SERVER-MODE.md`](../../SERVER-MODE.md)._
> Two things changed after this design was written:
> - The `member` role is now `developer`.
> - An `analyst` is no longer read-only. An analyst reads every user's rows and
> writes only their own.
## Where the code lives (decided 2026-10-02)
- **No fork while the local AgentHub is still moving.** A fork would need every
local fix carried over by hand, and S1–S3 touch nearly every route, the DB and
`pty-run.js` — two lines editing those in parallel conflict on every sync.
- **Server features go into the AgentHub codebase as a server MODE**, off by
default: login, roles and per-user scoping are additive; with the mode off the
local app behaves as one implicit admin and nothing changes for it.
- **What only a server deployment needs ships in this repo too**:
- provisioning, the sudoers rule and the pane helper are in
`scripts/server/`, installed by `agenthub server install-system`;
- the shared tools layout is `/opt/agenthub/tools`;
- the operator docs are the runbook in `docs/SERVER-MODE.md`.
(This first went to a separate AgentHub-Server repo. That repo is gone.)
- If a separate codebase ever becomes necessary, fork from a tagged, stable
AgentHub release, never from work in progress.
## Goal
Several people (A, B, C) use one AgentHub over the web. One Linux account (the
**host account**) has installed every AI tool and holds the shared logins:
AI Router profiles, several Claude native accounts, several Codex accounts.
- Each person logs in to AgentHub, works only in their own folder, and can
switch Claude/Codex account, pick any AI Router and use any installed tool —
without installing a tool or logging in to one themselves.
- Nobody sees anyone else's workspaces, sessions, prompts, tokens or cost.
- An **admin** sees and manages everything; an **analyst** reads everything
(usage, prompts) but changes nothing.
## Why one Linux user per account
Isolation inside AgentHub alone covers only the web UI. An agent runs a shell
(and can be put in bypass mode) as whatever Linux user started it; if A's agent
runs as the same user as B's, "read /srv/agenthub/users/B" just works. Only the
OS can stop an agent, so each AgentHub account maps to its own Linux user, and
the folders are `0750` with the AgentHub service in the group.
The cheaper variant — one Linux user, isolation in the UI only — is phase S1-S2
on their own. It is acceptable only where everyone is trusted and "cannot see"
means "is not shown".
## What is shared and what is not
| Thing | Shared? | Where |
| --- | --- | --- |
| Tool binaries | **Yes**, installed once | `/opt/agenthub/tools/` (root or `agenthub` owned, world `r-x`), on every pane's `PATH`. Self-updaters off (`DISABLE_AUTOUPDATER=1` for Claude; per tool, verified in S0); AgentHub's **Update** runs as the owner of `/opt/agenthub/tools`. |
| Today's install | — | `~/.local/bin/claude`, `~/.codex/packages/…/codex`, `~/.local/bin/agy` inside a `0750` home: other users cannot reach them, and Codex's binary lives in its credentials dir. Hence the move. |
| Tool config / login / transcripts | **No**, per account | `/srv/agenthub/users/<acct>/.claude`, `.codex`, `.gemini`, `.cursor`… (`HOME`, `CLAUDE_CONFIG_DIR`, `CODEX_HOME` point here). Sharing this dir would share every transcript. |
| Account logins (Claude, Codex, …) | **Yes**, as a vault | Host account, `agenthub`-owned, `0700`. Injected into a pane at spawn (S5). |
| AI Router profiles | **Yes** | Already per workspace (`router-apply.js`); the store moves to the vault dir. |
| Hooks | **Yes**, one copy | Claude: `/etc/claude-code/managed-settings.json` (applies to every user). Codex: written into each account's `CODEX_HOME` at provisioning. |
| Database | One SQLite, rows owned | `owner` on every user-data table (S2). |
## Phases
| # | Scope |
| --- | --- |
| S0 | **Spike** (needs `sudo`): two test Linux users; tools under `/opt/agenthub/tools`; each tool started with a foreign `HOME`; verify per tool how a login can be handed in (below) and how its auto-update is turned off; measure what concurrent use of one OAuth login does. Output: a table in this file, go/no-go per tool. |
| S1 | **Accounts and login**, still one Linux user. `users` table (id, name, role `admin|analyst|member`, password hash via `crypto.scrypt`, disabled), `auth_sessions` (httpOnly SameSite=Strict cookie), login page, every `/api` and `/ws` behind it except `/api/hooks` (local only, see S3). First run: create the admin from the CLI (`agenthub users add --admin`). |
| S2 | **Ownership.** `owner` (user id) on `lanes`, `sessions`, `token_usage`, `events`, `dashboard_runs`, `lane_sources`, alerts, ai-tools prefs. A session's owner = its workspace's owner (phase-1 `lane_id`), else derived from its cwd under `/srv/agenthub/users/<acct>/`. Every query of a member is scoped (one helper, not per route); WebSocket broadcasts filtered per connection. Workspace creation refuses a folder outside the account root. Admin/analyst bypass the scope; analyst is read-only. |
| S3 | **Per-account Linux user.** Provisioning (`agenthub users add`): `useradd`, `/srv/agenthub/users/<acct>` `<acct>:agenthub 0750`, `HOME` there, per-tool dirs, Codex hooks. Spawning as the account: a small privileged helper (or a `sudoers` rule limited to `tmux` under group `agenthub-users`) — per-account tmux server; `pty-run.js`/`tmux.js`/`pty-attach.js` gain a target user. Hooks keep posting to localhost; the server trusts the owner it derives from the cwd/run, never one the hook names. Transcripts read through group `agenthub`. **Security review before merge.** |
| S4 | **Shared tools.** Catalog detection also looks in `/opt/agenthub/tools/bin`; AI Tools install/update write there; per-tool auto-update off in each pane's env. |
| S5 | **Login vault and switching.** A member picks, per run or as a default, a Claude profile / Codex profile / AI Router. At spawn the pane gets it: Claude a long-lived token (`claude setup-token` → `CLAUDE_CODE_OAUTH_TOKEN` through tmux `-e`), Cursor `CURSOR_API_KEY`, Codex `auth.json` copied into the account's `CODEX_HOME` (or an API key), others per S0. Today's `cc-profiles.js` switch rewrites ONE live credentials file — that becomes "copy into this account", never global. Codex profiles are new. Refresh-token rotation (two people on one OAuth login) is the open risk S0 measures. |
| S6 | **Admin and analyst views.** User management; every page gets a user filter for admin/analyst; prompt analytics (UserPromptSubmit events, transcripts) and token/cost per user; admin may attach read-only to any pane. Every admin read of another user's content is audit-logged. |
| S7 | **Hardening.** TLS (reverse proxy), bind address, login rate limit, cookie flags, `sameOriginGuard` kept, `/api/hooks` accepted from loopback only, backup of the vault. |
## Rules this plan will change in `CLAUDE.md`
- Settings/`CLAUDE_HOME` become per account; the host account's `~/.claude` is no
longer "the" Claude home.
- `cc-profiles.js` "a switch changes the live account" → per account.
- Every new route is owner-scoped; "a route never takes X from the request"
rules gain "…nor an owner".
- The bootstrap/systemd rules stay for the host account; members never run the
bootstrap.
## Open questions for the user (before S3)
1. With Claude Enterprise each person has a seat — should members log in their
own seat (cleaner audit, no rotation risk) and the vault serve only Codex,
AI Routers and spares?
2. Does an admin's live view of a pane need to be announced to the member?
3. Retention of prompts for analytics — how long, and who may export them?