@staix/agent-hub 0.12.20 → 0.12.21

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 (60) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +6 -5
  3. package/docs/agent-notes/adapters.md +9 -2
  4. package/docs/agent-notes/bus.md +5 -2
  5. package/docs/agent-notes/daemon.md +8 -2
  6. package/docs/agent-notes/local-worker.md +3 -1
  7. package/docs/agent-notes/tasks.md +3 -1
  8. package/docs/agent-notes/tests.md +3 -2
  9. package/docs/bench.md +143 -0
  10. package/docs/events.md +2 -1
  11. package/docs/operations.md +271 -46
  12. package/docs/quickstart.md +12 -10
  13. package/docs/research.md +92 -0
  14. package/docs/security.md +45 -10
  15. package/docs/smoke.md +79 -0
  16. package/docs/specs/2026-09-19-agent-hub-design.md +194 -20
  17. package/docs/specs/2026-09-20-upgrade-recovery-design.md +68 -10
  18. package/docs/verified.json +28 -17
  19. package/package.json +1 -1
  20. package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
  21. package/plugins/agent-hub/server.js +3 -2
  22. package/src/adapters/acp.ts +67 -6
  23. package/src/adapters/codex-appserver.ts +67 -2
  24. package/src/adapters/local-worker.ts +4 -3
  25. package/src/adapters/pi.ts +58 -4
  26. package/src/cli/bench.ts +287 -0
  27. package/src/cli/console-state.ts +226 -28
  28. package/src/cli/console.ts +16 -4
  29. package/src/cli/facts-hook.ts +50 -8
  30. package/src/cli/help.ts +10 -0
  31. package/src/cli/identity.ts +10 -4
  32. package/src/cli/launch.ts +96 -17
  33. package/src/cli/main.ts +159 -18
  34. package/src/cli/preview.ts +13 -3
  35. package/src/cli/status-lines.ts +2 -1
  36. package/src/cli/terminal-recovery.ts +83 -31
  37. package/src/cli/upgrade-runtime.ts +67 -18
  38. package/src/cli/upgrade.ts +81 -9
  39. package/src/hub/bench.ts +211 -0
  40. package/src/hub/bus.ts +19 -4
  41. package/src/hub/conductor.ts +22 -21
  42. package/src/hub/config-trust.ts +1 -0
  43. package/src/hub/control-client.ts +4 -1
  44. package/src/hub/daemon.ts +346 -40
  45. package/src/hub/delivery-journal.ts +6 -6
  46. package/src/hub/events.ts +2 -1
  47. package/src/hub/lifecycle.ts +8 -13
  48. package/src/hub/manager.ts +16 -14
  49. package/src/hub/permission-mode.ts +44 -0
  50. package/src/hub/recovery-store.ts +21 -9
  51. package/src/hub/registry.ts +36 -22
  52. package/src/hub/research.ts +255 -0
  53. package/src/hub/tasks.ts +70 -43
  54. package/src/hub/tool-refusal.ts +7 -0
  55. package/src/hub/ui.ts +4 -1
  56. package/src/local/tools.ts +14 -10
  57. package/src/pi/process-signature.ts +23 -0
  58. package/src/pi/tool-receipts.ts +7 -1
  59. package/src/ui/index.html +83 -6
  60. package/src/ui/task-progress.ts +33 -0
@@ -1,6 +1,6 @@
1
1
  # Operations guide
2
2
 
3
- This guide describes ahub 0.12.20 and control protocol 16. Live verification
3
+ This guide describes ahub 0.12.21 and control protocol 16. Live verification
4
4
  results and remaining prerequisites are recorded separately in [the smoke ledger](smoke.md).
5
5
 
6
6
  ## Command help
@@ -49,15 +49,17 @@ counts `needs_review` deliveries, in the attention and failure tones.
49
49
 
50
50
  Peers, Approvals, Tasks and Queue are tables with a header row. A column is as
51
51
  wide as its widest cell, up to a third of the terminal; the last column takes
52
- the rest and is cut with `...`.
52
+ the rest and is cut with `...`. In Tasks, TITLE takes the rest and the STAGE
53
+ meter keeps a fixed column at the right edge.
53
54
 
54
55
  | Panel | Columns |
55
56
  |---|---|
56
- | Peers | PEER STATE LINK Q ! REVIEW PAUSE QUOTA MODEL |
57
+ | Peers | PEER STATE MODE LINK Q ! REVIEW PAUSE QUOTA MODEL |
57
58
  | Approvals | ID PEER LEFT TITLE |
58
- | Tasks | ID STATE OWNER REVIEWER CLASS AGE TITLE |
59
+ | Tasks | ID STATE OWNER REVIEWER CLASS AGE TITLE STAGE |
59
60
  | Queue | ID PEER STATE REV AGE |
60
61
 
62
+ The MODE column appears only when at least one peer has a non-`ask` mode.
61
63
  Zero counters and unknown values read `-`. Durations read `45s`, `12m`, `3h05m`
62
64
  or `2d03h`; quota resets and budget pauses read `in 2h13m`, and a pause by the
63
65
  console user reads `user`. When a peer's status carries #205's optional
@@ -83,14 +85,43 @@ showed it. The footer's last line lists only the keys that act in the current
83
85
  mode, panel and state. A notice shows on the footer's second line until the next
