ai-agentmanager 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +4 -0
  3. package/README.md +767 -2
  4. package/SECURITY.md +44 -0
  5. package/bin/agentmgr-hub.js +16 -0
  6. package/bin/agentmgr.js +4 -0
  7. package/docs/architecture.md +67 -0
  8. package/docs/distributed.md +147 -0
  9. package/docs/engines.md +177 -0
  10. package/docs/images/agents.jpg +0 -0
  11. package/docs/images/conflict-radar.jpg +0 -0
  12. package/docs/plugins.md +81 -0
  13. package/package.json +56 -4
  14. package/public/app.js +3083 -0
  15. package/public/hub/hub.css +230 -0
  16. package/public/hub/hub.js +766 -0
  17. package/public/hub/index.html +146 -0
  18. package/public/hub/term.js +147 -0
  19. package/public/index.html +342 -0
  20. package/public/share.html +34 -0
  21. package/public/share.js +41 -0
  22. package/public/style.css +541 -0
  23. package/public/touchscroll.js +73 -0
  24. package/public/transport.js +78 -0
  25. package/scripts/build-release.sh +58 -0
  26. package/scripts/fix-pty.js +15 -0
  27. package/src/backlog.js +413 -0
  28. package/src/brief.js +512 -0
  29. package/src/cli.js +1447 -0
  30. package/src/client.js +49 -0
  31. package/src/config.js +109 -0
  32. package/src/conflicts.js +105 -0
  33. package/src/engines/antigravity.js +296 -0
  34. package/src/engines/claude.js +98 -0
  35. package/src/engines/codex.js +331 -0
  36. package/src/engines/index.js +37 -0
  37. package/src/fleet.js +176 -0
  38. package/src/git.js +340 -0
  39. package/src/hub.js +857 -0
  40. package/src/hubactions.js +107 -0
  41. package/src/hubbacklog.js +373 -0
  42. package/src/hublink.js +437 -0
  43. package/src/inbox.js +94 -0
  44. package/src/judge.js +278 -0
  45. package/src/livebrowser.js +637 -0
  46. package/src/manager.js +2554 -0
  47. package/src/notify.js +284 -0
  48. package/src/plugins.js +119 -0
  49. package/src/preview.js +334 -0
  50. package/src/races.js +192 -0
  51. package/src/remote.js +284 -0
  52. package/src/server.js +759 -0
  53. package/src/service.js +150 -0
  54. package/src/sessions.js +160 -0
  55. package/src/store.js +104 -0
  56. package/src/stuck.js +160 -0
  57. package/src/tracker.js +311 -0
  58. package/src/trust.js +44 -0
