- 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).
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 and CHECKLIST.md. The operator
runbook is in docs/SERVER-MODE.md.
Two things changed after this design was written:
- The
memberrole is nowdeveloper.- An
analystis 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 byagenthub 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.)
- provisioning, the sudoers rule and the pane helper are in
- 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 |
| 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_HOMEbecome per account; the host account's~/.claudeis 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)
- 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?
- Does an admin's live view of a pane need to be announced to the member?
- Retention of prompts for analytics — how long, and who may export them?