84
86
  key or for about ten seconds: errors and refusals (a refused command, a request
85
87
  error, the size refusal) in the failure tone, notices that only inform (a closed
86
- or missing selection) in the attention tone. With a detail open, `p`, `r` and `a`
88
+ or missing selection) in the attention tone. With a detail open, `p`, `r`, `m` and `a`
87
89
  act on the item the detail shows. `:` enters a command; its output lines
88
- start at column 4, as message bodies do. Assignment, delivery resolution and
89
- allow decisions require confirmation; delivery resolution requires a reason. Tasks use the same public redaction as the board. Panels need at least
90
- 80 columns by 24 rows; smaller terminals stay in stream mode. Task and queue
91
- polling runs only while the corresponding panel is visible. Leaving restores
90
+ start at column 4, as message bodies do. Assignment, delivery resolution, allow
91
+ decisions and a never-ask permission mode require confirmation; delivery resolution requires a reason. Tasks use the same public redaction as the board. Panels need at least
92
+ 80 columns by 24 rows; smaller terminals stay in stream mode. Full task and queue reads run only in panels; stream progress uses the existing
93
+ status state counts and never fetches the board. Leaving restores
92
94
  the terminal and returning from panels replays the bounded stream buffer.
93
95
 
96
+ The Tasks panel summarizes the whole public board with an approved/all fraction,
97
+ a 20-cell ASCII bar and state counts. Proposed tasks with unapproved or missing
98
+ `after` dependencies count as waiting; PII stubs count normally. Each task has a
99
+ four-cell stage meter (proposed, in progress, review, approved). Changes requested
100
+ returns to in progress and uses `!` plus the failure tone. Narrow panels retain
101
+ the fraction/bar before optional counts, which appear in fixed importance order:
102
+ changes requested, review, waiting, in progress, proposed, then unknown when nonzero. Narrow widths drop
103
+ only the suffix. Bar cells and percentages round down so open tasks never imply
104
+ completion. Stream `tasks N/M approved` lives on the approvals footer line, leaving the
105
+ peer/queue footer line intact. Operator notices or an already-full approvals line
106
+ take precedence over this optional count. A brief notice hides it only until the
107
+ notice's ten-second TTL expires, then the retained known count returns on redraw; the scroll
108
+ region starts at row 1 to preserve terminal scrollback. All existing status state
109
+ counts contribute to its total, including an unfamiliar state. Until a successful
110
+ status read it says loading; later failures preserve the prior count with a notice.
111
+ Panels retain their existing full-board refresh and loading/error handling.
112
+
113
+ The dashboard Task board shows the same shared public-board progress model,
114
+ including state counts, waiting, stacked segments and a labeled four-stage track
115
+ per row. Filtering rows does not filter overall progress. The existing signature guard
116
+ updates the live region only when progress changes. The stacked SVG fills the
117
+ section width; its text legend carries every state count. Theme offers System,
118
+ Light and Dark; it is applied before first paint and stored in a host-scoped loopback cookie
119
+ (`Path=/`, `SameSite=Strict`, one-year lifetime), shared across dashboard ports.
120
+ Blocked cookie access defaults to System on reload and never prevents operation. System follows
121
+ OS color preference through CSS. Both inline scripts and the style retain CSP
122
+ hashes; there are no external chart/theme assets. Live theme/readability inspection
123
+ is tracked in `docs/smoke.md` and remains unverified until performed.
124
+
94
125
  Console color policy is `--color=auto|always|never`, with `auto` as the default.
95
126
  Auto enables color only when both input and output are TTYs, `TERM` is not
96
127
  `dumb`, and `NO_COLOR` is empty or absent. Explicit `always` overrides these
@@ -105,11 +136,30 @@ management or the final reset. Invalid values fail before connecting.
105
136
  | Waiting and attention | Yellow | Busy/paused peer, pending approval, confirmation, review/ready task, important priority |
106
137
  | Failure and intervention | Red | Failed/check-failed task, undeliverable/overflow, `needs_review` queue, denial request or expired/cancelled approval |
107
138
  | Metadata | Bright black | Ages, remaining approval time and rules |
139
+ | Stream peer names | Claude bright blue (94), Codex bright cyan (96); all others default | Fixed map, no hashing or themes |
140
+ | Stream `task` keyword | Magenta | Task event keyword |
141
+ | Stream numbers | Bold default foreground (1) | Percentages, durations and measured times |
142
+ | Stream issue references | Underlined default foreground (4) | `#232` |
143
+ | Stream task references | Magenta underlined (4;35) | `#1` following `task`/`Task`, `[task #1]`, `[review #1]` |
108
144
 
109
145
  Offline peers, primary titles, action details and message body lines keep the
110
146
  terminal's default foreground. Labels and prompts remain readable without