package/SECURITY.md ADDED
@@ -0,0 +1,44 @@
1
+ # Security
2
+
3
+ ## Reporting a vulnerability
4
+
5
+ Please report security issues privately through GitHub: **Security → Report a vulnerability** on this repository. Don't open a public issue. You'll get a reply within a few days, and a fix or a plan before anything is disclosed.
6
+
7
+ ## What Agent Manager is, from a security point of view
8
+
9
+ Agent Manager runs coding agents (Claude Code, Codex, Antigravity) **as your user, on your machine**, in real terminals. Anyone who can drive Agent Manager can run commands as you. The design goal is that only you can drive it, from this machine, unless you deliberately open it up.
10
+
11
+ ### Trust boundaries
12
+
13
+ | Part | Who can reach it by default | How it is protected |
14
+ | --- | --- | --- |
15
+ | Daemon and web UI (`127.0.0.1:4717`) | Processes on this machine | Listens on loopback only. The `Host` header must be loopback (blocks DNS rebinding), a browser `Origin` must be the UI itself (blocks cross-site requests and WebSockets), and every write needs the `x-agentmgr` header, which a web page on another site can't send without a CORS preflight that is never granted. |
16
+ | Remote access (`https://<ip>:4718`) | Nobody: **off until you run `agentmgr remote on`** | HTTPS with a certificate made on this machine. Every API call needs a signed-in device session (sign-in with the access token or its QR code; sessions last 30 days, can be revoked one by one, and rotating the token signs everyone out). Only this machine's own addresses are accepted as `Host`. Turning remote access on or off, the token, devices and shutdown are local-only. |
17
+ | Hub (`agentmgr hub serve`, port 4720) | This machine and its Tailscale address | One admin token (or Google sign-in limited to listed emails); browsers get a 30-day session cookie. Plain `http://` is accepted only on loopback or Tailscale, which encrypts traffic; anything else must be `https://`. |
18
+ | A machine joined to a hub | The hub, over a connection the machine opens | Ed25519 key per machine (private key in `~/.agentmanager/hub-link.json`, mode 600); each connection signs a fresh challenge. The machine decides what the hub may do (`hub.allow`, default `view` only), checks every request itself, matches agents by exact ID, runs only a fixed list of actions (never deletes) and logs each request and refusal to `~/.agentmanager/hub-audit.log`. |
19
+ | Site relays (browser tabs from another device) | Signed-in remote devices | Never relay to Agent Manager's own ports; its sign-in cookie is stripped before forwarding; sites other than localhost must present a valid certificate; relays close when remote access is turned off. |
20
+ | CLI sign-in callbacks | The UI | Delivered only to the loopback port the sign-in link itself named. |
21
+
22
+ ### What is stored, and where
23
+
24
+ Everything lives in `~/.agentmanager/` (or `$AGENTMGR_HOME`):
25
+
26
+ - `remote/state.json`: remote access token and device sessions (mode 600).
27
+ - `hub-link.json`: this machine's hub key (mode 600). `hub/state.json` on the hub: its token and joined machines' public keys (mode 600).
28
+ - `notify.json`: the Slack webhook or bot token, if you set one (mode 600).
29
+ - `config.json`: your settings. Keep the whole folder private to your user.
30
+ - `browser-profile/`: the live browser's Chrome profile. Sites you sign in to there stay signed in, as in any browser.
31
+ - `scrollback/`, `briefs/`, `state.json`: terminal output and agent state. These can contain anything an agent printed, including secrets it read.
32
+
33
+ Agent Manager sends **no telemetry**. It talks to the network only for what you set up: git remotes, GitHub (`gh`), Slack, a hub you joined, and the agents' own model providers.
34
+
35
+ ### Things to be aware of
36
+
37
+ - **Agents act as you.** Each agent's permission mode (for Claude: `default`, `acceptEdits`, `plan`, `auto`, `bypassPermissions`) is passed to the engine; Agent Manager doesn't sandbox agents beyond that. Run agents in folders and with modes you'd be comfortable running the engine in by hand.
38
+ - **Other processes running as your user** can call the local API, just as they could run `claude` themselves. Agent Manager doesn't defend against malware already running as you.
39
+ - **The AI judge** sends short summaries of an agent's screen, activity and brief to the same engine the agents use (by default Claude `haiku`), as separate calls. Turn it off with `agentmgr judge off`.
40
+ - **Remote access on an untrusted network** (café Wi-Fi) exposes the sign-in page on your local network. Leave it off there, or use Tailscale and the hub instead.
41
+
42
+ ## Supported versions
43
+
44
+ Security fixes go into the latest release.
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ // The hub on its own, for hosts without the rest of Agent Manager (Railway, a container).
3
+ // Locally, "agentmgr hub serve" does the same.
4
+ import { startHub } from '../src/hub.js';
5
+
6
+ const port = Number(process.env.PORT || 4720);
7
+ const host = process.env.HOST || (process.env.PORT ? '0.0.0.0' : '127.0.0.1');
8
+ const hub = await startHub({ port, host });
9
+ console.log(`[hub] listening on ${hub.url}`);
10
+ if (hub.createdToken) console.log('[hub] generated an access token; set AGENTMGR_HUB_TOKEN instead to choose your own');
11
+ for (const sig of ['SIGINT', 'SIGTERM']) {
12
+ process.on(sig, async () => {
13
+ await hub.close();
14
+ process.exit(0);
15
+ });
16
+ }
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { main } from '../src/cli.js';
3
+
4
+ main(process.argv);
@@ -0,0 +1,67 @@
1
+ # Architecture
2
+
3
+ Agent Manager is one long-running **daemon** per machine. It owns every agent's terminal and state. The **web UI** and the **CLI** are clients of its HTTP and WebSocket API. Optionally, daemons on several machines connect out to a **hub**.
4
+
5
+ ```
6
+ browser (UI) ──┐ ┌── claude / codex / agy in PTYs (one per agent, plus shells)
7
+ phone (HTTPS) ─┼── daemon (server.js) ─┼── git worktrees under ~/.agentmanager
8
+ agentmgr CLI ──┘ │ manager.js └── headless Chrome (live browser tabs)
9
+ │
10
+ └── hub link (outbound WebSocket) ──► hub (hub.js) ◄── other machines
11
+ ```
12
+
13
+ No build step: Node ES modules on the server, plain ES modules in the browser. State is JSON files in `~/.agentmanager` (or `$AGENTMGR_HOME`).
14
+
15
+ ## The daemon
16
+
17
+ | File | Role |
18
+ | --- | --- |
19
+ | `src/server.js` | Express API and WebSockets on `127.0.0.1:4717`, the optional HTTPS remote listener, and the request checks described in [SECURITY.md](../SECURITY.md). Thin: routes call the manager. |
20
+ | `src/manager.js` | The core. Creates agents (clone or worktree, branch, launch), runs their PTYs (`node-pty`), keeps scrollback, resumes sessions after restarts, and runs a periodic tick that refreshes git, PRs, transcripts, stuck and conflict checks, inbox items and the judge. Emits events the server pushes to clients. |
21
+ | `src/store.js` | `state.json`: agent records, saved atomically and versioned (`STATE_VERSION`, migrations, backups). |
22
+ | `src/config.js` | Paths, defaults and `config.json`. |
23
+ | `src/engines/` | One adapter per agent CLI (Claude Code, Codex, Antigravity): launch and resume arguments, permission modes, where transcripts live and how to read them. See [engines.md](engines.md). |
24
+ | `src/git.js` | Clones (partial), worktrees, branches, diffs, PR lookup through `gh`. |
25
+ | `src/tracker.js` | Reads transcripts: tokens, cost, PR and bug references, activity. |
26
+ | `src/brief.js` | Each agent's living brief (`BRIEF.md` outside the repo), results, explain, insights and review documents. |
27
+ | `src/inbox.js`, `src/stuck.js`, `src/conflicts.js` | The decision inbox (prompts and questions), stuck detection and the conflict radar: the rules that decide *when* to look. |
28
+ | `src/judge.js` | The AI judge: one-shot, tool-less model calls that decide *what is going on* (stuck, waiting, done, collisions, backlog order, alert triage), with a daily cap and feedback examples. Falls back to the rules. |
29
+ | `src/backlog.js`, `src/races.js` | Work orders picked up per project; several agents on one intent, compared. |
30
+ | `src/notify.js` | Slack alerts, triaged by the judge. |
31
+ | `src/preview.js`, `src/livebrowser.js` | Browser tabs: HTTPS relays for remote devices, and a headless Chrome streamed over the Chrome DevTools Protocol (frames, input, sound). |
32
+ | `src/sessions.js` | Finds Claude Code sessions started in a terminal so they can be continued as agents. |
33
+ | `src/remote.js` | Remote access: certificate, token, device sessions. |
34
+ | `src/service.js` | Start at login (launchd or systemd). |
35
+ | `src/trust.js` | Pre-accepts Claude Code's folder trust prompt for agent folders. |
36
+ | `src/plugins.js` | Plugins from a separate package: policies before actions, listeners after, extra routes, engines and hub sign-in. See [plugins.md](plugins.md). |
37
+
38
+ ## The clients
39
+
40
+ | File | Role |
41
+ | --- | --- |
42
+ | `public/index.html`, `public/app.js`, `public/style.css` | The web UI: agents tree, inbox, backlog, terminals (xterm.js), briefs, browser tabs. |
43
+ | `public/transport.js` | WebSockets locally; over remote HTTPS, server-sent events plus POSTs (works through more networks). |
44
+ | `src/cli.js`, `src/client.js` | The `agentmgr` command: starts the daemon when needed and calls the same API. `attach` pipes your terminal to an agent's PTY. |
45
+
46
+ ## Several machines
47
+
48
+ | File | Role |
49
+ | --- | --- |
50
+ | `src/hub.js`, `bin/agentmgr-hub.js` | The hub: sign-in, enrolled machines, the fleet view, the shared backlog, terminals relayed from machines. Runs alone in a container (`hub/Dockerfile`). |
51
+ | `src/hublink.js` | A daemon's outbound link to its hub: signed challenge per connection, policy checks on every request, audit log. |
52
+ | `src/hubactions.js` | The fixed list of actions a hub may ask for, and the permission each needs. |
53
+ | `src/fleet.js` | The whitelisted view of agents a machine shares. |
54
+ | `src/hubbacklog.js` | Placing shared backlog items on machines with room. |
55
+
56
+ Design notes: [distributed.md](distributed.md) (hub) and [engines.md](engines.md) (engines).
57
+
58
+ ## Lifecycle of an agent
59
+
60
+ 1. **Create** (`POST /api/agents`): the manager makes a record, prepares the workspace (clone or worktree and branch, or a folder), and pre-trusts the folder.
61
+ 2. **Launch**: the engine adapter builds the command with a session ID chosen up front; the PTY starts in the workspace. Instructions about the brief and questions are passed with the first prompt.
62
+ 3. **Track**: the tick reads git, the transcript and the screen; the brief, inbox, stuck, conflicts and the judge update the record; clients get events.
63
+ 4. **Close and resume**: closing keeps the session ID; starting again resumes it. On daemon shutdown, scrollback and state are flushed, and running agents come back on the next start.
64
+
65
+ ## Tests
66
+
67
+ `test/e2e.test.js` starts a real daemon on a temporary home and port, with fake engines that print what the real CLIs print. `test/unit.test.js` covers parsers and pure functions. CI runs both on macOS and Linux, Node 20 and 22.
@@ -0,0 +1,147 @@
1
+ # Distributed agent management: design
2
+
3
+ Status: phases 1–3 built (0.3, branch `v0.3-fleet`) · Phases 4–5: planned
4
+
5
+ ## Problem
6
+
7
+ Agent Manager runs every agent on the machine its daemon runs on. Once you have agents on more than one machine (a Mac mini that is always on, a laptop, a cloud VM with more cores, a teammate's box), you get one UI per machine, one inbox per machine, and a backlog that can't use spare capacity elsewhere. Overlaps between agents on different machines go unnoticed until merge time.
8
+
9
+ ## Goals
10
+
11
+ - **One place to see and run everything:** all machines' agents, inbox and backlog in one UI and one CLI.
12
+ - **Use capacity wherever it is:** backlog items go to the machine with room, the repo already cached, and the right labels (OS, GPU, network access).
13
+ - **Keep machines independent:** a machine that loses the hub keeps running its agents and its local UI. No agent state is ever held only by the hub.
14
+ - **Nothing new to open on home networks:** machines connect out; no port forwarding.
15
+ - **At least as safe as today:** a live terminal is remote code execution, so every machine decides what the hub may do there.
16
+
17
+ ## Non-goals (for now)
18
+
19
+ - Moving a running agent between machines. A fork across machines is in scope for a later phase; a live migration isn't.
20
+ - Several people sharing one hub. The design leaves room for it, but 0.3 is single-user.
21
+ - A multi-user hosted service. The hub will move to Railway, but as a single-user hub with one access token.
22
+
23
+ ## Options considered
24
+
25
+ | | A. Federation | B. Hub and runners (recommended) | C. Hosted control plane |
26
+ | --- | --- | --- | --- |
27
+ | Shape | Each UI connects to every daemon directly | Daemons connect out to one hub; the UI talks to the hub | Daemons connect to a service on the internet (e.g. Railway) |
28
+ | Reachability | Every machine must be reachable from every client | Only the hub must be reachable, and only by the runners and you | Works anywhere |
29
+ | Shared backlog and scheduling | Hard: no one place decides | Natural: the hub schedules | Natural |
30
+ | Cross-machine inbox and conflicts | Merged in the browser | Computed at the hub | Computed centrally |
31
+ | Blast radius if compromised | One machine | The hub can reach every runner, limited by each runner's policy | An internet-facing service can reach every runner |
32
+ | Effort | Low | Medium | High (accounts, SSO, hardening, hosting) |
33
+
34
+ A is a fine stopgap but can't schedule. C is where a team product ends up, but it puts terminals behind an internet service before the trust model is proven. **B** gives one UI and real scheduling, works through NAT, and can later move the hub onto the internet without changing the runners.
35
+
36
+ ## Architecture
37
+
38
+ ```mermaid
39
+ flowchart LR
40
+ subgraph You
41
+ UI[Browser / phone UI]
42
+ CLI[agentmgr CLI]
43
+ end
44
+ subgraph Hub["Hub: a daemon in hub mode"]
45
+ API[Fleet API + UI]
46
+ SCHED[Backlog scheduler]
47
+ VIEW[Fleet view: agents, inbox, conflicts]
48
+ end
49
+ subgraph M1["Runner: Mac mini"]
50
+ D1[agentmgr daemon] --> A1[agents in PTYs]
51
+ end
52
+ subgraph M2["Runner: laptop"]
53
+ D2[agentmgr daemon] --> A2[agents in PTYs]
54
+ end
55
+ subgraph M3["Runner: cloud VM"]
56
+ D3[agentmgr daemon] --> A3[agents in PTYs]
57
+ end
58
+ UI --> API
59
+ CLI --> API
60
+ D1 -- "wss, outbound" --> Hub
61
+ D2 -- "wss, outbound" --> Hub
62
+ D3 -- "wss, outbound" --> Hub
63
+ ```
64
+
65
+ - **Runner:** today's daemon plus a small hub client. It stays the source of truth for its agents: `state.json`, sessions, worktrees and checkpoints stay where they are, and its local UI keeps working.
66
+ - **Hub:** a daemon started with `agentmgr hub`. It can also run agents itself, as its own runner. It holds a cache of what runners report, plus fleet-wide things: the shared backlog and its assignments, enrolled runners, and the conflict view across machines.
67
+ - **One connection per runner:** a single WebSocket from runner to hub carries everything, multiplexed.
68
+
69
+ ### What flows over the connection
70
+
71
+ | Frame | Direction | Purpose |
72
+ | --- | --- | --- |
73
+ | `challenge` | hub → runner | A fresh nonce the runner signs |
74
+ | `hello` | runner → hub | Runner ID, signature, version, platform, policy, UI addresses (later: labels, capacity, cached repos) |
75
+ | `welcome` | hub → runner | Accepted; the runner sends its state |
76
+ | `state` | runner → hub | The whole fleet view: whitelisted agent fields and inbox items (see `src/fleet.js`), plus, when the runner allows `create`, its projects (with a cross-machine `ref` and whether each is cloned), capacity and labels. Sent at most every 2 s and only when it changed; small enough that deltas aren't worth it yet. Later: change sets for the conflict radar |
77
+ | `leave` | runner → hub | The runner is leaving; the hub forgets it |
78
+ | `rpc` / `rpc-result` | hub → runner → hub | One of a fixed list of actions (`src/hubactions.js`), checked against the runner's policy and audited there; 20 s timeout |
79
+ | `term-open` / `term-in` / `term-resize` / `term-close` | hub → runner | A browser's terminal on channel N |
80
+ | `term-hello` / `term-out` / `term-msg` / `term-error` / `term-end` | runner → hub | Its screen: the last 256 KB of history, then live output batched every 30 ms. A lagging channel drops output, then restarts from a fresh screen |
81
+ | `ping` / `pong` | runner → hub → runner | Liveness every 15 s; a runner is dropped after 45 s without one |
82
+
83
+ Agent IDs become `runner/agent` (for example `mini/f532b9`) in the hub's UI and CLI. The bare ID still works when it's unique.
84
+
85
+ ### Scheduling the shared backlog
86
+
87
+ The hub's backlog works like today's, with one extra step: pick a runner. In order:
88
+
89
+ 1. **Hard constraints:** the item's required labels (for example `os:linux`, `gpu`), the runner's policy allows creating agents, and the runner is online.
90
+ 2. **Capacity:** the runner's parallel limit per project and in total, as today.
91
+ 3. **Warm start:** prefer a runner that already has the repo cloned. A big repo clone can take many minutes; this matters more than load.
92
+ 4. **Spread:** otherwise, the runner with the fewest active agents.
93
+
94
+ Built in `src/hubbacklog.js`. Projects match across runners by `ref`: the repo (`repo:github.com/org/app`) or, without a remote, the name (`name:site`), so local paths never leave a runner. Capacity is the runner's own `hub.maxAgents` and `hub.perProject`, counting hub items it holds plus active items from its local backlog. "Done" is a brief marked done after the brief time the hub last saw, not a timestamp comparison, so clock skew between machines doesn't matter.
95
+
96
+ The hub sends `rpc create` to the chosen runner, which runs the agent through its normal path. If the runner drops before the item finishes, the item shows as *stalled on mini*. It is not silently re-run elsewhere, because work on that runner's worktree would be lost or duplicated. You decide: wait, or restart it on another runner.
97
+
98
+ ### Fleet-wide features
99
+
100
+ - **Inbox:** the union of every runner's inbox items, each tagged with its machine. Answers go back as `rpc`.
101
+ - **Conflict radar across machines:** runners send their change sets (files and line ranges, not file contents). The hub runs the same `findConflicts` across runners, keyed by normalized repo URL instead of the local clone path.
102
+ - **Races across machines:** a race may place each attempt on a different runner, for example the same intent on macOS and Linux. Tests run on the attempt's own runner.
103
+ - **Forks across machines (later):** packing the checkpoint as a `git bundle` with the transcript and unpacking it on the target runner. This is the only feature that moves code between machines, so it waits for the trust model to settle.
104
+
105
+ ## Security
106
+
107
+ The hub can drive terminals on every runner, so a hub compromise must not mean "everything, everywhere".
108
+
109
+ - **Enrollment:** `agentmgr hub invite` prints a one-time code that is valid for 10 minutes. `agentmgr join <hub-url> <code>` makes an Ed25519 key pair on the runner and sends the hub only the public key. From then on, the runner proves itself by signing a fresh challenge on each connection, and the hub can remove one runner without touching the others. A removed runner stops retrying until it joins again.
110
+ - **The runner decides:** each runner has a local policy the hub can't change:
111
+
112
+ ```json
113
+ { "hub": { "allow": ["view", "inbox", "control", "terminal", "create"], "projects": ["*"] } }
114
+ ```
115
+
116
+ The default is `["view"]` only; `terminal`, `create` and the rest are opt-in per machine. `projects` takes project names or shortcuts. Every hub-initiated action is checked against this list on the runner and written to the runner's local audit log (`hub-audit.log`). Phase 1 runners refuse and log anything but the handshake.
117
+ - **Transport:** `https://` (Railway's certificate, so no self-signed pinning), or plain `http://` only for localhost and Tailscale addresses (100.64.0.0/10, `*.ts.net`), where WireGuard already encrypts. Runners never accept inbound connections from the hub.
118
+ - **You, at the hub:** one access token (`AGENTMGR_HUB_TOKEN` on Railway), a sign-in link, and 30-day HttpOnly, SameSite=Strict session cookies. Failed sign-ins and joins are rate limited. The CLI sends the token as a bearer header. Runner-supplied text is only ever rendered as text, under a strict Content-Security-Policy, so a compromised runner can't script the hub UI and reach the others. SSO is left for a multi-user hub.
119
+ - **Data minimization:** the hub caches agent views and change sets (file names and line ranges). It doesn't store transcripts or code, and terminal data passes through without being kept.
120
+
121
+ ## Failure modes
122
+
123
+ | Failure | What happens |
124
+ | --- | --- |
125
+ | Hub down | Runners keep running; each local UI still works. On reconnect, runners resend a snapshot. |
126
+ | Runner offline | Its agents show as offline with when they were last seen. Its queued backlog items wait; active ones show as stalled. |
127
+ | Network flap | The runner reconnects with backoff and resumes open terminals from their saved scrollback. |
128
+ | Version skew | `hello` carries versions. The hub refuses calls a runner doesn't support and says so in the UI. |
129
+
130
+ ## Plan
131
+
132
+ | Phase | Delivers | Size |
133
+ | --- | --- | --- |
134
+ | 1. Fleet view ✅ | `agentmgr hub serve`, `invite`, `join`, `leave`, `fleet`; the hub shows every runner's agents and inbox, read-only, with links to each runner's own UI; `hub/Dockerfile` for Railway | done |
135
+ | 2. Control ✅ | Actions (inbox, control, create) and terminals through the hub; permissions `inbox`, `control`, `create`, `terminal`, `shell` checked on the runner; audit logs on both ends | done |
136
+ | 3. Scheduling ✅ | Shared backlog with labels, warm-start placement, stalled-item handling (`agentmgr fleet backlog`, the hub's Backlog tab) | done |
137
+ | 4. Cross-machine | Conflict radar and races across runners | ~1 week |
138
+ | 5. Later | Forks across machines (git bundles), a hosted hub with SSO, several users | — |
139
+
140
+ Phase 1 is useful on its own and low-risk, because the hub only reads.
141
+
142
+ ## Decisions (2026-10-18)
143
+
144
+ 1. **Where the hub runs:** eventually Railway. Phase 1 runs on a Mac on the tailnet, and the hub is already standalone (express and ws only, `hub/Dockerfile`) so it can move there. Being internet-facing shaped the security above: a token plus session sign-in, rate limits, CSP, and data minimization.
145
+ 2. **Which machines:** Macs only for now. No Linux labels or multi-user work yet.
146
+ 3. **Tailscale:** on every machine. Plain `http://` hub addresses are accepted over Tailscale, and **Open** links prefer each Mac's Tailscale address.
147
+ 4. **Default policy:** new runners start with `view` only.
@@ -0,0 +1,177 @@
1
+ # Design: several agent engines (Claude Code, Codex CLI, Gemini CLI)
2
+
3
+ Status: phase 1, Codex and Antigravity built. Gemini CLI dropped: since June 18, 2026 it no longer serves personal Google accounts, and Antigravity CLI (`agy`, `src/engines/antigravity.js`, checked against 1.4.3, fixtures in `test/fixtures/antigravity/`) replaced it. 0.4 (branch `v0.4-engines`) · 2026-10-18. Targets the current releases of each CLI, checked in phase 1.
4
+
5
+ Agent Manager runs every agent with Claude Code. If Anthropic has an outage, a rate limit hits, or prices change, every agent stops at once. Running agents on other engines as well makes the fleet resilient across providers, and lets each task use the engine that suits it.
6
+
7
+ ## Goals
8
+
9
+ - **Same workflow, any engine:** create, watch, answer, message, review and merge agents the same way whether they run Claude Code, OpenAI's Codex CLI or Google's Gemini CLI.
10
+ - **Choose per agent:** a default engine, overridable per agent, per project and per backlog item.
11
+ - **Fail over:** when an engine is down or rate-limited, new work goes to another engine instead of waiting.
12
+ - **Honest UI:** features an engine can't support (for example a cost figure) are hidden for its agents, not faked.
13
+
14
+ ## Non-goals
15
+
16
+ - **Moving a live conversation between engines.** Conversations stay with their engine. A task can continue on another engine through a handoff (below), which carries the work but not the chat history.
17
+ - **Editor-based agents** (Antigravity, Cursor, and so on). Agent Manager drives command-line agents in a terminal.
18
+ - **Running models directly through APIs.** Each engine keeps its own CLI, sign-in, tools and safety settings.
19
+
20
+ ## What is Claude-specific today
21
+
22
+ | Area | Where | What it relies on |
23
+ | --- | --- | --- |
24
+ | Launch and resume | `manager.js` `start`, `baseArgs`, `spawnClaude` | `--session-id` (an ID we choose up front), `--resume`, `--name`, `--permission-mode`, `--model`, `--add-dir`, `--allowedTools`, `--append-system-prompt` |
25
+ | Transcripts | `tracker.js`, `stuck.js` | `~/.claude/projects/<cwd>/<session>.jsonl`: tokens, cost, PR and bug links, activity, subagents |
26
+ | Planning handoff | `manager.js` `moveTranscript` | Moving a transcript between folders so the same session continues in the repo |
27
+ | Inbox prompts | `inbox.js` `promptChoices` | The layout of Claude Code's numbered choice prompts on screen |
28
+ | Folder trust | `trust.js` | `~/.claude.json` `hasTrustDialogAccepted` |
29
+ | Brief and questions | `brief.js`, `inbox.js` | Claude writing `BRIEF.md` / `QUESTIONS.md` in a folder outside the repo that it was allowed to write to |
30
+ | Explain, insights, documents | `brief.js` prompts, run through `claude -p` | A tool-less one-shot call |
31
+ | Config | `config.js` | `claudeBin`, `claudeArgs`, `permissionMode` |
32
+
33
+ Everything else is engine-neutral: clones, worktrees and branches, the backlog, PR tracking, the conflict radar (git diffs), races, checkpoints (git; the transcript copy is engine-specific), terminals and the hub.
34
+
35
+ ## The engine adapter
36
+
37
+ Each engine is one module in `src/engines/` that implements the same small interface. The manager calls the adapter instead of building Claude flags itself.
38
+
39
+ ```js
40
+ export default {
41
+ id: 'codex', // 'claude' | 'codex' | 'gemini'
42
+ label: 'Codex',
43
+ bin: (config) => config.engines?.codex?.bin ?? 'codex',
44
+
45
+ // What the UI may offer for this engine's agents.
46
+ supports: { resume: true, chooseSessionId: false, cost: false, tokens: true, prompts: true,
47
+ planHandoff: false, fork: false, permissionModes: ['default', 'acceptEdits', 'plan'] },
48
+
49
+ /** Arguments for a new session, or for resuming one. */
50
+ launch({ agent, workdir, sessionId, resume, prompt, instructions, writableDirs }) { /* -> { args, env } */ },
51
+
52
+ /** After a new launch: find the session the engine created (for engines that pick their own IDs). */
53
+ discoverSession({ workdir, startedAt }) { /* -> sessionId | null */ },
54
+
55
+ /** Incremental scan of the session log: usage, links, activity events for stuck detection. */
56
+ scan({ workdir, sessionId, cursor }) { /* -> { cursor, usage, prs, bugs, events, lastActivityAt } */ },
57
+
58
+ /** A numbered choice prompt visible on screen, if any: [{ key, label }]. */
59
+ promptChoices(screenText) {},
60
+
61
+ /** Make the folder trusted for the engine, if it asks. */
62
+ trust(dir) {},
63
+
64
+ /** One-shot, tool-less call for Explain and documents. */
65
+ oneShot({ prompt, model }) { /* -> { args } */ },
66
+
67
+ /** Is the engine usable right now? Installed, signed in, not rate-limited. */
68
+ async health() {},
69
+ };
70
+ ```
71
+
72
+ `claude.js` is today's behaviour moved behind the interface, so nothing changes for existing agents. Each agent gets an `engine` field (default `claude`, so existing state needs no migration).
73
+
74
+ ## The engines
75
+
76
+ Gemini CLI flags are checked against 0.64.0 (`test/fixtures/gemini/help-0.64.0.txt`) and Codex against codex-cli 0.163.0 (`test/fixtures/codex/`: help texts, a scrubbed session log, and recorded trust and approval screens). Both CLIs change quickly, so adapters check `--version` and the fixtures are refreshed on upgrades.
77
+
78
+ | | Claude Code | Codex CLI | Gemini CLI |
79
+ | --- | --- | --- | --- |
80
+ | Start with a task | `claude --session-id <id> -- "<task>"` | `codex --no-alt-screen … "<task>"` | `gemini --session-id <uuid> -i "<task>"` (interactive after the first prompt) |
81
+ | Resume | `--resume <id>` | `codex resume [options] <id>` | `--resume <id or index>` |
82
+ | Session ID | Chosen by us | Picked by Codex; adopted from the new session log in the agent's folder (its first line, `session_meta`, records the folder and is often 20+ KB) | Chosen by us (`--session-id <uuid>`, 0.64) |
83
+ | Session logs | `~/.claude/projects/…/*.jsonl` | `~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl` | `~/.gemini/tmp/<project hash>/chats/` |
84
+ | Project instructions | `CLAUDE.md` | `AGENTS.md` | `GEMINI.md` |
85
+ | Extra instructions per launch | `--append-system-prompt` | none: config override, or the first message | none: the first message (`GEMINI_SYSTEM_MD` replaces the whole system prompt, so it isn't used) |
86
+ | Extra writable folder | `--add-dir` + `--allowedTools` | `--add-dir` (newer versions) or sandbox `writable_roots` | `--include-directories`, plus a `--policy` file allowing writes there |
87
+ | Folder trust | `~/.claude.json` | `~/.codex/config.toml` trusted projects | `--skip-trust` (this session only) |
88
+ | Model | `--model` | `-m` | `-m` |
89
+ | Cost | Recorded by Claude Code | Tokens only (`token_count` events; cached input is counted inside input, so it is subtracted) | Tokens only |
90
+ | Rate limits | — | In every `token_count` event (`rate_limits.primary.used_percent`, `rate_limit_reached_type`): the input for fail over | — |
91
+
92
+ ### Permission modes
93
+
94
+ Agent Manager's three modes map to each engine's own approval settings. The hub's rule stays: no mode that skips all prompts can be started from the hub.
95
+
96
+ | Agent Manager | Claude Code | Codex CLI | Gemini CLI |
97
+ | --- | --- | --- | --- |
98
+ | `default` (ask) | `default` | `--sandbox workspace-write --ask-for-approval on-request` (0.163 only has `on-request` and `never`) | `--approval-mode default` |
99
+ | `acceptEdits` | `acceptEdits` | the same: edits inside the workspace never ask | `--approval-mode auto_edit` |
100
+ | `plan` | `plan` | `--sandbox read-only --ask-for-approval on-request` | `--approval-mode plan` (read-only) |
101
+ | skip all (local only) | `bypassPermissions` | `--dangerously-bypass-approvals-and-sandbox` | `--yolo` |
102
+
103
+ ### Briefs and questions
104
+
105
+ The brief and questions files are what make an agent reviewable from the inbox, so every engine needs them.
106
+
107
+ - **Instructions:** today they go in `--append-system-prompt`. Codex and Gemini have no per-launch system prompt flag, so the instructions go in the first message of a new session, and are repeated briefly on resume (in the resume message where the engine accepts one). Writing them into the repo's `AGENTS.md`/`GEMINI.md` is rejected: that would change the user's repo.
108
+ - **Writing outside the repo:** Codex's sandbox only writes inside the workspace by default. Options, in order of preference:
109
+ 1. Allow the brief folder as an extra writable root (`--add-dir` or `writable_roots`).
110
+ 2. Have the agent write `.agentmgr/BRIEF.md` inside its worktree, add `.agentmgr/` to the worktree's local git excludes (`.git/info/exclude` of the worktree, never the repo's `.gitignore`), and have Agent Manager read it from there.
111
+ - **Parsing** (`parseBrief`, `parseQuestions`) is already engine-neutral.
112
+
113
+ ### Usage, activity and stuck detection
114
+
115
+ `scan()` turns each engine's log into the same events: usage totals, PR and bug links from tool output, and tool-call/result pairs for stuck detection (`stuck.js` takes events, not Claude records). Engines without a cost figure show tokens only. If an engine's log format can't be read, the agent still works, without usage and stuck findings, and the UI says so.
116
+
117
+ ### Inbox prompts
118
+
119
+ `promptChoices` reads the terminal screen. Each adapter gets its own pattern, backed by recorded screen fixtures in the tests. Pressing a digit or Esc goes through the same `prompt-key` action, so the hub needs no change.
120
+
121
+ ### Explain and documents
122
+
123
+ These one-shot calls default to the agent's own engine (`oneShot`), and fall back to any healthy engine, since they only need the transcript digest and brief.
124
+
125
+ ## Fail over across providers
126
+
127
+ - **Engine health:** each daemon checks every configured engine: installed, signed in, and recent failures. A run of authentication errors, rate-limit messages or crashes on launch marks it *degraded* for a cool-down. The adapter supplies the patterns it recognizes.
128
+ - **New work:** an item can ask for one engine, a preference list (`["claude", "codex"]`), or `any`. The local backlog and the hub's shared backlog skip degraded engines, and each machine reports its engines to the hub as labels (`engine:codex`), so placement works as it does for other labels.
129
+ - **Running agents:** never moved automatically. When an agent's engine is degraded, the inbox shows **Continue on Codex** (or another engine), which performs a handoff:
130
+ 1. Checkpoint the worktree (commit on the agent's branch, as checkpoints do today).
131
+ 2. Start a new session of the other engine in the same worktree and branch.
132
+ 3. Its first message says what the task is, points to the brief, the questions and the commit log, and asks it to continue from there.
133
+ The old session is kept, so the agent can switch back later.
134
+
135
+ ## Config
136
+
137
+ ```json
138
+ {
139
+ "engine": "claude",
140
+ "engines": {
141
+ "claude": { "bin": "claude" },
142
+ "codex": { "bin": "codex", "args": [] },
143
+ "gemini": { "bin": "gemini", "args": [] }
144
+ },
145
+ "projects": { "web": { "engine": "codex" } },
146
+ "failover": ["claude", "codex", "gemini"]
147
+ }
148
+ ```
149
+
150
+ `claudeBin` and `claudeArgs` keep working as aliases of `engines.claude`.
151
+
152
+ ## UI and CLI
153
+
154
+ - **Engine picker** in New agent, Add to backlog and race variants; a small engine tag on each agent.
155
+ - **Races across engines:** the same intent on Claude, Codex and Gemini, scored the same way. This comes almost for free and is a good way to compare engines on real work.
156
+ - **CLI:** `agentmgr add --engine codex …`, `agentmgr engines` (installed, signed in, healthy), `agentmgr continue <agent> --engine gemini`.
157
+ - **Hub:** `engine` joins the fleet whitelist; machines report their engines; the shared backlog takes an engine preference.
158
+
159
+ ## Testing
160
+
161
+ The e2e suite uses a fake Claude (`test/fake-claude.js`). Each engine gets its own fake that writes sessions in that engine's log format and draws its prompts, so the adapters are tested without accounts. Phase 1 adds a manual check script that runs each real CLI once, records its flags, log format and prompt screens, and stores them as fixtures.
162
+
163
+ ## Plan
164
+
165
+ | Phase | Delivers |
166
+ | --- | --- |
167
+ | 1. Adapter seam ✅ | `src/engines/claude.js` with today's behaviour behind the interface; `engine` on agents; real CLIs checked and recorded as fixtures |
168
+ | 2. Antigravity CLI ✅ | Replaces Gemini CLI. `src/engines/antigravity.js`: launch with instructions in the first message, adoption of the conversation agy picks (`cache/last_conversations.json`), resume with `--conversation`, usage, commands and writes from `brain/<id>/.system_generated/logs/transcript_full.jsonl`, command / file / access menus, trust in `settings.json` `trustedWorkspaces`, Explain in print mode (`--mode plan`, prompt attached to `--print=`). Non-Claude engines get a shorter conventions note: a smaller model turned the Claude-specific one into a search of the whole disk. |
169
+ | 3. Codex CLI ✅ | `src/engines/codex.js`: launch and resume, session adoption, brief folder (`--add-dir`), usage, stuck detection, prompts, trust, Explain through `codex exec`; engine pickers in the CLI, the page and the hub; machines report `engine:*` labels for the shared backlog. Not yet: planning while cloning and forks. Built before Gemini, which waits on a sign-in. |
170
+ | 4. Fail over | Engine health, preference lists in both backlogs, engines reported to the hub, **Continue on…** handoff |
171
+
172
+ ## Open questions
173
+
174
+ 1. **Order:** Gemini first (you named it), then Codex?
175
+ 2. **Default engine** for new agents stays Claude, with others chosen per agent or project?
176
+ 3. **Fail over:** automatic for new work only, with running agents switched only when you click **Continue on…**?
177
+ 4. **Accounts:** Gemini CLI signed in with a Google account (free tier limits) or an API key / Vertex AI? Codex with a ChatGPT plan or an API key? This affects rate limits and the health checks.
Binary file
Binary file
@@ -0,0 +1,81 @@
1
+ # Plugins
2
+
3
+ A plugin extends Agent Manager from a separate package, without changing its code: add policies that can refuse actions, listen to what happens (for an audit trail), serve extra API routes, add an agent engine, or (on the hub) add a sign-in method.
4
+
5
+ ## Loading
6
+
7
+ **Daemon:** list modules in `~/.agentmanager/config.json`, or in `$AGENTMGR_PLUGINS` (comma separated):
8
+
9
+ ```json
10
+ { "plugins": ["@acme/agentmanager-policy", "./plugins/audit.js"] }
11
+ ```
12
+
13
+ Package names are resolved from Agent Manager's install; paths are relative to `~/.agentmanager` (`~` works too).
14
+
15
+ **Hub:** `AGENTMGR_HUB_PLUGINS=@acme/agentmanager-sso,./plugins/hub-audit.js` (paths relative to the hub's state folder).
16
+
17
+ If a listed plugin fails to load, the daemon or hub doesn't start. A policy that is silently missing is worse than no start.
18
+
19
+ ## Writing one
20
+
21
+ ```js
22
+ // audit-and-policy.js
23
+ export const name = 'acme'; // routes are served under /api/ext/acme
24
+
25
+ export default function register(api) {
26
+ // Refuse an action by returning { allow: false, reason }. Anything else allows it.
27
+ api.policy((action, details) => {
28
+ if (action === 'agent.create' && details.input.permissionMode === 'bypassPermissions') {
29
+ return { allow: false, reason: 'bypassPermissions is not allowed on company machines' };
30
+ }
31
+ if (action === 'agent.create' && details.input.repo && !details.input.repo.includes('github.com/acme/')) {
32
+ return { allow: false, reason: 'Only acme repositories' };
33
+ }
34
+ });
35
+
36
+ // Hear what happened. Listeners never block or break the action.
37
+ api.on((event, payload) => fs.appendFileSync('/var/log/agentmanager-audit.jsonl', JSON.stringify({ event, ...payload }) + '\n'));
38
+
39
+ // Extra endpoints, behind the same sign-in and request checks as the rest of the API.
40
+ api.routes((router) => router.get('/status', (req, res) => res.json({ ok: true })));
41
+ }
42
+ ```
43
+
44
+ The API object also has `api.name`, `api.config` (the daemon's config) and `api.log(message)`.
45
+
46
+ Policies are synchronous so every code path can call them. Keep them fast; do slow lookups ahead of time (on load or on a timer) and check against the result.
47
+
48
+ ## Daemon actions (policies)
49
+
50
+ | Action | When | `details` |
51
+ | --- | --- | --- |
52
+ | `agent.create` | Before an agent is created (UI, CLI, backlog, races, hub, imports) | `input`: kind, intent, name, repo, dir, worktree, base, permissionMode, model, project, priority, engine, imported |
53
+ | `agent.start` | Before an agent's engine is launched or resumed (including after a restart) | `agent`, `fresh` |
54
+ | `agent.message` | Before a message is typed into an agent (`tell`, inbox replies) | `agent`, `text`, `source` |
55
+ | `agent.remove` | Before an agent is deleted | `agent`, `deleteWorkspace` |
56
+ | `terminal.open` | Before a live terminal is attached (browser, CLI `attach`, hub) | `agent`, `kind` (`claude` or `shell:N`) |
57
+ | `hub.action` | Before an action the hub asked for, after this machine's own `hub.allow` check | `action`, `agent` |
58
+
59
+ `agent` is a summary: id, name, kind, engine, intent, repo, workdir, branch, permissionMode, model, project, status.
60
+
61
+ ## Daemon events (listeners)
62
+
63
+ `agent.created`, `agent.starting`, `agent.stopping`, `agent.message`, `agent.removed`, `hub.action`, each with the same `agent` summary and an `at` timestamp.
64
+
65
+ ## Engines
66
+
67
+ `api.engine(def)` adds an agent engine; `def` has the same shape as the built-in adapters in `src/engines/` (see [engines.md](engines.md)). Built-in engines can't be replaced.
68
+
69
+ ## Hub
70
+
71
+ | Hook | Use |
72
+ | --- | --- |
73
+ | `api.authenticate((req) => identity \| null)` | A sign-in method next to the access token and Google, for example SSO through a proxy that sets a verified header, or your own API keys. The identity (`{ user, email?, roles? }`) is `req.user` in routes and `details.user` in policies. Browser writes still need the `x-agentmgr` header. |
74
+ | `api.policy` with `hub.request` | Before every signed-in API request: `{ method, path, auth, user }`. `auth` is `bearer` (admin token), `session` (token or Google sign-in) or `plugin`. |
75
+ | `api.policy` with `hub.terminal` | Before a browser terminal to a machine opens: `{ runner, user }`. |
76
+ | `api.on` | `hub.request` for every signed-in write. |
77
+ | `api.routes` | Served under `/api/ext/<name>` after sign-in and policies. |
78
+
79
+ ## What plugins can't do
80
+
81
+ Plugins add checks and features; they can't loosen the built-in ones. A machine's own `hub.allow`, the local-only routes, the request checks in `server.js`, and remote access sign-in all apply first.
package/package.json CHANGED
@@ -1,6 +1,58 @@
1
1
  {
2
2
  "name": "ai-agentmanager",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0",
4
+ "description": "Run background coding agents (Claude Code, Codex) per intent, and keep track of them from a web UI, your phone or the CLI",
5
+ "keywords": [
6
+ "claude",
7
+ "claude-code",
8
+ "codex",
9
+ "ai-agents",
10
+ "coding-agents",
11
+ "agent-manager",
12
+ "cli"
13
+ ],
14
+ "homepage": "https://github.com/jdmunta/ai-agentmanager-core#readme",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/jdmunta/ai-agentmanager-core.git"
18
+ },
19
+ "bugs": {
20
+ "url": "https://github.com/jdmunta/ai-agentmanager-core/issues"
21
+ },
22
+ "license": "Apache-2.0",
23
+ "type": "module",
24
+ "bin": {
25
+ "agentmgr": "bin/agentmgr.js"
26
+ },
27
+ "files": [
28
+ "bin",
29
+ "src",
30
+ "public",
31
+ "scripts",
32
+ "docs",
33
+ "README.md",
34
+ "LICENSE",
35
+ "NOTICE",
36
+ "SECURITY.md"
37
+ ],
38
+ "scripts": {
39
+ "start": "node bin/agentmgr.js serve",
40
+ "test": "node --test --test-concurrency=1 test/*.test.js",
41
+ "postinstall": "node scripts/fix-pty.js"
42
+ },
43
+ "engines": {
44
+ "node": ">=20"
45
+ },
46
+ "dependencies": {
47
+ "@xterm/addon-fit": "^0.11.0",
48
+ "@xterm/xterm": "^6.0.0",
49
+ "commander": "^15.0.0",
50
+ "dompurify": "^3.4.16",
51
+ "express": "^5.2.1",
52
+ "marked": "^18.0.14",
53
+ "mermaid": "^11.17.2",
54
+ "node-pty": "^1.1.0",
55
+ "qrcode": "^1.5.4",
56
+ "ws": "^8.22.0"
57
+ }
58
+ }