Files
AgentHub/docs/plans/remote-clients
nntrivi2001 fa156d6a59 feat: v2.6.0 — Chat UI composer (+ menu, project files, activity panel, permission mode, Stop), git on a workspace's extra folders (#70), long paste as a file (#68), S3 server mode (experimental)
Chat UI:
- Each Claude/Codex session keeps its own chat/terminal view; Settings only
  sets how new sessions open.
- Composer + menu: upload, project files and folders (#54, also dragged from
  the Files/Changes tree), context files, recent uploads, skills, plugins.
- Activity side panel (progress, outputs, files read, uploads, memory,
  skills, connectors) with a file preview; edited-file counts per turn.
- Permission-mode chip (Shift+Tab steps, read back from the pane:
  server/lib/pane-mode.js, GET|POST /api/run/:id/permission-mode).
- Stop (Esc) while the main agent works; the status is checked per agent
  (with session_id /api/agents ignores its status filter) and re-read every
  5 s while Stop shows.
- Dropped images shown in the conversation (GET /api/run/drop/:name, the
  requester's own drop dir), full-size viewer.
- A plain-text paste over 5,000 characters is attached as pasted-text.txt,
  so no prompt reaches tmux/argv limits (#68).

Changes panel: every /api/lanes/:id/git* route takes ?source=<lane_sources
id> (sourceFolder; ENOSOURCE otherwise) and a folder picker switches the
whole panel to a workspace's extra folder (#70).

Server mode S3 (one Linux user per account) — experimental, verified with
fake sudo plus a P6 managed-settings spike on a test account:
- member tmux listing runs in the background (cached, 15 s => unknown);
- transcript snapshot copy is async, chunked, temp file + rename;
- Express Layer patch only on Express 4;
- tmux capturePane/sendKey go to the member's own server.

Docs: the multi-user design moved in as docs/plans/server-mode/README.md
(the AgentHub-Server repo is gone); S3-PLAN P6 result; LANES/API/openapi.
2026-10-09 16:35:07 +07:00
..

Remote clients — work runs on each user's machine, the hub only manages

Status: PLAN, not built. Decided 2026-10-09. Branch server-s3 (one Linux user per account on the hub) stays as it is until this plan is proven or dropped.

Why

Everyone logging into one machine (server mode, S3) does not scale past ~10 concurrent users: every pane, build and test runs on the hub. Here the compute moves to each user's machine; the hub keeps identity, the shared accounts, and every workspace/session/conversation/token record.

Decisions (user, 2026-10-09)

  1. Each user installs AgentHub local on their own machine.
  2. The full conversation is uploaded to the hub — only for sessions started through AgentHub.
  3. Shared accounts (the Claude pool, shared AI Proxies) are usable only through AgentHub. The company has at least as many seats as people; a seat may be used by more than one person.
  4. Use without AgentHub is detected and warned about, and blocked when an admin turns blocking on.
  5. The hub is reached over the company LAN at http://10.1.13.7:4820 — no HTTPS.
  6. An admin sees, in one click from the Users page, which machines a user has, which workspaces they used, and what each session did.
  7. server-s3 is kept until this architecture is settled.

Shape

 user machine — AgentHub LOCAL                 hub 10.1.13.7 — AgentHub SERVER mode
 ┌───────────────────────────────────┐         ┌──────────────────────────────────┐
 │ pane: claude                      │─hooks +▶│ /api/remote/ingest (device token)│
 │   ANTHROPIC_BASE_URL = hub gateway│ transcr.│  → sessions/events/tokens, owned │
 │   ANTHROPIC_AUTH_TOKEN = ahdev_…  │         │    by the device's user          │
 │                                   │─/v1/… ─▶│ /api/proxy-gw → pool token added │
 └───────────────────────────────────┘         └──────────────────────────────────┘

No pool token or proxy key ever reaches a user's machine; it holds one device token. Plain HTTP means anyone sniffing the LAN can read that token and the conversations — accepted for an internal network. Moving to HTTPS later is a reverse proxy in front of the hub (Caddy tls internal), no code change.

What the hub can see (per tool, checked 2026-10-09)

Uploads cover every session the machine's AgentHub started, in ANY of its workspaces — company repo or the user's own project alike. A session run outside AgentHub on the user's own login is invisible; only pool use is gated.

Tool Conversation Tokens / cost Pool / gate Reader today
Claude Code yes yes yes (R3) hooks + transcript
Codex yes yes not yet (Codex pool = later) codex-usage.js rollouts
Antigravity no — needs a renderer for transcript_full.jsonl yes no (own login) antigravity-usage.js
Oh My Pi, other catalog tools no no no none: only the pane run (workspace, time)
DeepSeek CLI / not in catalog no no no not launchable as a tool; a shell pane records nothing

R2 therefore uploads Claude transcripts, Codex rollouts and agy conversation files (remote-sync.js today mirrors only the first two). Any tool beyond that needs its own reader first — one per tool, on demand.

Gating

Verified 2026-10-09 against Claude Code 2.1.295 with a capture server: every /v1/messages call carries x-claude-code-session-id: <id> (same id in metadata.user_id), and --session-id <uuid> sets it — spawnRun already starts Claude panes that way.

The gateway checks each request:

  • bearer = a device token that is not revoked, of a user who is not disabled — else always 401;
  • x-claude-code-session-id = a session the hub received uploads for, from THAT device, within REMOTE_LIVE_SEC (300 s; 60 s grace for a new session).

A request failing the second check is a session AgentHub is not uploading (token copied out of AgentHub, or uploads switched off). Setting remote_gate decides: warn (default) forwards it and records the session as unmonitored with its token count from the response's usage, and fires an alert (existing fireAlert); block refuses it with 403. Ceiling: someone who also fakes the upload stream passes — detection, not proof.

Relay spike (2026-10-09, Claude Code 2.1.295, script kept in ~/.cache/agenthub-spike/relay.js): a client with NO login and ANTHROPIC_AUTH_TOKEN=<fake> worked through a relay that swapped in a team subscription's OAuth token (+ anthropic-beta: oauth-2025-04-20). Checked: answers, --resume, a local Bash tool call, prompt cache reads on every turn (18–26k tokens), anthropic-ratelimit-unified-* headers passed back. Three findings for R3:

  • Through a base URL Claude Code writes 5-minute cache entries; set ENABLE_PROMPT_CACHING_1H=1 in the pane (verified: 1h entries) to keep the subscription's 1-hour TTL.
  • The cache is per ACCOUNT (a fresh client read 18k cached tokens on its first call), so the gateway must keep a session on ONE pool account — switching accounts mid-session re-writes the whole cache.
  • A repo's .claude/settings*.json env overrides the pane's ANTHROPIC_BASE_URL/ANTHROPIC_AUTH_TOKEN (seen in this repo) — give the gateway through --settings=<file>, the layer that outranks project settings, as AI Proxy panes already do. Not checked yet (interactive features that call claude.ai with the user's own login, likely unavailable): /usage quota view, claude.ai MCP connectors, Remote Control / web sessions, org plugins from claude.ai.

What is and is not guaranteed:

  • Guaranteed: no hub account (or a disabled user / revoked device) → no issued credential, because the credential never leaves the hub. This holds only while the ADMIN alone holds each shared seat's login (setup-tokens made by the admin). A seat whose login (email/SSO) an employee knows can always be used directly at claude.ai or in a plain claude — AgentHub cannot stop that.
  • Not guaranteed: that the AgentHub APP was used. The device token sits on a machine the user controls; block stops a plain claude using it, unless the user also fakes the upload stream. Hard enforcement needs managed, locked-down machines.
  • Closes the visibility gap (R3b): the gateway sees every request body, so it can record the conversation of every pool request ITSELF — new messages per request by session id, plus the streamed answer and its usage — no matter which client sent it. Then nothing that uses an issued credential is invisible to the admin, AgentHub app or not.
  • Plain HTTP: a LAN sniffer can take a device token and act as that user. If that matters, HTTPS (reverse proxy + an internal CA every machine trusts, NODE_EXTRA_CA_CERTS for Claude Code).

Phases

Each one ships alone and changes nothing for a local-only install.

R0 — user activity view (hub, useful today). Users page row → a user page: their devices (once R1 exists), workspaces, and sessions by day with tokens/cost; a session opens the existing session page (conversation, tool calls). Backend: an owner=<user id> filter for staff on GET /api/sessions and GET /api/lanes, reusing sessionWhere/laneWhere for that user — no new query logic. Admin reads are audited as they already are. Verify: server test for the filter (developer cannot use it); client snapshot.

R1 — devices (hub + CLI). Table devices (id, user_id, name, token_hash, created_at, last_seen_at, revoked_at). agenthub remote login http://10.1.13.7:4820 signs in with the user's hub password (throttled like /api/auth/login), receives an ahdev_ token once, stores it 0600 in the runtime dir. The user page lists devices; an admin revokes one (re-auth + audit_log). Verify: mint/hash/revoke tests; a revoked token gets 401.

R2 — upload (both sides). An enrolled local AgentHub uploads each of ITS sessions (dashboard_runs): hook events as they arrive, the session files of the table above (Claude transcript, Codex rollout, agy conversation) incrementally (byte offset, retry queue on disk, never blocking a hook). The hub stores transcripts under <data dir>/remote/<user id>/<device id>/projects/ and imports them with the SAME importFromDirectory remote-sync.js uses, so tokens and cost match local sessions. Sessions are owned by the device's user (source = device:<id>); workspaces are mirrored as records (title, folder, tool) — shown, not driven. The existing ingest-batch stays as it is. Verify: two local instances → one hub; R0's page shows both users' work.

R3 — remote gateway. The local spawnRun points an enrolled machine's Claude pane at the hub (ANTHROPIC_BASE_URL, device token as ANTHROPIC_AUTH_TOKEN, through tmux -e). proxy-gateway.js (from origin/main since 84e2a9f0) gains the remote path and checks above; for the pool it picks an account with pickAccount and sends its OAuth token. Spike first on ONE real pool account: which headers a setup-token needs through a relay. Verify: a pane gets "pong"; a curl with the device token and a session id nobody uploads is logged (warn) or 403 (block).

R4 — shared skills/plugins. No AgentHub code: the company marketplace (git) plus a managed-settings.json template (extraKnownMarketplaces, enabledPlugins) and an install script for user machines.

Codex comes after R3 works for Claude: ChatGPT-account auth differs from an API key, and its session-id header still has to be checked.

Open risks

  • A hub outage stops every pool session; a machine can still use its own login.
  • The hub stores every conversation of an AgentHub session; tell users.