111
- color. Stream styling applies only to the header, using structured event data;
112
- message text cannot choose a color. A local denial is shown as requested, not
147
+ color. Stream styling applies only to meaningful tokens in the first line: peer names,
148
+ `task`, numbers and references. Ordinary words keep the default foreground.
149
+ Task prefixes and bracketed `[review #N]` identify task references
150
+ case-insensitively; prose `review #N` and other `#N` tokens are issue references. Legacy assign/which notices lack typed reference
151
+ ranges and use the issue tone until structured spans identify them. The dated
152
+ #239 palette reserves green/yellow/red for states, cyan for navigation and
153
+ blue/cyan for Claude/Codex. Tokens use plain magenta, underlining and bold default
154
+ foreground; no token SGR equals a state or peer SGR. The `task` keyword is never
155
+ bold. Only hub-written peer slots are colored, not title words or decline reasons.
156
+ State words and structural markers retain their event tone. Numeric-looking
157
+ identifiers stay plain. Wrapped headers normalize tabs before tokenization; plain headers preserve
158
+ their original tabs with color enabled or disabled.
159
+ A projection miss on the first physical header keeps only structural tones.
160
+ A missed continuation and every following continuation are fully plain; their
161
+ text cannot supply structural tones. Projection carries its span index forward
162
+ instead of rescanning earlier spans for every line. Bodies and command output stay plain. A local denial is shown as requested, not
113
163
  as a confirmed receipt; a remote answered closure has no option-kind metadata
114
164
  and is not guessed to be a denial. A fixed palette is applied after terminal
115
165
  control sanitization. Width, clipping, wrapping and cursor placement use plain
@@ -129,8 +179,8 @@ recorded separately in the smoke ledger; fake-terminal tests establish policy,
129
179
  geometry, sanitization and restoration only.
130
180
 
131
181
  The command input accepts existing status, board, task, review, say, pause,
132
- resume, budget, queue, permit, ask, remember, route, turns, undo, check-path and
133
- report operations. It executes an argument vector with closed stdin. Lifecycle,
182
+ resume, budget, queue, permit, permission, ask, remember, route, turns, undo,
183
+ check-path and report operations. It executes an argument vector with closed stdin. Lifecycle,
134
184
  launch, nested console, setup, UI, logs and tail commands are refused.
135
185
 
136
186
  ## Conducting a team from Claude Code or Codex
@@ -177,9 +227,11 @@ off withdraws pending feed notices.
177
227
 
178
228
  For a Claude conductor, `ahub claude` also observes native session and turn
179
229
  boundaries when facts injection and task-idle sweeps are off. Keep the managed
180
- hooks enabled to measure completion and supervision usage. Passing your own
181
- `--settings` takes precedence and produces a warning when it replaces that
182
- observation. The launcher records its private session identity in ordinary
230
+ hooks enabled to measure completion and supervision usage. Your own
231
+ `--settings` (given once, a JSON object or file) keeps that observation: the
232
+ launcher adds the hub's hooks to its hooks and warns only that the status line
233
+ tee is off; one that sets `disableAllHooks` is refused. The launcher records its
234
+ private session identity in ordinary
183
235
  terminals too; this does not grant terminal-recovery authority.
184
236
  Ordinary Claude sessions with turn-free facts or task-idle sweeps enabled get the
185
237
  same session/start observation. Other ordinary launches remain non-opt-in.
@@ -216,7 +268,8 @@ may request an approval.
216
268
 
217
269
  `.agenthub/config.json` can be committed and shared. The fields that choose
218
270
  what the hub runs, which files it sends as credentials, where task text goes,
219
- or how far the local worker's sandbox reaches (`kimi_cmd`, `codex_bin`,
271
+ how far the local worker's sandbox reaches, or how often a peer asks before it
272
+ acts (`kimi_cmd`, `permission_modes`, `codex_bin`,
220
273
  `pi.cmd`, `checks`, `mlx.bin`, `mlx.runtimeDir`, `mlx.modelPath`, `omniroute.urls`,
221
274
  `omniroute.access_hosts`, the `omniroute` key files, `memory.worker_url`,
222
275
  `local.read_allow`, `local.bash_network`, `local.network_allow`) are machine-local:
@@ -399,7 +452,10 @@ offered to nobody, cannot be claimed, accepted or marked done, and `ahub route
399
452
  explain <id>` says what it waits for. When the last one is approved, the task
400
453
  goes through assignment like a new one; if the hub stopped before it got that
401
454
  far, the task is offered within a minute of a peer that can take it attaching
402
- to the next run.
455
+ to the next run. An offer is used up only by an assignment, after an approval
456
+ and on the timer alike. If no peer can take it or a ready/assignment write fails,
457
+ the task stays eligible; the timer adds no history or notices while nobody can
458
+ take it.
403
459
  `ahub board --ready` and
404
460
  `hub_task_list {ready: true}` list the proposed tasks with nothing left to wait
405
461
  for. Dependencies are fixed when a task is proposed and can only name tasks that
@@ -498,6 +554,37 @@ turn, otherwise the peer's only `in_progress` task. Everything else is reported
498
554
  unattributed, and records from before 0.12.18 as a separate bucket; neither is
499
555
  redistributed (rules: [events](events.md#per-task-usage-reports)).
500
556
 
557
+ ## Research records
558
+
559
+ With `"research": { "enabled": true }` in `.agenthub/config.json` (off by default), the hub keeps one record per
560
+ approved task in `~/.agenthub/research/`, shared by every project that opts in: outcome, review rounds, rework, failed
561
+ checks, tokens and time per peer and model, never task or message text ([schema](research.md)).
562
+
563
+ ```bash
564
+ ahub research --since 30d # success, first-pass, rework and check-failure rates; tokens and wall time per task
565
+ ahub research --all --json # every opted-in project on this machine
566
+ ahub research export --format csv # the records, for your own analysis
567
+ ahub research backfill # build this project's records once from events.jsonl
568
+ ahub task label 12 reverted # a later verdict (ok, regressed, reverted, incomplete, wrong, abandoned)
569
+ ```
570
+
571
+ ## Benchmarks
572
+
573
+ In a git repository kept for benchmarks (`"bench": { "enabled": true }` in its `.agenthub/config.json`, a clean tree),
574
+ `ahub bench` runs a suite of tasks against the peers attached to its hub and compares configurations: pass rate, first
575
+ pass, tokens and wall time per attempt, with bootstrap intervals between arms. Each attempt resets the tree to the
576
+ task's commit, so never enable it in a project you work in. The project must be the root of its repository with
577
+ nothing tracked under `.agenthub/`, and the suite file lives outside its tree ([suite format and store](bench.md)).
578
+
579
+ ```bash
580
+ ahub bench run ../suites/suite.json --arm baseline --repeat 5 # a person's: resets the tree, proposes the tasks
581
+ ahub bench status # the run in progress
582
+ ahub bench compare baseline ripwire-on # arms side by side, inconclusive below 5 attempts each
583
+ ahub bench export --format csv
584
+ ```
585
+
586
+ The dashboard's Benchmarks section shows the latest runs and, per suite with two or more arms, the comparison.
587
+
501
588
  ## Turns and undo
502
589
 
503
590
  In a git work tree the hub snapshots the project's tracked and unignored files (the
@@ -607,8 +694,9 @@ stays the default until an evaluation says otherwise (`docs/cooperbench.md`).
607
694
  held.
608
695
  - Verified context path. A hub-launched Claude session (`ahub claude`) gets the
609
696
  hub's hook before and after every tool call and at the end of each turn, in
610
- its `--settings` next to the status line tee (a `--settings` of your own turns
611
- them off). Codex gets context by steer into its running turn. Until a fact or a
697
+ its `--settings` next to the status line tee (a `--settings` of your own keeps
698
+ them, added to its own hooks, and loses only the tee). Codex gets context by
699
+ steer into its running turn. Until a fact or a
612
700
  one-line probe has been read back, the peer counts as unverified: for Claude
613
701
  the row Claude Code writes in its transcript for the hook's additional
614
702
  context, matched by tool use id and the offer's id; for Codex the steered
@@ -753,6 +841,83 @@ stays the default until an evaluation says otherwise (`docs/cooperbench.md`).
753
841
  turns the stale-notice drop off, for a controlled comparison only (the #106
754
842
  ablation in `docs/cooperbench.md`); the hub logs it at start.
755
843
 
844
+ ## Permission modes for running peers
845
+
846
+ Use a plain terminal to inspect or change a running peer:
847
+
848
+ ```bash
849
+ ahub permission
850
+ ahub permission local ask-when-needed
851
+ ahub permission kimi never-ask --yes
852
+ ahub permission kimi ask
853
+ ```
854
+
855
+ In the console Peers panel, `m` opens the three modes. Never-ask requires `y`,
856
+ even for a typed command carrying --yes. Its confirmation says whether the peer
857
+ uses its own tools outside the hub sandbox (Kimi), the hub sandbox/path guard/
858
+ denylist (Pi and local), or native vendor boundaries (Claude and Codex).
859
+
860
+ | Peer | ask-when-needed | never-ask | ask |
861
+ | --- | --- | --- | --- |
862
+ | Kimi Code CLI | ACP yolo | ACP auto | ACP default |
863
+ | Codex | on-request for every outgoing turn/start | never for every outgoing turn/start | Restore the known native policy once, then stop overriding |
864
+ | Claude | Allow resolving project file tools outside .agenthub/.git/.claude/.codex/.qwen/.kimi/.pi/.mcp.json | Hook allow for every tool | No hook decision |
865
+ | Pi and local | Grant write/edit outside native agent configuration paths; read never asks; shell/git writes ask | Grant each tool once within the existing sandbox/guard | Normal approval handling |
866
+
867
+ Kimi's map applies only to the actual Kimi Code CLI identity, not another ACP
868
+ agent configured in kimi_cmd. Qwen and other ACP agents start fresh/resumed
869
+ unmanaged without mode changes or mode-based startup refusals; runtime changes
870
+ are refused until their own map exists.
871
+ A set_mode timeout leaves the peer offline with mode unknown; inspect and
872
+ restart before using it. Missing ids and refused startup changes name the mode.
873
+ The benign Kimi live check verifies file edits and harmless shells in both
874
+ modes without requests; risky-action behavior remains unverified.
875
+
876
+ Claude's hook is installed in every managed launch. File grants use canonical
877
+ real paths under the authenticated project root. Missing targets, symlink
878
+ escapes, metadata directories and uncertain wildcard targets leave the native
879
+ rules in charge. Grep/Glob path-bearing filters cannot bypass the exclusions.
880
+ Other tools retain native decisions, which may already allow a harmless shell.
881
+ Permission-only PreToolUse never marks Claude busy; former observation hooks
882
+ retain their native-turn role. Settings are passed as a 0600 state file retained
883
+ until that native launch exits. Live/unknown previous launchers keep their files;
884
+ verified dead wrapper and native identities permit crash cleanup. Without
885
+ separate native identity proof, old settings remain for manual inspection. Settings carry
886
+ no inline caller values in argv. Status reports an unverified Claude hook as
887
+ unverified. Unattended native sessions cannot take a runtime mode; restart
888
+ without that native flag. Codex must be behind the hub proxy, and returning to
889
+ ask requires a known native approval baseline. An offline or absent peer accepts
890
+ `ahub permission <peer> ask` to clear its hub choice. Codex --unattended launch
891
+ is refused under a non-ask choice; clear it with `ahub permission codex ask`
892
+ first. A pending never-ask default cannot be confirmed for an unattended launch.
893
+
894
+ Project defaults use permission_modes in .agenthub/config.json or
895
+ .agenthub/config.local.json. Keys merge per peer; a local ask overrides the
896
+ same peer's earlier default, while an empty local block preserves it. Every
897
+ non-ask default start logs its mode and contributing filename. Automatic
898
+ config effects stop at ask-when-needed. Git-tracked defaults are ignored and never
899
+ offered for confirmation. Never-ask from an applied untracked file starts in ask
900
+ and appears as a pending source-labelled console question at hub start.
901
+ Only its separate console y enables that default; there is no boot --yes
902
+ bypass. Only explicit n declines and keeps ask for that hub. Esc defers without declining
903
+ and leaves approval keys usable; a refused y is not reoffered until its state changes. A successful runtime change removes that pending default. Runtime choices expire when the hub
904
+ stops, and a later restart asks again for a never-ask config default.
905
+
906
+ The CLI marker and console-role checks are operating policy, not a hostile-agent
907
+ boundary. An agent able to run unrestricted shells may clear its markers,
908
+ use --yes, or read the control token and act as the console. Hub tools,
909
+ conductor operations and ordinary agent messages have no direct mode-changing
910
+ or startup-confirmation operation, but those checks do not contain such a
911
+ shell. Review the boundaries in [security](security.md) before choosing a mode.
912
+ Modes never alter native sandboxes, hub path guards or denylists.
913
+
914
+ Permission mode starts reconcile the current operator choice after native startup before bus delivery. Each non-ask start logs the mode and its config or runtime source. Dropped git-tracked defaults never appear as startup confirmations. Non-Kimi ACP peers run unmanaged, preserving their native fresh/resumed mode; permission commands refuse changes until a vendor map exists.
915
+
916
+ Status shows non-ask modes and pending defaults, and each runtime change emits
917
+ permission_mode (peer/from/to) with no tool arguments under events schema 1.
918
+ The optional control requests preserve PROTOCOL; an older hub answers unknown
919
+ and the CLI advises upgrading.
920
+
756
921
  ## Approvals and pauses
757
922
 
758
923
  Inspect permission requests in the terminal:
@@ -771,8 +936,8 @@ ahub permit <request-id> allow
771
936
 
772
937
  Use the exact option shown by `ahub tail`; do not approve an unresolved or
773
938
  unexpected request. On macOS a waiting request also raises a desktop
774
- notification that names the peer and, for Kimi, the tool, never what it would
775
- run. An unanswered request is cancelled after `approvals.timeout_s` (default
939
+ notification that names the peer and, for Kimi and the local worker, the tool,
940
+ never what it would run. An unanswered request is cancelled after `approvals.timeout_s` (default
776
941
  120, 30 to 3600) in `.agenthub/config.json`, and the console and log say so.
777
942
  Set `approvals.notify` to `false` to turn notifications off; a hub started
778
943
  without a project config file raises none. While Kimi waits for an answer its
@@ -832,6 +997,9 @@ has only `ahub status`, `ahub tail` and the task board.
832
997
 
833
998
  Resolution requires the current observed revision. A retry closes the old
834
999
  record and creates one linked attempt. Stale or conflicting resolutions fail.
1000
+ A refused or failed resolution puts back only that peer's queue and preface
1001
+ and keeps every pause, including budget and conductor holds. A storage failure
1002
+ stops the bus; a validation refusal leaves it running under the existing holds.
835
1003
  If dispatching or accepted work has an uncertain outcome after a crash,
836
1004
  the system marks it `needs_review`. Establish evidence before choosing
837
1005
  `completed`, `retry`, or `discard`. Timeouts,
@@ -910,8 +1078,10 @@ The runtime reset (the default):
910
1078
  - drops the agent session resume pointers `sessions.json`,
911
1079
  `claude-session.json` and `claude-context.json`, so the next launch starts
912
1080
  new agent sessions. Pi transcripts under `pi-sessions/` stay;
913
- - removes the manifest (`status.json`, `control-token`, `hub.pid`) a hub left
914
- when it did not stop cleanly, once no process behind it is alive;
1081
+ - removes the manifest (`status.json`, `control-token`, and `hub.pid` from a
1082
+ hub of 0.12.20 or older) a hub left when it did not stop cleanly, once no
1083
+ process behind it is alive (a pid that now belongs to another process counts
1084
+ as gone when the manifest carries the hub's signature, from 0.12.21);
915
1085
  - keeps the board (tasks, reviews, outcomes, turns, touches), `hub.log`,
916
1086
  `events.jsonl` and `cli-audit/`, recovery records, execution budgets,
917
1087
  configuration and files the hub does not own.
@@ -1001,21 +1171,21 @@ Rows without a live process are stale registrations; forget them with
1001
1171
 
1002
1172
  Upgrade running projects with the target release's own coordinator. It accepts
1003
1173
  a running source on control protocol 9 (0.6.x), 10 (0.7.0 through 0.12.0),
1004
- 11 (0.12.1 and 0.12.2), 12 (0.12.3), 13 (0.12.4 through 0.12.15), 14 (0.12.16), 15 (0.12.17 through 0.12.19) or 16 (0.12.20), and only
1174
+ 11 (0.12.1 and 0.12.2), 12 (0.12.3), 13 (0.12.4 through 0.12.15), 14 (0.12.16), 15 (0.12.17 through 0.12.19) or 16 (0.12.20 and 0.12.21), and only
1005
1175
  a target on its own protocol, so the target's coordinator fits every supported
1006
1176
  source and carries every recovery fix released up to it. Protocol 8 and older
1007
1177
  (0.5.x and earlier) are refused as `manual-bootstrap-required`. Run from the
1008
1178
  project directory, without replacing the global CLI first:
1009
1179
 
1010
1180
  ```bash
1011
- bunx --package @staix/agent-hub@0.12.20 ahub upgrade --to 0.12.20 --dry-run
1012
- bunx --package @staix/agent-hub@0.12.20 ahub upgrade --to 0.12.20 --yes
1181
+ bunx --package @staix/agent-hub@0.12.21 ahub upgrade --to 0.12.21 --dry-run
1182
+ bunx --package @staix/agent-hub@0.12.21 ahub upgrade --to 0.12.21 --yes
1013
1183
  ```
1014
1184
 
1015
1185
  | Running now | Coordinator to use |
1016
1186
  | --- | --- |
1017
1187
  | 0.6.x (protocol 9) | the target's, through `bunx` as above |
1018
- | 0.7.0 through 0.12.0 (protocol 10), 0.12.1 and 0.12.2 (protocol 11), 0.12.3 (protocol 12), 0.12.4 through 0.12.15 (protocol 13), 0.12.16 (protocol 14), 0.12.17 through 0.12.19 (protocol 15), 0.12.20 (protocol 16) | the target's, through `bunx` as above |
1188
+ | 0.7.0 through 0.12.0 (protocol 10), 0.12.1 and 0.12.2 (protocol 11), 0.12.3 (protocol 12), 0.12.4 through 0.12.15 (protocol 13), 0.12.16 (protocol 14), 0.12.17 through 0.12.19 (protocol 15), 0.12.20 and 0.12.21 (protocol 16) | the target's, through `bunx` as above |
1019
1189
  | any supported source, with the installed CLI already at the target | `ahub upgrade` below, which is the same coordinator |
1020
1190
  | 0.5.x or earlier (protocol 8 and older) | not supported: bootstrap by hand with the matching CLI |
1021
1191
 
@@ -1047,14 +1217,14 @@ projects first:
1047
1217
 
1048
1218
  ```bash
1049
1219
  ahub restart --dry-run
1050
- ahub upgrade --to 0.12.20 --dry-run
1220
+ ahub upgrade --to 0.12.21 --dry-run
1051
1221
  ```
1052
1222
 
1053
1223
  Apply only after reviewing the plan:
1054
1224
 
1055
1225
  ```bash
1056
1226
  ahub restart --yes
1057
- ahub upgrade --to 0.12.20 --yes
1227
+ ahub upgrade --to 0.12.21 --yes
1058
1228
  ahub recovery status <operation-id>
1059
1229
  ahub recovery resume <operation-id>
1060
1230
  ahub recovery abort <operation-id>
@@ -1179,14 +1349,22 @@ next actions").
1179
1349
  get past what is live: not past a replaced or missing source, a source that
1180
1350
  crashed before any commit request, a refusal at staging (the target release
1181
1351
  or the preserved source can never change back), a hub of another control
1182
- protocol, a target that stopped after it started, or a stopped hub without
1183
- the operation's restart snapshot.
1352
+ protocol, a target that stopped after it started when the operation's
1353
+ coordinator is older than 0.12.21, or a stopped hub without the operation's
1354
+ restart snapshot.
1184
1355
  A second `resume` while its own hold still stands checks the peers again
1185
1356
  before it closes anything.
1186
- - When a target hub dies after it started (a crash or a reboot), restarting it
1187
- from its snapshot is not supported in this release (its peers would have to
1188
- be relaunched against the new hub): `resume` is not offered and the way out
1189
- is stop-and-archive.
1357
+ - When a target hub dies after it started (a crash or a reboot), `resume`
1358
+ starts it again from the operation's snapshot while the project's state
1359
+ directory still holds it: it records the restart (status shows `restarts`),
1360
+ closes the terminals the dead hub's peers were restored into (or finds them
1361
+ gone), and relaunches those peers against the new hub, a session you had
1362
+ accepted instead of the original included. A launcher still running from
1363
+ the dead hub, or a session attached without an id, stops it until you end
1364
+ that session. A target that dies again in the same `resume` stops it with the
1365
+ restart count; look at its `hub.log` before resuming again. Without the
1366
+ snapshot, or for an operation started by a coordinator older than 0.12.21,
1367
+ the way out is stop-and-archive.
1190
1368
  - A Codex conversation comes back only when a rollout file naming its thread
1191
1369
  exists under `sessions/` of the store the restored terminal uses (the
1192
1370
  recorded `CODEX_HOME`, else `~/.codex`). Codex writes that file with the
@@ -1220,7 +1398,13 @@ next actions").
1220
1398
  be read counts as unknown, never as "nothing attached", and so does a launcher
1221
1399
  record file that cannot be read: `resume` then stops without changing any
1222
1400
  receipt; when the record file is what cannot be read, inspect it and move it
1223
- aside, then `resume`. `resume` is refused while a stop-and-archive is partway. A lifecycle command refused by the lock names
1401
+ aside, then `resume`. A record file that parses but holds a row that cannot
1402
+ be evaluated (a hand edit, a damaged or a future layout) is read the same way
1403
+ for the peer that row may belong to, and `ahub codex`, `ahub claude` and
1404
+ `ahub pi --mode tui` write such rows back unchanged: fix or remove that row,
1405
+ then `resume`. A launcher recorded by 0.12.12 or older is read with its own
1406
+ unpinned signature too, so it counts as running when this command runs in
1407
+ the same time zone and locale as the launcher did. `resume` is refused while a stop-and-archive is partway. A lifecycle command refused by the lock names
1224
1408
  the operation's own `status` command, which lists what to do next.
1225
1409
  - `ahub recovery dispose <operation-id> --fresh-session <peer> --reason <text>`
1226
1410
  applies to a Codex or Claude peer whose restoration failed (Pi is refused: a
@@ -1234,12 +1418,22 @@ next actions").
1234
1418
  <id>` instead of offering it. A hub that never answers (its pid is alive,
1235
1419
  nothing replies): take the `pid` from that project's
1236
1420
  `.agenthub/state/status.json`, check with `ps -p <pid> -o command=` that it
1237
- is that project's hub daemon, end it by hand, then run `status` again. If
1238
- that pid now belongs to another process (reused after a reboot), move the
1239
- stale `status.json` aside instead, then run `status` again; likewise when
1240
- `next` says `wait: runner <pid>` and that pid is not an `ahub recovery-run`
1241
- process, move the operation's `~/.agenthub/recovery/<id>.json.runner.db`
1242
- aside, then run `status` again. A
1421
+ is that project's hub daemon, end it by hand, then run `status` again. From
1422
+ 0.12.21 a hub's manifest and registry claim and a runner's record carry the
1423
+ owner's process signature, so a pid that a reboot handed to another process
1424
+ reads as gone by itself. Records written by 0.12.20 or older carry none and
1425
+ are judged by their pid alone: if that pid now belongs to another process,
1426
+ move the stale `status.json` and `hub.pid` aside together (with only one of
1427
+ them gone the other still reads as an owner), then run `status` again;
1428
+ likewise when `next` says `wait: runner <pid>` and that pid is not an `ahub
1429
+ recovery-run` process, move the operation's
1430
+ `~/.agenthub/recovery/<id>.json.runner.db` aside, then run `status` again.
1431
+ An unsigned registry claim of such a hub, whose pid now belongs to another
1432
+ process, still reads as starting, and no command clears it: after `ps -p
1433
+ <pid> -o command=` shows that the process is not an `ahub ... daemon` of that
1434
+ project, clear it with `sqlite3 ~/.agenthub/registry.db "UPDATE projects SET
1435
+ instance_id = NULL, pid = NULL, claimed_at = NULL WHERE id = '<project id>'
1436
+ AND pid = <pid>"` (`ahub projects --json` shows the id). A
1243
1437
  project whose directory is
1244
1438
  gone is only recorded (`ahub doctor --orphans` lists a hub left running
1245
1439
  there). It records its decision before it acts. It releases a source hold of
@@ -1249,7 +1443,13 @@ next actions").
1249
1443
  `restart.json` aside as `restart.abandoned.<hash>.json` and leaves any other
1250
1444
  hub running; only then is the operation recorded `cancelled` and the lock
1251
1445
  released. If it fails partway it keeps the lock, records what it did, and
1252
- `resume` refuses until you run it again. Queued messages are not delivered
1446
+ `resume` refuses until you run it again. Once a stop-and-archive is recorded,
1447
+ only the command `next` names continues it. On an operation whose
1448
+ coordinator predates these commands it also marks the receipt `schema: 2`,
1449
+ which every older release refuses before it acts: an older global `ahub
1450
+ recovery resume` still prints "scheduled", but its runner exits without
1451
+ touching the operation, and an older `ahub recovery abort` refuses with
1452
+ "unsupported operation receipt". Queued messages are not delivered
1253
1453
  from the archived file; a 0.7.0 or later hub reloads the queues it kept in
1254
1454
  `hub.db` when it starts again. Terminals it closed stay closed: start those
1255
1455
  sessions again by hand. Replacement terminals it opened stay open but have
@@ -1508,8 +1708,8 @@ The hooks return no facts in advisory mode. A native Stop establishes an idle
1508
1708
  boundary; a subsequent PreToolUse marks activity. A delivery acknowledgement or
1509
1709
  task approval does not establish native idle. Restart the daemon and relaunch
1510
1710
  Claude after enabling the sweep so its session receives the hooks. A caller's
1511
- `--settings` still wins: the launcher warns that native idle observation is off,
1512
- and the sweep cannot verify that Claude session between turns.
1711
+ `--settings` keeps them: the launcher adds the hub's hooks to the caller's and
1712
+ warns only that the status line tee is off.
1513
1713
 
1514
1714
  `auto_reassign: true` explicitly allows an available alternative owner selected
1515
1715
  under the existing routing, role and PII constraints to receive the task at step
@@ -1634,3 +1834,28 @@ Quota pause and task handoff are separate; a context checkpoint neither pauses
1634
1834
  nor hands work over. Continue normally or deliberately restart into a fresh
1635
1835
  session with your chosen checkpoint as preface. No automatic restart or native
1636
1836
  compaction override is performed.
1837
+
1838
+ ## Pi tool refusals, exit causes and idle automatic restart
1839
+
1840
+ Pi hub-task/conductor refusals proven before any task or board effect return
1841
+ their original `error:` text and are recorded as completed tool receipts.
1842
+ PII screening and its telemetry record may precede the refusal. An uncertain
1843
+ post-effect or storage failure remains pending: inspect it before repeating an
1844
+ action. Old pending receipts carry no proof of a past refusal and stay pending
1845
+ across startup. Conductor assignment and escalation entry checks explicitly opt
1846
+ into refusal typing; internal escalation after a saved review remains generic.
1847
+
1848
+ Pi exit records show code or signal, turn/tool activity, whether teardown was
1849
+ expected, startup status, and the last tool's name without arguments. An
1850
+ unobserved native-terminal exit records unknown OS status. The console notice
1851
+ gives the next action, usually `ahub pi`, or says why automatic start is held.
1852
+ A requested replacement says the new owner is starting; internal failure
1853
+ teardown directs session inspection rather than claiming a person requested it.
1854
+
1855
+ With `pi.auto_start`, an unexpectedly idle headless Pi is resumed on its
1856
+ verified persisted session once. The 60-second retry window rearms when the
1857
+ attempt settles; a second exit inside that window stays offline. Missing or
1858
+ invalid session history has no fresh fallback; inspect it before running
1859
+ `ahub pi`. Active turns/tools, failed startup, a superseded owner, native
1860
+ terminal exits, requested shutdown and recovery operations suppress this
1861
+ restart. Crash recovery's #66 recorded-session/fresh-start choices are unchanged.
@@ -17,7 +17,7 @@ ahub setup # installs the Claude Code channel plu
17
17
  Or use the matching GitHub release:
18
18
 
19
19
  ```bash
20
- bun add -g github:STAIxBWLB/agent-hub#v0.12.20
20
+ bun add -g github:STAIxBWLB/agent-hub#v0.12.21
21
21
  ahub setup
22
22
  ```
23
23
 
@@ -50,13 +50,14 @@ ahub say @kimi "run the tests and report" # one peer
50
50
 
51
51
  ## The local worker
52
52
 
53
- `local` needs an OpenAI-compatible gateway. Put yours in `.agenthub/config.json`:
53
+ `local` needs an OpenAI-compatible gateway. Put yours in `.agenthub/config.local.json`
54
+ (machine-local, kept out of git by `ahub init`):
54
55
 
55
56
  ```json
56
57
  { "omniroute": { "urls": ["http://your-gateway:20128/v1"], "api_key_file": "/path/to/key" } }
57
58
  ```
58
59
 
59
- or set `OMNIROUTE_API_KEY`. Name the model in `.agenthub/routing.toml` (`[local] fixed_model`). Then `ahub local`. It works only inside the project, asks before it writes or runs anything (`ahub permit <id> allow`), and everything it executes is sandboxed.
60
+ or set `OMNIROUTE_API_KEY`. Name the model in `.agenthub/routing.toml` (`[local] fixed_model`). Then `ahub local`. It works only inside the project, asks before it writes or runs anything (`ahub permit <id> allow`) unless you chose a permission mode for it (`ahub permission`), and everything it executes is sandboxed.
60
61
 
61
62
  ## Commands of a working day
62
63
 
@@ -74,11 +75,12 @@ More: `ahub help`. How it is built and why: [`docs/specs/2026-09-19-agent-hub-de
74
75
  ## Browser dashboard
75
76
 
76
77
  After `ahub up`, run `ahub ui` to open the local dashboard. It refreshes the
77
- conversation stream, peer queues, task board, budget windows and approvals every
78
- second. No build step, web server command or browser extension is needed.
78
+ conversation stream, peer queues, task board, budget windows, approvals and
79
+ benchmark runs every second. No build step, web server command or browser
80
+ extension is needed.
79
81
 
80
82
  Use the page to pause/resume a peer, send a console message, propose/assign tasks,
81
- and allow/deny agent permission requests. Local-worker requests show no tool
83
+ and allow/deny agent permission requests. Local-worker and Pi requests show no tool
82
84
  contents: inspect them with `ahub tail`, then use `ahub permit <id> <option>` to
83
85
  allow. They can be denied on the page. PII task details remain available only via
84
86
  `ahub task show <id>` in the terminal.
@@ -88,10 +90,10 @@ within 60 seconds. A session expires after one hour; run `ahub ui` again. Restar
88
90
  the daemon invalidates all links and sessions. The UI listener is started only on
89
91
  request, binds loopback and stops with the daemon. Remote access is not supported.
90
92
 
91
- The stream retains the latest 200 events since the dashboard listener was started;
92
- it does not load historical logs. An unopened dashboard has no listener or event
93
- buffer. A paused budget window cannot be overridden on the page; the CLI's
94
- `ahub budget resume <peer>` remains the explicit override.
93
+ The stream retains the latest 200 events since the daemon started; it does not
94
+ load historical logs. An unopened dashboard has no listener. A paused budget
95
+ window cannot be overridden on the page; the CLI's `ahub budget resume <peer>`
96
+ remains the explicit override.
95
97
 
96
98
 
97
99
  ## Concurrent project development (0.4.0)