@staix/agent-hub 0.20.0 → 0.22.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 (183) hide show
  1. package/CHANGELOG.md +66 -3
  2. package/README.md +2 -2
  3. package/docs/agent-notes/README.md +11 -0
  4. package/docs/agent-notes/adapters.md +7 -3
  5. package/docs/agent-notes/benchmarks.md +27 -0
  6. package/docs/agent-notes/budget.md +2 -1
  7. package/docs/agent-notes/bus.md +3 -3
  8. package/docs/agent-notes/daemon.md +17 -8
  9. package/docs/agent-notes/local-worker.md +4 -0
  10. package/docs/agent-notes/models.md +2 -0
  11. package/docs/agent-notes/tasks.md +17 -9
  12. package/docs/agent-notes/tests.md +9 -2
  13. package/docs/bench.md +1 -1
  14. package/docs/data-policy.md +2 -0
  15. package/docs/dogfood.md +39 -4
  16. package/docs/events.md +2 -1
  17. package/docs/memory.md +9 -3
  18. package/docs/operations.md +610 -62
  19. package/docs/profiles.md +13 -1
  20. package/docs/quickstart.md +18 -9
  21. package/docs/research.md +7 -4
  22. package/docs/sdlc.md +54 -10
  23. package/docs/security.md +396 -34
  24. package/docs/smoke.md +133 -0
  25. package/docs/specs/2026-09-19-agent-hub-design.md +1101 -22
  26. package/docs/verification/2026-10-27-0.19.0.md +5 -5
  27. package/docs/verification/2026-10-28-claude-2.1.311-2.1.312-bash-permission.json +635 -0
  28. package/docs/verification/2026-10-28-claude-2.1.311-2.1.312-permission-transport.json +403 -0
  29. package/docs/verification/2026-10-28-claude-implementer-permission-triage.md +123 -0
  30. package/docs/verification/2026-10-28-jcode-0.99.1-usage.md +41 -0
  31. package/docs/verification/2026-10-28-realpath-name-audit.md +222 -0
  32. package/docs/verification/2026-10-28-routing-f0bb8ad0.md +1000 -0
  33. package/docs/verification/2026-10-29-0.21.0.md +61 -0
  34. package/docs/verification/2026-10-29-model-effort-capabilities.md +123 -0
  35. package/docs/verification/2026-10-29-modelrouting-b152ddb9.md +953 -0
  36. package/docs/verification/2026-10-30-0.22.0.md +49 -0
  37. package/docs/verification/2026-10-30-445-capability-probes.md +172 -0
  38. package/docs/verification/2026-10-30-bus-delivery-operational-settings.md +77 -0
  39. package/docs/verification/2026-10-30-jcode-ac4.md +24 -0
  40. package/docs/verification/2026-10-30-jcode-ac6.md +52 -0
  41. package/docs/verification/2026-10-30-recovery-wait-settings.md +141 -0
  42. package/docs/verification/claude-implementer-permission-triage.ts +160 -0
  43. package/docs/verification/claude-permission-transport-native.py +71 -14
  44. package/docs/verification/profiles/YYYY-MM-DD-research.md +1 -1
  45. package/docs/verified.json +15 -15
  46. package/package.json +2 -2
  47. package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
  48. package/plugins/agent-hub/server.js +1016 -280
  49. package/src/adapters/acp.ts +79 -3
  50. package/src/adapters/channel-peer.ts +4 -0
  51. package/src/adapters/claude-channel.ts +13 -6
  52. package/src/adapters/claude-implementer.ts +60 -7
  53. package/src/adapters/codex-appserver.ts +77 -4
  54. package/src/adapters/conformance/fakes/jcode-agent.ts +6 -2
  55. package/src/adapters/conformance/kit.ts +3 -9
  56. package/src/adapters/definitions/builtin-metadata.ts +3 -114
  57. package/src/adapters/definitions/native-config-defaults.ts +1 -1
  58. package/src/adapters/definitions/native-config-policy.ts +3 -2
  59. package/src/adapters/definitions/native-context.ts +12 -0
  60. package/src/adapters/external-metadata.ts +48 -0
  61. package/src/adapters/external-modules.ts +63 -3
  62. package/src/adapters/jcode.ts +6 -2
  63. package/src/adapters/local-worker.ts +40 -25
  64. package/src/adapters/native-proof-ports.ts +10 -0
  65. package/src/adapters/registry-types.ts +4 -0
  66. package/src/broker/protocol.ts +203 -0
  67. package/src/broker/server.ts +314 -0
  68. package/src/broker/store.ts +548 -0
  69. package/src/cli/bench.ts +3 -2
  70. package/src/cli/broker.ts +58 -0
  71. package/src/cli/conductor-recovery.ts +142 -0
  72. package/src/cli/configured-acp-entry.ts +26 -6
  73. package/src/cli/console-state.ts +59 -17
  74. package/src/cli/console.ts +17 -2
  75. package/src/cli/facts-hook.ts +3 -1
  76. package/src/cli/help.ts +29 -16
  77. package/src/cli/identity.ts +25 -10
  78. package/src/cli/init.ts +34 -7
  79. package/src/cli/intake-output.ts +33 -0
  80. package/src/cli/learn-output.ts +38 -0
  81. package/src/cli/main.ts +334 -73
  82. package/src/cli/output.ts +41 -1
  83. package/src/cli/recovery-package.ts +18 -1
  84. package/src/cli/recovery-waits.ts +26 -0
  85. package/src/cli/role-holders.ts +35 -0
  86. package/src/cli/room.ts +304 -0
  87. package/src/cli/say-input.ts +16 -0
  88. package/src/cli/schedule-output.ts +49 -0
  89. package/src/cli/sdlc-output.ts +44 -6
  90. package/src/cli/smoke-pty-bridge.ts +82 -0
  91. package/src/cli/smoke-pty.ts +153 -0
  92. package/src/cli/smoke-run.ts +139 -0
  93. package/src/cli/smoke-target.ts +130 -0
  94. package/src/cli/source-output.ts +29 -0
  95. package/src/cli/task-output.ts +3 -2
  96. package/src/cli/upgrade-runtime.ts +112 -28
  97. package/src/cli/upgrade.ts +68 -8
  98. package/src/hub/ask.ts +76 -12
  99. package/src/hub/board.ts +132 -22
  100. package/src/hub/bus.ts +416 -65
  101. package/src/hub/child-process.ts +2 -0
  102. package/src/hub/ci-watch.ts +42 -20
  103. package/src/hub/claude-failure-stage.ts +85 -0
  104. package/src/hub/claude-shell-wrapper.ts +11 -5
  105. package/src/hub/conductor.ts +17 -4
  106. package/src/hub/config-apply.ts +34 -6
  107. package/src/hub/config-reload.ts +7 -5
  108. package/src/hub/config-trust.ts +7 -1
  109. package/src/hub/context-window.ts +33 -3
  110. package/src/hub/control-client.ts +14 -2
  111. package/src/hub/corpus-config.ts +34 -0
  112. package/src/hub/corpus-evidence.ts +184 -0
  113. package/src/hub/corpus.ts +373 -0
  114. package/src/hub/cost-governance.ts +26 -0
  115. package/src/hub/daemon.ts +1209 -183
  116. package/src/hub/deliverable-files.ts +8 -2
  117. package/src/hub/delivery-journal.ts +200 -73
  118. package/src/hub/dogfood-profile.ts +32 -5
  119. package/src/hub/dogfood-read.ts +6 -2
  120. package/src/hub/envelope.ts +11 -0
  121. package/src/hub/eval-gate.ts +131 -0
  122. package/src/hub/events.ts +1 -1
  123. package/src/hub/external-instance.ts +69 -0
  124. package/src/hub/facts.ts +15 -5
  125. package/src/hub/folder-trigger.ts +223 -0
  126. package/src/hub/hub-tools.ts +2 -1
  127. package/src/hub/inference.ts +20 -5
  128. package/src/hub/instance-config.ts +69 -3
  129. package/src/hub/instance-execution.ts +6 -2
  130. package/src/hub/instances.ts +32 -4
  131. package/src/hub/intake.ts +125 -0
  132. package/src/hub/learning.ts +117 -0
  133. package/src/hub/lifecycle.ts +7 -3
  134. package/src/hub/limits.ts +12 -5
  135. package/src/hub/native-permission-request.ts +77 -0
  136. package/src/hub/operational-authority.ts +63 -5
  137. package/src/hub/operational-settings.ts +154 -0
  138. package/src/hub/operational-view.ts +1 -1
  139. package/src/hub/packs.ts +132 -0
  140. package/src/hub/path-policy.ts +7 -0
  141. package/src/hub/peer-doing.ts +130 -0
  142. package/src/hub/peers.ts +12 -0
  143. package/src/hub/queue-backpressure.ts +58 -0
  144. package/src/hub/read-tools.ts +2 -0
  145. package/src/hub/recovery-authority.ts +151 -0
  146. package/src/hub/recovery-policy.ts +220 -0
  147. package/src/hub/research.ts +24 -14
  148. package/src/hub/restart.ts +7 -0
  149. package/src/hub/routing.ts +54 -11
  150. package/src/hub/schedules.ts +266 -0
  151. package/src/hub/sdlc-merge.ts +38 -14
  152. package/src/hub/sdlc-metrics.ts +32 -10
  153. package/src/hub/sdlc-queue.ts +7 -1
  154. package/src/hub/sdlc-release.ts +1 -1
  155. package/src/hub/sdlc.ts +37 -7
  156. package/src/hub/security-baseline.ts +38 -0
  157. package/src/hub/settings.ts +27 -2
  158. package/src/hub/shipped-packs.ts +8 -0
  159. package/src/hub/snapshots.ts +72 -6
  160. package/src/hub/source-read.ts +160 -0
  161. package/src/hub/sources.ts +311 -0
  162. package/src/hub/tasks.ts +987 -110
  163. package/src/hub/workflow.ts +1 -1
  164. package/src/local/doc-read.ts +69 -0
  165. package/src/local/proxy.ts +4 -13
  166. package/src/local/tools.ts +37 -3
  167. package/src/local/web-fetch-config.ts +43 -0
  168. package/src/local/web-fetch.ts +183 -0
  169. package/src/memory/brief.ts +85 -13
  170. package/src/memory/capture.ts +1 -1
  171. package/src/rooms/application.ts +114 -0
  172. package/src/rooms/boundary.ts +103 -0
  173. package/src/rooms/commands.ts +294 -0
  174. package/src/rooms/crypto.ts +371 -0
  175. package/src/rooms/link.ts +584 -0
  176. package/src/rooms/policy.ts +82 -0
  177. package/src/rooms/remote-peer.ts +80 -0
  178. package/src/rooms/service.ts +426 -0
  179. package/src/rooms/status.ts +146 -0
  180. package/src/rooms/store.ts +1210 -0
  181. package/src/rooms/wire.ts +465 -0
  182. package/src/ui/index.html +91 -4
  183. package/templates/packs/example.pack.json +13 -0
@@ -1,6 +1,6 @@
1
1
  # Operations guide
2
2
 
3
- This guide describes ahub 0.20.0 and control protocol 16. Live verification
3
+ This guide describes ahub 0.21.0 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
  ## Agent-operated live verification
@@ -67,7 +67,7 @@ smoke ledger; the presence of this option proves no outstanding #273 live leg.
67
67
  `status --all` uses the projects table. `queue show`, `models status` and `budget execution status` show labelled fields; add `--json` for the raw fetched document.
68
68
  `report --by task` keeps the attribution and coverage sentences with section headings and aligned counters.
69
69
  Use `--json` for scripts; it returns the fetched data without color, and `status --json` keeps its existing document.
70
- `sdlc status`, `sdlc explain`, `sdlc queue`, `sdlc release`, `sdlc metrics`, `sdlc costs` and `sdlc authorize-release` print readable output by default (#814: aligned columns, wrapped text, readable times, `--color=auto|always|never`). A script that parsed their JSON must pass `--json`, which prints exactly the document they printed before. `sdlc export` is a data format and stays JSON, as do `export`, `research export` and `bench export`.
70
+ `sdlc status`, `sdlc explain`, `sdlc queue`, `sdlc release`, `sdlc metrics`, `sdlc costs`, `sdlc merge` and `sdlc authorize-release` print readable output by default (#814, #899: aligned columns, wrapped text, readable times, `--color=auto|always|never`). A script that parsed their JSON must pass `--json`, which prints exactly the document they printed before. `sdlc export` is a data format and stays JSON, as do `export`, `research export` and `bench export`.
71
71
  `restart` and `upgrade` off an interactive terminal (a pipe, `--dry-run`), `recovery status` and the results of `recovery reattach`, `resume`, `abort` and `dispose`, `models setup` and `start`, `queue resolve`, and `budget execution configure` and `disable` print readable text by default (#814); a script that parsed their JSON must pass `--json`, which prints exactly the document they printed before, including `restart --json --yes`. A readable plan lists each project, each peer with how it runs and what happens to it, every blocker, every reconnect-only session and every fresh start, and says nothing was changed; `--json` keeps those as stderr lines. A pipe wraps at `COLUMNS` when it is set, else at 80 columns.
72
72
  `task show`, `task cancel`, `backlog groom`, `backlog return|plan|pin|split|undo|pull-ready` and the `release` commands (`list`, `show`, `new`, `plan`, `activate|freeze|ship|drop`) print a readable block by default (#814): the task or release as the hub holds it, a table for lists, every identifier whole, long text wrapped to the terminal. A script that parsed their JSON must pass `--json`, which prints the hub tool's text exactly as before. A private task arrives from the hub as its stub and is shown as that stub. Because these commands also take free text, `--json` and `--color=` count as flags only at the start or the end of the arguments: `--json` in the middle of a reason is part of the reason, and a value flag's value (`task cancel --reason --json`) is never a flag. A reason that ends in a bare `--json` loses it to the flag. `backlog add|pull`, `task escalate|assign|unassign` already answered one sentence and are unchanged.
73
73
  In `projects --json` and `status --all --json`, an unavailable hub that explicitly refuses an inspection for SQLite contention also carries `failure: { "code": "database_busy" }`; it is additive metadata alongside the neutral `error` sentence.
@@ -219,20 +219,21 @@ the stream; only the header is tab-flattened before tokenizing. Body lines retai
219
219
  the previous rendering and stay plain, as does command output; colour never changes
220
220
  what the panel's text says.
221
221
 
222
- Peers, Approvals, Tasks, Queue, Settings and Usage are tables with a header row. A column is as
222
+ Peers, Approvals, Tasks, Queue, Settings, Usage and Rooms are tables with a header row. A column is as
223
223
  wide as its widest cell, up to a third of the terminal; the last column takes
224
224
  the rest and is cut with `...`. In Tasks, TITLE takes the rest and the STAGE
225
225
  meter keeps a fixed column at the right edge. In Usage, LAST RESET is the column that gives way on a narrow terminal.
226
226
 
227
227
  | Panel | Columns |
228
228
  |---|---|
229
- | Peers | PEER STATE MODE LINK Q ! WIP REVIEW PAUSE QUOTA MODEL |
229
+ | Peers | PEER STATE MODE LINK Q ! WIP REVIEW PAUSE QUOTA MODEL DOING |
230
230
  | Approvals | ID PEER LEFT TITLE |
231
231
  | Tasks | ID STATE OWNER REVIEWER CLASS AGE TITLE STAGE |
232
232
  | Tasks, backlog view (`b`) | ID PRI RESERVED DEPS CLASS AGE TITLE |
233
233
  | Queue | ID PEER STATE REV AGE |
234
234
  | Settings | GROUP KEY VALUE SOURCE APPLIES |
235
235
  | Usage | PEER TODAY 7D LIFETIME PAUSE QUOTA LAST RESET |
236
+ | Rooms | ROOM LINK MEMBERS REMOTE |
236
237
 
237
238
  In Tasks, `b` toggles open work and backlog. The backlog heading counts its rows;
238
239
  rows sort by rank, then id. PRI shows priority/rank, RESERVED the reserved peer,
@@ -263,6 +264,20 @@ including `0/2`; a peer without an entry reads `-`. At or above the limit, the
263
264
  cell uses the attention tone. At narrow widths WIP gives way before PEER, STATE
264
265
  and QUOTA, whether or not MODE is present.
265
266
 
267
+ DOING (#878) is what a peer is working on, from signals the hub already receives:
268
+ a Claude session's title (its transcript's latest `ai-title` row) and the
269
+ in-progress item of its `TodoWrite` list or the task `TaskUpdate` put in
270
+ progress; a Codex thread's name and its plan's in-progress step; an ACP agent's
271
+ plan entry in progress. The current item wins over the title, cut to 120
272
+ characters. A peer with no such source, or one that is offline, shows nothing,
273
+ and the column appears only when some peer has a value; it is the first column
274
+ to give way on a narrow terminal. The value is a `[private]` stub while the peer
275
+ owns or reviews a PII task or is in a private turn, when it was observed then,
276
+ or when the public metadata screen refuses it or fails. `ahub status`, the
277
+ dashboard's peer rows and the conductor's `hub_status` show the same value; it
278
+ lives in the daemon's memory only, never in `status.json`, a log, an event or
279
+ memory capture. Board WIP is unchanged.
280
+
266
281
  In Peers, select a row and press `w` to edit `tasks.wip_limits.<peer>`, prefilled
267
282
  with its current limit. The editor stays on Peers and uses the existing Settings
268
283
  value, daemon preview, nonempty reason and `y` confirmation flow; Esc cancels without writing. The
@@ -343,15 +358,16 @@ The local worker and headless ACP peers, including Jcode, receive tools only ins
343
358
  Every console resolution needs a reason. Queue `discard` removes queued originals or
344
359
  settles a needs_review receipt; it never cancels a task or native turn.
345
360
 
346
- `ahub task unassign <id> [--role owner|reviewer] [--revision N]` is the terminal
347
- equivalent. Owner removal is allowed in `proposed`, `in_progress` and
361
+ `ahub task unassign <id> [--role owner|reviewer] [--revision N]` is available to
362
+ the person or configured conductor. The board records the caller that performed
363
+ the unassign. Owner removal is allowed in `proposed`, `in_progress` and
348
364
  `changes_requested`, returns active work to `proposed`, and clears any owner
349
365
  reservation. It is refused in `in_review`, `approved` and `backlog`. Reviewer
350
366
  removal retains the state, including `in_review`, and refuses to remove the human
351
367
  reviewer of PII work. No replacement is routed and no native process or external
352
- effect is rolled back. A person-unassigned task stays out of automatic owner
368
+ effect is rolled back. An owner-unassigned task stays out of automatic owner
353
369
  offers across dependency release, planner-pull processing, release timers and
354
- restarts until an explicit assignment or reservation. The later unassign also
370
+ restarts until a later authorized explicit assignment or reservation. The later unassign also
355
371
  fences planner undo, so undo and re-pull cannot restore the removed reservation.
356
372
  Former queued assignment or review requests are withdrawn without resolving
357
373
  uncertain deliveries, and the former agent loses current authority. A withdrawal
@@ -370,8 +386,8 @@ the time); `dispatching` and `accepted` deliveries stay refused, and `retry` kee
370
386
  `stale revision`, `unknown delivery`, `already resolved`, `queued delivery is already scheduled`, `recipient queue is full`, `delivery journal unavailable`), never one
371
387
  generic sentence (#713).
372
388
 
373
- `ahub queue discard --peer <id> --reason <text> [--state queued|needs_review] [--yes] [--json]` clears one peer's backlog and is for the person only (an agent identity is
374
- refused). Without `--yes` it only says how many deliveries it would discard; with `--yes` it discards each queued (default) or `needs_review` delivery of that peer through the
389
+ `ahub queue discard --peer <id> --reason <text> [--state queued|needs_review] [--yes] [--json]` clears one peer's backlog and is for the person or the configured conductor (any other agent identity is
390
+ refused, and a remote `peer@label` queue is the person's only). Without `--yes` it only says how many deliveries it would discard; with `--yes` it discards each queued (default) or `needs_review` delivery of that peer through the
375
391
  same resolution as `queue resolve --action discard`, each recorded with the reason, and prints `discarded N of M` with the causes of any it could not discard. Only the
376
392
  named peer's queue is read, so other peers are untouched; it is refused while recovery or shutdown holds queue mutations. In the console Queue panel, `D` on a row opens
377
393
  the same command for that row's peer, asks for a reason, then a `y` that names the count. No task is cancelled and no native turn is stopped.
@@ -551,16 +567,19 @@ and so is every help entry the gate has no class for yet;
551
567
  | `ahub release show <name>` | Allowed | public release metadata; private scope and internal history remain withheld |
552
568
  | `ahub projects [--json] [--color=...]` | Allowed | lists registered projects; `projects remove` stays Console |
553
569
  | `ahub models status [--json] [--color=...]` | Allowed | reads local model state; `setup\|start\|stop` stay Console |
554
- | `ahub recovery status [<op-id>] [--json]` | Allowed | reads a preflight operation; the bare interactive screen, `resume`, `abort` and `reattach` stay Console |
555
- | `ahub upgrade ... --dry-run` (no `--yes`) | Allowed | reviews the upgrade plan and applies nothing; `--yes` stays Console |
556
- | `ahub restart --dry-run [--json]` (no `--yes`) | Allowed | reviews the restart plan and applies nothing; `--yes` stays Console |
570
+ | `ahub recovery status [<op-id>] [--json]` | Allowed | reads a preflight operation; the bare interactive screen and `reattach` stay Console |
571
+ | `ahub upgrade ... --dry-run` (no `--yes`) | Allowed | reviews the upgrade plan and applies nothing; apply is a separate person or conductor operation |
572
+ | `ahub restart --dry-run [--json]` (no `--yes`) | Allowed | reviews the restart plan and applies nothing |
573
+ | `ahub task cancel\|label\|role <id> ...` | Allowed | the daemon decides per task (#933): `label` and `role` only on a task the caller owns, `role` not while it is in review, and `cancel` by the `hub_task_cancel` rule (the planner's own unaccepted backlog or proposed work) |
574
+ | `ahub deliverables [--task <id>] [--approved] [--check] [--json]` | Allowed | only the rows `hub_task_show` would show the caller, through the same public screening (#933) |
575
+ | `ahub room list [--json]`, `ahub room status [<name>] [--json]` | Allowed | room metadata only; every other `room` form, the inbox included, stays Console |
557
576
 
558
577
  `backlog list`, `release list` and `sdlc costs|metrics` retain their existing
559
578
  planner/person policy. Their CLI forms stay Console for ordinary agents; a
560
579
  configured planner can use the existing planner tools without borrowing console
561
580
  authority. All newly admitted reads use the ordinary tools connection and public
562
581
  views, never console identity. The person's full readback stays unchanged.
563
- Conductor operations are listed in [the operational command table](#conductor-operations-812-t2).
582
+ Conductor operations are listed in [the operational command table](#conductor-operations-812-t2-933-900).
564
583
 
565
584
  ## Conducting a team from Claude Code or Codex
566
585
 
@@ -596,8 +615,11 @@ cause an otherwise unassigned owner to claim duplicate work.
596
615
  The person answers approvals in the console, resolves `needs_review` deliveries
597
616
  with `ahub queue resolve`, stops a headless peer with `ahub stop`, and handles
598
617
  budget overrides and hub lifecycle.
599
- Running these commands from an agent shell is refused; the CLI retains the
600
- agent's identity even when invoked through a shell tool.
618
+ The configured conductor may itself run `queue resolve`, `stop <peer>` and the
619
+ other forms in [the operational command table](#conductor-operations-812-t2-933-900);
620
+ approvals and budget overrides stay the person's. From any other agent shell these
621
+ commands are refused; the CLI retains the agent's identity even when invoked
622
+ through a shell tool.
601
623
 
602
624
  `conductor.feed` is `own` by default, `all` for all tasks, or `off`. Ordinary
603
625
  milestones share the existing digest window and collapse repeated queued
@@ -908,7 +930,7 @@ directly under a `.git`, `commondir` below it, and `.agenthub` anywhere in the
908
930
  project, plus `config.worktree` beside each such `config`. A submodule's git
909
931
  directory gets the same `config` and `hooks` refusal under every `modules/`
910
932
  tree the project can reach: the common dir's, the worktree's own and every
911
- linked worktree's (#281, #518). Nothing else may be written under such a tree
933
+ linked worktree's (#281, task 518). Nothing else may be written under such a tree
912
934
  outside a discovered submodule git dir (#865). A command can therefore not
913
935
  create, rename or remove a `.git`: `git init`, `git clone` and
914
936
  `git worktree add` do not run inside the sandbox, and neither do
@@ -1128,7 +1150,7 @@ listed and the rest are counted. The block is advice: deleted definitions are no
1128
1150
 
1129
1151
  The dashboard's task sheet has the same block as a "Code" group (#684 phase 2c): the changed symbols as buttons marked `matching` or `unplanned` against the plan, the plan names nothing changed under as `untouched`, and the same advisory line (deleted symbols are not reported, an incomplete observation may hide a changed one). The hub classifies them (`codeGroup` on each task of the UI snapshot, from the same public observation the console reads), so the page never matches names itself. A symbol opens the Memory tab with its canonical node (`symbol:<name>@<path>`) as the graph start; the list is capped at 40 with the count of what is left out, and a summary line counts matching, unplanned and untouched before the cap. A plan name the hub withholds (screened out or malformed) is counted, and the line then says a symbol marked unplanned may be in the plan. Without code facts the group reads `unknown (no code facts)`, and a private task has no group at all, so the sheet shows nothing for it.
1130
1152
 
1131
- Task detail also shows an `overlaps` block (#379), using the hub's read-only
1153
+ Task detail also shows an `overlaps` block (task 379), using the hub's read-only
1132
1154
  `symbolOverlaps` projection of its existing open-task/other-owner overlap selection.
1133
1155
  Each row names the other task ID and screened shared paths. A row says "same file,
1134
1156
  different symbols" only when the same current screened predicate used by completion
@@ -1153,10 +1175,10 @@ a reviewable path unaccounted is refused, and advise notes the gap in the verdic
1153
1175
  (#520). The hub
1154
1176
  records how each review turned out, per implementer, reviewer and class:
1155
1177
  approved; caught (changes were requested and the owner's redo was approved);
1156
- contradicted (within a week, work on the same file or symbol failed its check
1178
+ contradicted (within `tasks.contradiction_window_days`, default 7, work on the same file or symbol failed its check
1157
1179
  or review); escalated (after the reviewer asked for changes). A record counts
1158
1180
  tasks, not verdicts. `ahub task show <id>` lists a task's outcomes and `ahub
1159
- route explain` shows each reviewer's record with the implementer. `ahub route explain <id>` also prints the planner walk (`planner candidate X: ...` lines, then `planner: X (rotation | named | kept)`): the rotation among the rostered planner holders by task id (#525-3), shown for information, since nothing records or enforces a per-task planner yet. A peer that accepted or finished a task never reviews it; when every eligible reviewer did, the person reviews and the trace says so. With
1181
+ route explain` shows each reviewer's record with the implementer. `ahub route explain <id>` also prints the planner walk (`planner candidate X: ...` lines, then `planner: X (rotation | named | kept)`): the same planner walk used when a new or pulled task records its planner (#525-2, #525-3). The task detail, dashboard and console show the recorded planner and selection reason; only that effective planner or the person may change its planning. A peer that accepted or finished a task never reviews it; when every eligible reviewer did, the person reviews and the trace says so. With
1160
1182
  `"review": { "adaptive": true }` in `.agenthub/config.json`, reviewers with at
1161
1183
  least `min_reviews` (5) reviews of that implementer in the class are ordered by
1162
1184
  how their reviews held up, ahead of quota but after idle before busy; off by
@@ -1244,6 +1266,16 @@ ahub say @claude "[STATUS] the test run is complete"
1244
1266
  ahub say @kimi "[FYI] the result is recorded"
1245
1267
  ```
1246
1268
 
1269
+ The `hub_send` tool declares `to` as an array of peer ids (#963). The control `send`
1270
+ boundary also accepts one nonempty string as a single recipient; it never
1271
+ turns that string into a broadcast. Without `reply_to`, omitting `to` or sending
1272
+ an empty array keeps the broadcast meaning. A reply without recipients inherits
1273
+ the sender it answers. Other recipient shapes, including `null`, empty strings
1274
+ and arrays containing a non-string or empty item, are refused before publication.
1275
+ The tool reports `not sent: to must be an array of peer ids` (the control reply's
1276
+ error is `to must be an array of peer ids`). Valid arrays still deduplicate
1277
+ recipients and pass through the existing unknown-peer, roster and privacy gates.
1278
+
1247
1279
  What agents send is limited per sender (`limits` in `.agenthub/config.json`; a
1248
1280
  project config gets the values below unless it sets others, `0` turns one off):
1249
1281
 
@@ -1322,7 +1354,7 @@ rescans the whole file on each call.
1322
1354
  for the history it already spent. A task with no link, and a record with no task, are `unattributed`.
1323
1355
 
1324
1356
  Every number names its source: `native` (the provider's own counter), `hub count` (the hub's own `tokens` increments, which cover peers that
1325
- report no usage record, such as Jcode), or `unknown`. Kimi's rows are `native`: the hub reads Kimi's own per-request counters from its session wire log and records them as `kimi_wire` usage records (#285 phase 2). Unknown is never shown as 0. `TOTAL` includes cache reads and `WITHOUT CACHE`
1357
+ report no usage record), or `unknown`. Jcode's rows are `native` from 0.99.1 on: the hub records the per-turn buckets of its ACP prompt result as `jcode_acp` usage records (#801). Kimi's rows are `native`: the hub reads Kimi's own per-request counters from its session wire log and records them as `kimi_wire` usage records (#285 phase 2). Unknown is never shown as 0. `TOTAL` includes cache reads and `WITHOUT CACHE`
1326
1358
  removes them; Claude's total is the sum of its four reported counters because its transcript carries none (`ahub report` still shows it as unknown), and a source whose input already
1327
1359
  contains the cached part (Codex, the gateway) gets no derived total. A lifetime total that mixes a native and a hub-counted peer is labelled
1328
1360
  `hub count`. When a peer is unknown the overall figure is `partial, at least`: a lower bound that leaves the named peers out, never a sum that counts them
@@ -1367,7 +1399,7 @@ ahub memory forget 1a2b3c4d # a person's; also promote, demote, sup
1367
1399
 
1368
1400
  `"memory": { "native": false }` in `.agenthub/config.json` turns it off for a project.
1369
1401
 
1370
- The exact-pinned `@staix/agent-mem` 0.11.0 resolves `AMEM_HOME`, then `$AGENTHUB_HOME/memory`, then `~/.agenthub/memory`. An absent `M/profile.json` selects shared v1; an explicit generation selects `M/generations/G` with the package's readonly legacy bridge. All daemon memory consumers and the hub memory/goal CLI use that selected profile. Invalid metadata or a missing selected generation makes native memory unavailable while the bus and board continue; there is no fallback to legacy writes. Inspect the daemon's memory binding/availability before interpreting missing memory as an empty store.
1402
+ The exact-pinned `@staix/agent-mem` 0.13.0 resolves `AMEM_HOME`, then `$AGENTHUB_HOME/memory`, then `~/.agenthub/memory`. An absent `M/profile.json` selects shared v1; an explicit generation selects `M/generations/G` with the package's readonly legacy bridge. All daemon memory consumers and the hub memory/goal CLI use that selected profile. Invalid metadata or a missing selected generation makes native memory unavailable while the bus and board continue; there is no fallback to legacy writes. Inspect the daemon's memory binding/availability before interpreting missing memory as an empty store.
1371
1403
 
1372
1404
  The daemon checks its selector at hub operation entry and before candidate/counter writes. It does not hot-switch handles or provide an atomic fence against selection changes during an in-flight core call. Quiesce consumers and close their handles before migration or selection changes; reopen through the supported session-preserving lifecycle afterward. Candidate/counter storage remains `M/candidates.db`, independent of `G`; since agent-mem 0.7.0 `amem review` also lists and accepts candidates on a selected generation. Every running hub using this home needs an authenticated receipt: a matching ready binding, or a generation-capable unbound receipt (`disabled` when native memory is off, `unavailable` when opening failed) proving it holds no memory handle. An unbound daemon and zero running hubs are quiesced for memory; a stale bound handle, unreadable registry or unknown runtime is not ready. Existing JSON memory/goal results remain unchanged; the additive binding receipt belongs to daemon status, profile-check and the decay object. This source integration does not authorize installing a new runtime, migrating real memory, restarting sessions or claiming real-machine sync/capture receipts. Migration/deactivation and their limits belong to [agent-mem](https://github.com/STAIxBWLB/agent-mem); see [the hub profile contract](memory.md#selected-memory-profile).
1373
1405
 
@@ -1392,7 +1424,7 @@ ahub goal # open ones
1392
1424
  ahub goal take 1a2b3c4d --force # take one over from another machine
1393
1425
  ```
1394
1426
 
1395
- Without a remote the periodic sync stays quiet; any other sync failure writes `hub memory sync failed: <reason>` to `hub.log` (#401).
1427
+ Without a remote the periodic sync stays quiet; any other sync failure writes `hub memory sync failed: <reason>` to `hub.log` (task 401).
1396
1428
 
1397
1429
  ## Benchmarks
1398
1430
 
@@ -1403,7 +1435,7 @@ task's commit, so never enable it in a project you work in. The project must be
1403
1435
  nothing tracked under `.agenthub/`, and the suite file lives outside its tree ([suite format and store](bench.md)).
1404
1436
 
1405
1437
  ```bash
1406
- ahub bench run ../suites/suite.json --arm baseline --repeat 5 # a person's: resets the tree, proposes the tasks
1438
+ ahub bench run ../suites/suite.json --arm baseline --repeat 5 # the person's or the conductor's: resets the tree, proposes the tasks
1407
1439
  ahub bench status # the run in progress
1408
1440
  ahub bench compare baseline ripwire-on # arms side by side, inconclusive below 5 attempts each
1409
1441
  ahub bench export --format csv
@@ -1444,6 +1476,13 @@ ahub undo <turn> --yes --context # Codex's latest turn: also drop it from Codex
1444
1476
  Nothing is restored then. It plans again right before restoring and stops if
1445
1477
  anything moved meanwhile. A turn that is already undone says so.
1446
1478
  - A file the turn created is deleted; a file it deleted comes back.
1479
+ - The hub does not observe inside a git submodule (#798). A submodule the turn
1480
+ left dirty is listed as `<path> (changed, contents unobserved)` in `ahub turns`
1481
+ (`unobserved` in `--json`), and `ahub undo` says its contents cannot be restored:
1482
+ undo them in the submodule itself. A task that declares a path inside a submodule
1483
+ gets a notice at propose and accept, never a refusal. Without `.gitmodules` no git
1484
+ call is added; with one, each turn end adds one bounded `git status` probe, and a
1485
+ probe that cannot answer records unknown, never none.
1447
1486
  - When HEAD moves during a turn, the hub reads the appended HEAD reflog (bounded to 64 moves and 128 KiB). The turn's own commits, amends, cherry-picks and rebase picks still count as its changes. Files brought in by a pull, merge, rebase start, branch checkout or fast-forward reset are left out, unless they are still dirty at the turn's end. An unreadable or ambiguous reflog, a rewinding reset, or a file touched by both kinds of move makes the turn's files unknown (#576).
1448
1487
  - `--context` asks Codex (`thread/revert`) to drop the turn, and every later one,
1449
1488
  from its thread's saved history before the files are restored; Codex receives
@@ -1668,8 +1707,8 @@ stays the default until an evaluation says otherwise (`docs/cooperbench.md`).
1668
1707
  other owner, while the overlapping task is not started; the routing record,
1669
1708
  and a cohort record formed as a task is assigned, are taken before the task is
1670
1709
  sent, so a busy routed peer is not available then) with no other open work (an overlapping task its owner
1671
- has started counts), and each has five measured tasks with no more than 30%
1672
- failures and comparable work times. The work stage of a task ends at its first
1710
+ has started counts), and each has `tasks.split_min_observations` (default five) measured tasks with no more
1711
+ than `tasks.split_max_failure_share` (default 30%) failures and comparable work times. The work stage of a task ends at its first
1673
1712
  `hub_task_done`, and the task itself is never one of its own observations.
1674
1713
  - `"experiments": {"stale_notices": "deliver"}` in `.agenthub/config.json`
1675
1714
  turns the stale-notice drop off, for a controlled comparison only (the #106
@@ -1814,7 +1853,8 @@ ahub settings set roster.kimi false --preview
1814
1853
  ahub settings set roster.kimi false --yes
1815
1854
  ```
1816
1855
 
1817
- These are person's commands and use the same settings preview, write and undo as
1856
+ These are the person's commands (`roster add|remove` also the configured conductor's,
1857
+ see [the operational command table](#conductor-operations-812-t2-933-900)) and use the same settings preview, write and undo as
1818
1858
  the dashboard's Roster settings. An ordinary dashboard session may remove a peer;
1819
1859
  adding requires `ahub ui --settings` or a person's terminal. Removal requires the
1820
1860
  peer id as confirmation. Without `--now`, a busy peer is refused. A conductor
@@ -1914,8 +1954,117 @@ A guarded file edit that the daemon keeps at its accepted value remains visible
1914
1954
  | Switches | `task_sweep.enabled`, `.auto_reassign`, `.interval_s`, `.unaccepted_min`, `.idle_min`, `.review_min`, `.ready_min`, `.ladder_min`, `.no_writes_turns` | `config.local.json` | live; `.no_writes_turns` sets the turn threshold independently; other keys reschedule the sweep timer |
1915
1955
  | Routing | `tasks.wip_limits.<peer>` | `config.local.json` | live |
1916
1956
  | Routing | `tasks.escalate_after` (1-10, default 3: consecutive `changes_requested` before a task moves to the next `escalate_to` peer) | `config.local.json` | live; the next verdict uses it |
1957
+ | Routing | `tasks.groom_fyi_window_min` (1-1440 minutes, default 10) | `config.local.json` | live; the next planner-notice flush uses it; planning-change task notices are unchanged |
1958
+ | Routing | `tasks.demote_at` (0.5-20 decayed recent failures, default 1.5) | `config.local.json` | live; the next demotion read (routing, `ahub route explain`) uses it |
1959
+ | Routing | `tasks.demotion_half_life_h` (1-168 hours, default 24) | `config.local.json` | live; the next demotion read uses it |
1960
+ | Routing | `tasks.contradiction_window_days` (1-90 days, default 7) | `config.local.json` | live; the next changes_requested verdict uses it |
1961
+ | Routing | `tasks.rescreen_max` (1-100 attempts, default 10) | `config.local.json` | live; the next release-timer re-screen uses it |
1962
+ | Routing | `tasks.split_min_observations` (3-100 measured tasks, default 5) | `config.local.json` | live; the next shadow split prediction uses it |
1963
+ | Routing | `tasks.split_max_failure_share` (0-1, default 0.3) | `config.local.json` | live; the next shadow split prediction uses it |
1917
1964
  | Switches | `tasks.auto_pull` (default off: pull the top explicitly reserved ready card when a planner slot frees) | `config.local.json` | live; the next freed planner slot uses it |
1965
+ | Routing | `tasks.queue_backpressure.max_queued` (0-10000), `.max_oldest_s` (0-604800); 0, the default, disables each ([backpressure](#queue-backpressure-on-automatic-task-offers-800)) | `config.local.json` | live |
1918
1966
  | Roles | `planner` (one or up to eight planner holders) | `config.local.json` | at once |
1967
+ | Routing | `bus.max_attempts` (1-100 attempts, default 3) | `config.local.json` | live; next safe-failure retry evaluation, retaining existing counts and quota pauses |
1968
+ | Routing | `bus.digest_max` (1-10 originals, default 10) | `config.local.json` | live; next push/attached-channel take, with poison-alone and remote-singleton rules |
1969
+ | Routing | `bus.pull_budget_bytes` (1-65536 bytes, default 65536) | `config.local.json` | live; next pull-only read, oldest first; the first oversized item still goes |
1970
+ | Routing | `bus.notes_kept` (1-10 notes, default 10) | `config.local.json` | live; next shared-note or failed-preface merge; existing prefaces remain unchanged on reload |
1971
+ | Routing | `bus.terminal_history_limit` (1-2048 rows, default 2048) | `config.local.json` | live; next normal terminal settlement; unresolved rows and retry ancestry stay protected |
1972
+ | Routing | `recovery.reconnect_wait_s` (600-3600 seconds, default 600) | `config.local.json` | next use; the next managed-terminal attachment wait of an upgrade recovery reads it once, at its start |
1973
+ | Routing | `recovery.unmanaged_reconnect_wait_s` (90-900 seconds, default 90) | `config.local.json` | next use; the next unmanaged Claude reconnect wait reads it once, at its start |
1974
+ | Routing | `recovery.control_timeout_s` (30-300 seconds, default 30) | `config.local.json` | next use; the next control request of the recovery coordinator to a hub reads it |
1975
+ | Routing | `recovery.shutdown_wait_s` (30-300 seconds, default 30) | `config.local.json` | next use; the next wait for a committed source to stop reads it once, at its start |
1976
+ | Routing | `recovery.terminal_idle_wait_s` (30-600 seconds, default 120) | `config.local.json` | next use; the next wait for a terminal to go idle before it is closed reads it |
1977
+
1978
+ ### Operational numeric settings (#489 H1)
1979
+
1980
+ Numeric operational conversions use the existing settings writer, loader and
1981
+ reload path. A fixed row in `src/hub/operational-settings.ts` supplies the key,
1982
+ unit, default, bounds, apply class and console/dashboard metadata. The closed
1983
+ `config-apply.ts` schema still names each leaf explicitly; there is no dynamic
1984
+ registration, extra configuration file, environment knob or per-call override.
1985
+ `configApply` and the UI row consume the same metadata. The default enters the
1986
+ existing `DEFAULT_CONFIG`; unset therefore keeps the previous value.
1987
+
1988
+ The worked H0 conversion is `tasks.groom_fyi_window_min`, the spacing between
1989
+ state-only planner board-update FYIs. Its range is 1-1440 minutes and its
1990
+ default is the existing 10 minutes. A settings-session or terminal change takes
1991
+ effect at the next notice flush, without restarting a peer or altering task
1992
+ ownership. It does not delay new planning/dependency task notices. Removing a
1993
+ local value inherits the shared value or default through the ordinary settings
1994
+ undo/write path. This new key grants no conductor authority.
1995
+
1996
+ For a later operational group, add its metadata row, exact schema leaf,
1997
+ default and runtime config read, then its settings-table entry and independent
1998
+ pin against the original constant. Test schema/UI bounds and apply-class parity,
1999
+ inherit/undo and the real runtime boundary. H0 safety floors and contract
2000
+ constants stay outside this table. Exposing a floor requires schema bounds that
2001
+ permit tightening only, tested for that floor; UI `floor` metadata alone does
2002
+ not enforce a safety ceiling. This H1 example changes no safety floor, protocol,
2003
+ wire format or storage contract. The remaining H0 conversions belong to later
2004
+ area PRs.
2005
+
2006
+ #### Tasks and routing conversions (#489 H2)
2007
+
2008
+ Six more operational numbers use the same fixed rows, all `live`, `risk: raises`, config store, with the
2009
+ default equal to the constant it replaces (each pinned by a literal in `test/operational-settings.test.ts`):
2010
+ `tasks.demote_at` (DEMOTE_AT), `tasks.demotion_half_life_h` (DEMOTION_HALF_LIFE_MS),
2011
+ `tasks.contradiction_window_days` (CONTRADICTION_WINDOW_MS), `tasks.rescreen_max` (RESCREEN_MAX),
2012
+ `tasks.split_min_observations` (SPLIT_MIN) and `tasks.split_max_failure_share` (SPLIT_FAILURE_SHARE). Each is
2013
+ read where its constant was used, so a change applies at the next demotion read, verdict, re-screen or
2014
+ prediction without a restart. The split thresholds only feed the shadow prediction, which never changes
2015
+ assignment. None grants conductor authority.
2016
+
2017
+ The H0 operational items of `tasks.ts`, `routing.ts` and `task-sweep.ts` that stay as they are (42 listed, 6 converted here, 1 in H1):
2018
+
2019
+ - Already settings, so nothing to add: `task_sweep.interval_s`, `unaccepted_min`, `idle_min`, `review_min`, `ready_min`
2020
+ (both uses), `ladder_min` and `no_writes_turns` (the H0 rows are the defaults of the existing keys), `tasks.escalate_after`
2021
+ (ESCALATE_AFTER), `tasks.wip_limits` (the default of 1) and `tasks.groom_fyi_window_min` (H1).
2022
+ - Already `routing.toml` keys, not config-store: `pi.dgx_max_context_tokens`, `pi.mlx_max_context_tokens`, the
2023
+ `pi.efficient_wait_ms` bound and `long_context_tokens`. H1 reaches the config store only, and
2024
+ `routing.local.toml` may not hold them (`checkOverlay`), so converting them would need a different mechanism.
2025
+ - Resource bounds, not tunables: the 256-entry plan caches (`observedPlans`, `planReads`), the 1024-entry conditional
2026
+ map, the 500-task and 256-item grooming limits (`GROOMING_TASK_LIMIT`, `GROOMING_ITEM_LIMIT`, the digest's 500
2027
+ slices), and the 8, 16 and 32 read counts and 16-id lists of the digest, cost report and release cut. Raising any of
2028
+ them raises memory or work per call, so each would need a tighten-only schema bound and a test per floor
2029
+ (acceptance criterion 3), which this group does not add.
2030
+ - Host deadlines for the `gh` reads (1_000, 4_000 and 20_000 ms in five places) and the 5 000 ms release-window
2031
+ freshness check. They bound a whole call, a refusal of a stale host window protects the release, and none is
2032
+ independently tunable without changing what a partial result means.
2033
+
2034
+ #### Bus and delivery conversions (#489 H3)
2035
+
2036
+ The five `bus.*` rows above use the H1 descriptor, schema, settings writer and live
2037
+ reload path. Unset defaults equal `MAX_ATTEMPTS`, `DIGEST_MAX`, `PULL_BUDGET_BYTES`,
2038
+ `NOTES_KEPT` and `TERMINAL_CAP`. Only `bus.max_attempts` may rise; the other four
2039
+ can only tighten today's ceilings. A reload does not trim/reorder queues or
2040
+ prefaces, reset attempts, re-arm batch deadlines, prune history, or change a pause
2041
+ or in-flight delivery. Terminal history is a target: protected retry parents and the current mutation receipt may
2042
+ keep the count above it. The settings add no conductor authority.
2043
+
2044
+ The [H3 H0 ledger](verification/2026-10-30-bus-delivery-operational-settings.md)
2045
+ lists all 59 unique inventory identities (5 converted and 54 excluded). The
2046
+ persisted `SEEN_CAP`/dedupe contract and withdrawn cancellation evidence stay
2047
+ fixed; recovery fence polling goes to H4, expired-limit-record cleanup remains
2048
+ maintenance, existing limits/watchdog/batching knobs remain in their current
2049
+ configuration path, and wire/storage/safety/test-seam values remain constants.
2050
+
2051
+ #### Recovery and upgrade waits (#489 H4)
2052
+
2053
+ The five `recovery.*` rows extend the existing `recovery` block (`auto_resume_after_crash` is unchanged) and use
2054
+ the H1 descriptor, schema and settings writer. They are read by the upgrade coordinator, a CLI process and not the
2055
+ daemon, from the project it is acting on, at the start of each wait: the shared `config.json` first and the
2056
+ machine-local `config.local.json` over it (ignored when git tracks it). The apply class is `next use`: a wait that is
2057
+ already running keeps the deadline the receipt records in `waits`, and a resume reads the file again. Unset defaults
2058
+ equal `RECONNECT_WAIT_MS`, `UNMANAGED_RECONNECT_WAIT_MS`, `RECOVERY_CONTROL_TIMEOUT_MS`, the 30 s shutdown wait and
2059
+ the 120 s terminal idle wait. A value outside a row's bounds, or a file the coordinator cannot read, is the default:
2060
+ a recovery never fails on its configuration.
2061
+
2062
+ Every minimum is today's constant except `recovery.terminal_idle_wait_s`: a person-visible wait can be raised and
2063
+ never shortened below what ships, and a control request that times out may already have applied. A shorter terminal
2064
+ idle wait only ends in manual-required, which retains the terminal. The 600 s wait for the source to become quiet is
2065
+ not a setting: the source's preparation lease is a fixed ten minutes, so no other value is valid. The 180 s global
2066
+ install timeout has no project to own it, and the 610 s Orca command timeout follows terminal recovery's own ten-minute
2067
+ ceiling. See the [H4 ledger](verification/2026-10-30-recovery-wait-settings.md) for every item in the group.
1919
2068
 
1920
2069
  - Writes go only to `.agenthub/config.local.json` and `.agenthub/routing.local.toml`,
1921
2070
  the machine-local files (`ahub init` ignores both in git). The shared
@@ -1967,7 +2116,8 @@ Pi relay model aliases (`pi.dgx_coding`, `pi.dgx_fast`) and `routing.pi` retain
1967
2116
  limit to 0, turns `task_sweep.enabled` or `.auto_reassign` off and changes
1968
2117
  the other Switches. Everything
1969
2118
  else needs `ahub ui --settings`, whose session may raise for 15 minutes, or
1970
- `ahub settings` in a terminal. `never-ask` needs the peer id typed in the
2119
+ `ahub settings` in a terminal; the configured conductor may set only the
2120
+ thirteen keys in [the operational command table](#conductor-operations-812-t2-933-900). `never-ask` needs the peer id typed in the
1971
2121
  dashboard, or `--yes` in the terminal, and a stored never-ask default still
1972
2122
  waits for `y` in `ahub console` at each hub start. Tool approvals stay
1973
2123
  deny-only for Pi and local in every session.
@@ -1991,7 +2141,7 @@ Instances, the review and approval blocks and the lifecycle can be edited withou
1991
2141
  - `mode` and `required_checks` in `.agenthub/sdlc.toml`. Only those two lines change: comments and every other table stay as written, and a layout the editor cannot change faithfully (a key repeated, or one inside a multi-line string) is refused with its own reason.
1992
2142
 
1993
2143
  `roster.*`, `tasks.wip_limits.*`, `permission_modes`, `peers` and the rest of the registry keep their own `ahub settings set` path.
1994
- 3. **Executable and secret-locating keys** are console only: the rest of `NEVER_EDITABLE` (`kimi_cmd`, `jcode_cmd`, `codex_bin`, `pi.cmd`, `checks`, `mlx`, `omniroute`, `terminal`, `adapters`, `adapter_modules` and the like) and the Jcode instance fields `jcode_home`, `provider`, `model` and `provider_profile`. The console shows "this runs code with the daemon's rights, or chooses where secrets are read from" and writes only when the key's exact name is typed, one key at a time; `--yes` never confirms one. The dashboard lists each of them with the console command that changes it and nothing else: no value, no path, no input. A forged dashboard request for one is refused by the hub.
2144
+ 3. **Executable and secret-locating keys** are console only: the rest of `NEVER_EDITABLE` (`kimi_cmd`, `jcode_cmd`, `codex_bin`, `pi.cmd`, `checks`, `mlx`, `omniroute`, `terminal`, `schedules`, `folder_triggers`, `corpus`, `rooms`, `local.doc_read`, `local.web_fetch`, `adapters`, `adapter_modules` and the like) and the Jcode instance fields `jcode_home`, `provider`, `model` and `provider_profile`. The console shows "this runs code with the daemon's rights, or chooses where secrets are read from" and writes only when the key's exact name is typed, one key at a time; `--yes` never confirms one. The dashboard lists each of them with the console command that changes it and nothing else: no value, no path, no input. A forged dashboard request for one is refused by the hub.
1995
2145
 
1996
2146
  In the console (`ahub console`, Settings tab `6`): `c` asks what to edit (`1` an instance, `2` the lifecycle, `3` another key), asks each field, shows the hub's diff or its loader reason, asks for a reason, then `y`. Esc cancels at any step, and pasted text never confirms. `R` appears while a restart is pending and prefills `restart`; the command still asks its own `y`. In the dashboard, open `ahub ui --settings`: the Structure group has the same forms; outside a settings session it is read only.
1997
2147
 
@@ -2009,7 +2159,7 @@ Rules:
2009
2159
  - A whole instance that holds a native home, account or model (`jcode_home`, `provider`, `model`, `provider_profile`) is tier 3 to remove or replace, so a dashboard can neither see nor delete those values; an existing instance changes leaf by leaf (`instances.<id>.<field>`), and `clear` removes one field. A preview or write shows a tier 3 value only as `(set)`, and a write changes exactly one tier 3 key, counting the keys the file adds (removing two Jcode instances is two writes, each confirmed by its own typed name). The audit record gives a tier 3 key no value hash at all, because a model or provider name is easy to recover from an unsalted hash; the file revisions bind the change instead. An instance already in `config.local.json` is changed leaf by leaf even before a restart has started it. If `.agenthub` or `config.local.json` is a symlink, the Settings view reports its revision as `unavailable` and no edit is planned. An edit of `sdlc.toml` keeps the file's own line endings.
2010
2160
  - Every write records a `config_edit` event: the file, each key path with a short hash of its value before and after (never a value), the file revisions before and after, the tier, the source (`dashboard` or `terminal`) and `by: person`. The notice and `hub.log` name key paths only.
2011
2161
  - A changed instance declaration is restart-bound: it is listed as `pending restart` (`instances`) and takes effect at the next hub start. A changed `sdlc.toml` is read once at start and is listed as `sdlc.mode` or `sdlc.required_checks` until the running hub holds the same value. `review`, `snapshots`, `approvals` and `context.gate` publish through the one reload path, one key at a time.
2012
- - Person only: an agent shell or a hub tool is refused (`ahub settings` is a console command in the identity gate, and the three control requests answer only the console role). Recovery or shutdown holds every edit.
2162
+ - Person only: an agent shell or a hub tool is refused (`ahub settings edit` is a console command in the identity gate, and the three control requests answer only the console role). Recovery or shutdown holds every edit.
2013
2163
  - The hub must run this release: an older hub answers `unknown message` and the command says to upgrade and restart it.
2014
2164
 
2015
2165
  ## Start modes: TUI by default, headless as opt-in
@@ -2216,10 +2366,10 @@ Quota also shapes routing and handoffs:
2216
2366
  while it waits (a week window crosses the gate), its work is handed over after
2217
2367
  all. `ahub budget` shows each decision and why in the pause reason.
2218
2368
  - A peer whose recent failures in a class (failed checks, changes requested,
2219
- escalations by hand) reach 1.5 after decay, and outweigh its recent
2369
+ escalations by hand) reach `tasks.demote_at` (default 1.5) after decay, and outweigh its recent
2220
2370
  approvals there (a task without a reviewer counts when done), goes behind the
2221
2371
  other candidates in the same state for that class, `local` and `pi` included.
2222
- A failure counts half after a day. `ahub route explain` names demoted peers.
2372
+ A failure counts half after `tasks.demotion_half_life_h` (default 24 hours). `ahub route explain` names demoted peers.
2223
2373
 
2224
2374
  ## Durable delivery and queue resolution
2225
2375
 
@@ -2333,6 +2483,8 @@ ahub tail
2333
2483
  ahub kill
2334
2484
  ```
2335
2485
 
2486
+ `ahub down` is the same verified stop as `ahub kill`, named as the counterpart of
2487
+ `ahub up` (#957); both are the person's, refused from an agent shell.
2336
2488
  The daemon waits for shutdown work that must survive termination. Afterward,
2337
2489
  run `ahub status` or `ahub up` and read the project state before restarting
2338
2490
  peers. A stopped daemon does not delete task records or the durable journal.
@@ -2417,10 +2569,12 @@ the top card explicitly reserved for the peer whose slot just freed.
2417
2569
 
2418
2570
  When `hub_task_done` leaves a review whose exact-head CI is not yet green (the
2419
2571
  lifecycle must be on, the task linked to a PR and not private), the hub starts one watcher
2420
- for that head (#785): one `gh pr view` a minute, ended by the CI result, by a
2572
+ for that head (#785): one `gh pr view` a minute, ended by a passed CI result, by a
2421
2573
  45 minute deadline, by a newer head, a verdict or a cancellation, or by hub
2422
2574
  shutdown; a restart does not restore it, and the next `hub_task_done` starts a new
2423
- one. On the result the owner and the reviewer each get one
2575
+ one. A failed or incomplete result is told once and the watcher keeps reading for a
2576
+ rerun of the same head: the first pending read after a newly told result renews the
2577
+ deadline once, and a deadline after a told result ends silently (#540). On the result the owner and the reviewer each get one
2424
2578
  `ci_completed: #<id> head <sha12> passed|failed|incomplete` task notice (ids only, the
2425
2579
  head cut to 12 characters; a person reviewer gets a console line instead), and
2426
2580
  unless CI failed, a review the hub held as premature for that head is offered
@@ -2570,6 +2724,7 @@ Agent CLIs update themselves often, and a hub release cannot follow every one. O
2570
2724
  - What trust cannot prove is printed when you trust.
2571
2725
  - `ahub doctor` shows one line for each installed CLI that is not on a verified version.
2572
2726
  - A daily workflow (`.github/workflows/agent-compat.yml`) runs `scripts/agent-compat-watch.ts`, which reads the latest published version of every gated CLI that has a public source: the npm packages `@anthropic-ai/claude-code`, `@openai/codex`, `@moonshot-ai/kimi-code`, `@qwen-code/qwen-code` (for both `qwen` and `qwen-dual`) and `@earendil-works/pi-coding-agent` (for `pi`, since #847 gave it a COMPAT entry), and the GitHub releases of Jcode. agy has no public source, so its row reads "no public source; local drift only" and is never a failure. Every COMPAT entry names the receipt to repeat (`receipt`), and a test requires that file to exist and the entry to have a watcher source. A version that cannot be read reads as unknown, not as a crash. The workflow keeps one `agent-compat` issue current while a source is ahead and closes it when none is.
2727
+ - **Tracking issue identity (#964):** only the exact title `agent CLI versions ahead of the verified set` identifies the issue; the `agent-compat` label is for people. The workflow reads open issues without filtering by label, then `scripts/agent-compat-issue.ts` selects one valid issue number or confirmed absence. Multiple exact-title matches fail with their issue numbers before any label or issue write. A failed read, malformed inventory or full 1000-issue result also stops without mutation. With no match, an ahead report creates the tracker and an all-verified report does nothing; unrelated labelled issues and their bodies are never selected.
2573
2728
  - **Maintenance loop (#843):** the daily upstream watch is the trigger; `ahub doctor` is the local drift notice for versions installed on a machine, and the hub itself tells the planner (below). The planner files one receipt task per (id, version): the implementer repeats the procedure in the entry's last `docs/verification/` receipt, writes a new one, and adds the version to `verified` in that same PR; the next release ships the bump. A person's `ahub agents trust` stays the stopgap that keeps a machine running meanwhile, and the person may revoke it once the bump lands.
2574
2729
  - **Local drift notice (#843 AC3):** at hub start and once a day the daemon reads each gated CLI's installed version with the probe `ahub agents` uses (no network, no install). For every installed CLI on a version that is not `verified` (in-line, trusted or refused) the effective planner gets one notice per (id, version): the id, the version, the state and the last receipt path, nothing the CLI printed. A CLI that is not installed, or whose version cannot be read, is not drift. The marker is kept per (id, version, planner) in `hub.db` (`agent_drift_notices`), so a restart repeats nothing, a planner whose notice could not be queued (or who joins later) is retried alone without telling the others again, and a new version is a new notice; with no rostered planner nothing is recorded. `ahub status` shows one `agent CLI drift` attention line per drifted id until the installed version is verified.
2575
2730
 
@@ -2592,7 +2747,7 @@ section is what they run.
2592
2747
 
2593
2748
  Upgrade running projects with the target release's own coordinator. It accepts
2594
2749
  a running source on control protocol 9 (0.6.x), 10 (0.7.0 through 0.12.0),
2595
- 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 through 0.20.0), and only
2750
+ 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 through 0.21.0), and only
2596
2751
  a target on its own protocol, so the target's coordinator fits every supported
2597
2752
  source and carries every recovery fix released up to it. Protocol 8 and older
2598
2753
  (0.5.x and earlier) are refused as `manual-bootstrap-required`. Run from the
@@ -2606,7 +2761,7 @@ bunx --package @staix/agent-hub@0.12.43 ahub upgrade --to 0.12.43 --yes
2606
2761
  | Running now | Coordinator to use |
2607
2762
  | --- | --- |
2608
2763
  | 0.6.x (protocol 9) | the target's, through `bunx` as above |
2609
- | 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 through 0.20.0 (protocol 16) | the target's, through `bunx` as above |
2764
+ | 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 through 0.21.0 (protocol 16) | the target's, through `bunx` as above |
2610
2765
  | any supported source, with the installed CLI already at the target | `ahub upgrade` below, which is the same coordinator |
2611
2766
  | 0.5.x or earlier (protocol 8 and older) | not supported: bootstrap by hand with the matching CLI |
2612
2767
 
@@ -3511,7 +3666,15 @@ output through unchanged and appends one byte to a progress file for each of tho
3511
3666
  the watchdog polls that file's size. On a stop it prints where the run is and the process
3512
3667
  table below it first. `AHUB_CHECK_HANG_S` (960 by default) is only the backstop for a run
3513
3668
  that keeps printing results while hung; hosted CI sets 1500 on Linux inside a 35-minute
3514
- job limit and 1800 on macOS inside a 40-minute one (#378, #593, #752, #516).
3669
+ job limit (#378, #593, #752, task 516). The macOS leg runs as four shards (#548): `AHUB_CHECK_SHARD=i/n`
3670
+ makes `scripts/check.sh` run slice `i` of the full suite through `bun test --shard=i/n --timings=scripts/test-timings.json`, which puts
3671
+ every test file in exactly one shard and balances them by the recorded per-file times; shard 1 also runs the docs check, typecheck, role check, bundle freshness and
3672
+ package contents, so each of those runs once per gate, and the others install and test. Each shard runs under the same 1500 s backstop as Linux inside a 25-minute job
3673
+ limit, and its last line is `check: OK (shard i/n)`. The required check `check (macos-latest)` is an aggregator job that passes only when every shard passed (or an
3674
+ identical-tree full proof was reused), so branch protection and the reuse proof read the same names as before. `AHUB_CHECK_SHARD` is the full suite only: it cannot be
3675
+ combined with `--changed`. A shard execs the system git once before its tests (`AHUB_CHECK_WARM_GIT`, default `/usr/bin/git`; a failure of it is ignored), because the first execs of `/usr/bin/git` on a fresh hosted macOS runner outlast the 1000 ms some tests give it. `scripts/test-timings.json` is advice for balance, never a list of tests: a file missing from it still runs (Bun gives it the median time).
3676
+ Regenerate it from a green macOS leg when the shards drift apart: `gh api --allow-escape-sequences repos/<owner>/<repo>/actions/jobs/<macOS shard job id>/logs`
3677
+ for each shard, or from one unsharded leg's log, then `bun scripts/make-test-timings.ts <log>`.
3515
3678
  The `closing-refs` workflow fails a PR whose title, body or commit messages would make
3516
3679
  GitHub close an issue the text only mentions (`Refs #N`, a negated or qualified keyword);
3517
3680
  an issue linked only in the sidebar passes (#760).
@@ -3559,6 +3722,7 @@ own 5 h and weekly ratios stay unknown. Not yet verified live against Kimi's `/u
3559
3722
  update whenever it names the current session, because 2.1.1's source text
3560
3723
  emits it after the turn settles (read from the source, not yet observed live
3561
3724
  through the hub; docs/smoke.md), so the peer may already be idle.
3725
+ Jcode's context occupancy comes from its ACP `usage_update` (`{used, size}`, source `jcode_usage_update`, bound to the adapter's own session id) on the verified 0.99.1 shape; its consumption is one `usage` event per prompt (source `jcode_acp`: the prompt result's `inputTokens`, `outputTokens`, `cachedReadTokens` and `cachedWriteTokens` as the hub's buckets, never Jcode's `totalTokens`, which counts the cached read twice for OpenAI-style providers) and never a `tokens` event or a window. Jcode has no native quota reader: `jcode usage --json` carries a free-text limit name and no window length, so `ahub budget` keeps `no reading` for it and `ahub budget set` stays the way in (receipt: `docs/verification/2026-10-28-jcode-0.99.1-usage.md`).
3562
3726
  Pi, Qwen (until a native source is verified, #625) and other unsupported surfaces show unknown.
3563
3727
  A stale or disconnected reading is unknown, not 0%. Codex's accumulated session
3564
3728
  usage is never used as context occupancy. In the tail (`ahub tail` and the
@@ -3596,6 +3760,32 @@ nor hands work over. Continue normally or deliberately restart into a fresh
3596
3760
  session with your chosen checkpoint as preface. No automatic restart or native
3597
3761
  compaction override is performed.
3598
3762
 
3763
+ ## Context headroom admission for new work (#802)
3764
+
3765
+ `context.max_assignment_used` is a separate, default-off admission threshold.
3766
+ In an untracked `.agenthub/config.local.json`, set it to a fraction in `(0, 1]`,
3767
+ for example `{"context":{"max_assignment_used":0.85}}`. Zero disables it.
3768
+ Tracked configuration cannot enable it on another machine. Restart deliberately,
3769
+ or preview and confirm `ahub settings reload` as the person; it applies to the
3770
+ next routing decision. `context.stale_min` is the freshness limit.
3771
+
3772
+ A fresh native occupancy at or above the threshold skips that peer for a new
3773
+ automatic owner offer. `ahub route explain` uses the same pure decision and
3774
+ prints the measured fraction and limit. Missing, malformed, future, stale,
3775
+ detached or replaced-session evidence is explicitly unknown: it neither blocks
3776
+ nor establishes spare capacity, and the existing routing rules still decide.
3777
+ Only adapters with declared native context readers supply evidence, including
3778
+ Jcode's session-bound reader; unsupported surfaces stay unknown.
3779
+
3780
+ A name or reservation alone does not bypass admission. Explicit assignments by
3781
+ the person or current configured conductor, and a peer's own self-claim, may
3782
+ bypass this context limit; roster, privacy, quota, capability and other gates
3783
+ still apply. Reviewer selection is unchanged, including review-class owners.
3784
+ The rule never pauses, compacts, replaces or moves an active session or task.
3785
+ It leaves `context.gate` checkpoint events and requests unchanged, and does not
3786
+ add a telemetry-driven reassignment or retry timer. Re-evaluate or explicitly
3787
+ assign an unassigned task when its owner's context has changed.
3788
+
3599
3789
  ## Pi tool refusals, exit causes and idle automatic restart
3600
3790
 
3601
3791
  Pi hub-task/conductor refusals proven before any task or board effect return
@@ -3689,8 +3879,9 @@ request and does not apply to model-origin bash calls.
3689
3879
 
3690
3880
  `ahub stop kimi`, `ahub stop pi` (headless mode), or `ahub stop local` stops
3691
3881
  that hub-owned runtime through its adapter and prints `<peer>: offline`.
3692
- The hub and its other peers keep running. The command is human-only;
3693
- agent shells and tools-role connections cannot use the `peer_stop` request.
3882
+ The hub and its other peers keep running. The command is the person's or the
3883
+ configured conductor's (an audited operational admission); other agent shells and
3884
+ tools-role connections are refused at the `peer_stop` request.
3694
3885
  An older hub that does not know the request needs an upgrade before this
3695
3886
  command is available.
3696
3887
 
@@ -3756,7 +3947,7 @@ that the file is untracked; a dropped value is reported by the settings result.
3756
3947
 
3757
3948
  ## SDLC artifact receipts and gates
3758
3949
 
3759
- The playbook lifecycle (#257, #481) is off without `.agenthub/sdlc.toml`. When enabled its stages are intent, spec, plan, build, test, review, merge, release and maintain, grouped as Plan, Design, Build, Test, Deploy and Maintain. Advise shows blockers; enforce checks implemented head-bound review and approval gates. Stored implement/verify receipts read as build/test; new enabled receipts use canonical ids. See [the SDLC contract](sdlc.md) for gates, required check names, local receipts, console-only readbacks and the S0-S8 delivery table. A person accepts a spec issue with `ahub sdlc accept <task> --issue <n>` (S1). The planner records plan reviews with review and CI scope (`hub_plan_review`, `ahub sdlc plan-review`) and grooms with `ahub backlog groom`; `ahub backlog return <task> <revision> <reason>` returns accepted, untouched work (S9). `ahub sdlc costs [owner/repo]` reports review rounds and CI runs of merged PRs without writing the board (S10). `ahub sdlc metrics [--since <YYYY-MM-DD|ISO time>]` (the planner tool `hub_sdlc_metrics`, planner or person, read-only) reports lead times, first-pass approval and check rates, rework rounds, stale-head re-reviews and open stalls from board history, with `unknown` counted and never 0; optional `[bands.<metric>]` in `sdlc.toml` add control bands that the daemon checks every five minutes in advise or enforce, writing a hub notice (`log`), a read-only `triage` backlog task (`diagnose`) or a `plan` backlog card (`propose`), one per breach episode and never a GitHub issue (S7, see [metrics](sdlc.md#sdlc-metrics-and-control-bands-481-s7)). With the lifecycle on, a task's issue identity is linked when it is proposed or accepted, from the one issue it names (an imported GitHub issue, or a `#N`, `owner/repo#N` or issue URL in its title); a title that names a sibling repository (`agent-mem#13`) links only when `refs.repo` names it, and without `refs.repo` the propose result says to set it. `ahub backlog groom` also backfills up to 32 links per call, within 1 s (#723). S11 release trains ship in 0.12.37 (see [release scheduling](#planner-release-scheduling)). S3 structured review findings (an open Important finding blocks approval in enforce; `max_nits` in `sdlc.toml` caps nits) and S2 literal-path plan conformance with the person-only `ahub sdlc accept-departure <task> <head> <reason...>` are in source; see [findings](sdlc.md#structured-review-findings-481-s3) and [plan conformance](sdlc.md#plan-conformance-481-s2). S4 [test integrity](sdlc.md#test-integrity-481-s4) and S6 [release authorization](#release-approval-and-optional-tag-guard) are in source too. S5 continuous evals, release metrics and bands, and a full live advise/release cycle remain pending. Owners/reviewers record bounded identities through `hub_sdlc_record`; these grant no additional authority. Off output, receipt and schema shapes stay unchanged.
3950
+ The playbook lifecycle (#257, #481) is off without `.agenthub/sdlc.toml`. When enabled its stages are intent, spec, plan, build, test, review, merge, release and maintain, grouped as Plan, Design, Build, Test, Deploy and Maintain. Advise shows blockers; enforce checks implemented head-bound review and approval gates. Stored implement/verify receipts read as build/test; new enabled receipts use canonical ids. See [the SDLC contract](sdlc.md) for gates, required check names, local receipts, console-only readbacks and the S0-S8 delivery table. A person accepts a spec issue with `ahub sdlc accept <task> --issue <n>` (S1). The planner records plan reviews with review and CI scope (`hub_plan_review`, `ahub sdlc plan-review`) and grooms with `ahub backlog groom`; `ahub backlog return <task> <revision> <reason>` returns accepted, untouched work (S9). `ahub sdlc costs [owner/repo]` reports review rounds and CI runs of merged PRs without writing the board (S10). `ahub sdlc metrics [--since <YYYY-MM-DD|ISO time>]` (the planner tool `hub_sdlc_metrics`, planner or person, read-only) reports lead times, first-pass approval and check rates, rework rounds, stale-head re-reviews and open stalls from board history, with `unknown` counted and never 0; optional `[bands.<metric>]` in `sdlc.toml` add control bands that the daemon checks every five minutes in advise or enforce, writing a hub notice (`log`), a read-only `triage` backlog task (`diagnose`) or a `plan` backlog card (`propose`), one per breach episode and never a GitHub issue (S7, see [metrics](sdlc.md#sdlc-metrics-and-control-bands-481-s7)). With the lifecycle on, a task's issue identity is linked when it is proposed or accepted, from the one issue it names (an imported GitHub issue, or a `#N`, `owner/repo#N` or issue URL in its title); a title that names a sibling repository (`agent-mem#13`) links only when `refs.repo` names it, and without `refs.repo` the propose result says to set it. `ahub backlog groom` also backfills up to 32 links per call, within 1 s (#723). S11 release trains ship in 0.12.37 (see [release scheduling](#planner-release-scheduling)). S3 structured review findings (an open Important finding blocks approval in enforce; `max_nits` in `sdlc.toml` caps nits) and S2 literal-path plan conformance with the person-only `ahub sdlc accept-departure <task> <head> <reason...>` are in source; see [findings](sdlc.md#structured-review-findings-481-s3) and [plan conformance](sdlc.md#plan-conformance-481-s2). S4 [test integrity](sdlc.md#test-integrity-481-s4) and S6 [release authorization](#release-approval-and-optional-tag-guard) are in source too. S5 is in source as an [eval gate mechanism](sdlc.md#eval-gate-for-agent-configuration-481-s5) with no suite chosen, and S7 adds merge-time readback, [release metrics](sdlc.md#release-metrics-481-ac12) and an opt-in security baseline; a full live advise/release cycle and the per-release gates-run record remain pending. Owners/reviewers record bounded identities through `hub_sdlc_record`; these grant no additional authority. Off output, receipt and schema shapes stay unchanged.
3760
3951
  ## Optional Qwen Code peer
3761
3952
 
3762
3953
  Select Qwen per project with `ahub roster add qwen`; it defaults off and is absent
@@ -3771,7 +3962,7 @@ selected roster and existing capabilities; the TUI is always a person's start.
3771
3962
  `ahub qwen --mode tui` runs `qwen --input-file <in> --json-file <out> --mcp-config <hub server> --approval-mode default` in the terminal you ran it from. The daemon prepares the two files (mode 0600, under `<state dir>/qwen/qwen-dual/`) and returns the launch; the hub then appends one `submit` line per delivery and tails the event file from a saved offset. It needs a Qwen the dual-output compat entry verifies (`ahub agents` shows `qwen-dual`, verified 0.26.1 only; the headless `qwen` entry is separate and unchanged) and refuses another version before it creates a file. The TUI is explicit only: `ahub qwen` without `--mode` is still headless, Qwen is not a start-mode peer, and the hub never opens this terminal by itself. The TUI start is refused for an extra Qwen instance, off roster, outside a terminal, and while a headless Qwen runs (`ahub stop qwen` first); a headless start is refused while the TUI holds its terminal.
3772
3963
 
3773
3964
  - What the hub does: a delivery becomes a `submit`; the last assistant message of the turn (an `assistant` event whose `stop_reason` is not `tool_use`, then `message_stop`) is shared as the answer; busy and idle follow the events. A `can_use_tool` request goes to the console approval path (`ahub permit`, the dashboard), and the hub's own tools are approved only by their exact `mcp__agent-hub__<tool>` name. If you answer a prompt in the Qwen terminal first, the hub's pending approval is withdrawn.
3774
- - **Limit 1, a deny ends the turn without an answer.** Qwen emits nothing when the hub denies: it cancels the whole turn. The hub ends the turn itself, shares no answer, settles the delivery as `completed` (a deny is a known outcome, and a `needs_review` would hold every later message to Qwen until a person resolved it) and says so in a notice. A turn whose outcome is not known (Qwen exited, an Esc followed by a new prompt, an event file that became unreadable) is the one that settles `needs_review`. Qwen's own notice that a background shell finished (a `user` event that is wholly one or more `<task-notification>` envelopes) is not a new prompt: it ends no hub turn and leaves an idle peer idle (#517).
3965
+ - **Limit 1, a deny ends the turn without an answer.** Qwen emits nothing when the hub denies: it cancels the whole turn. The hub ends the turn itself, shares no answer, settles the delivery as `completed` (a deny is a known outcome, and a `needs_review` would hold every later message to Qwen until a person resolved it) and says so in a notice. A turn whose outcome is not known (Qwen exited, an Esc followed by a new prompt, an event file that became unreadable) is the one that settles `needs_review`. Qwen's own notice that a background shell finished (a `user` event that is wholly one or more `<task-notification>` envelopes) is not a new prompt: it ends no hub turn and leaves an idle peer idle (task 517).
3775
3966
  - **Limit 2, the hub cannot stop a Qwen turn.** The input protocol has only `submit` and `confirmation_response`, and a signal does not stop a running turn. The inactivity watchdog therefore never cancels: after `watchdog_ms` of silence it marks the turn stalled (`ahub status`, the console and the dashboard say "press Esc in the Qwen terminal") and keeps the peer busy. The hub never types into your terminal and never signals Qwen to stop a turn.
3776
3967
  - A Qwen write or shell command that names the hub state directory is refused (see [security](security.md): the input file is a prompt channel).
3777
3968
  - If the hub cannot read the event file the peer is `detached`, shown as offline with a notice, never idle: Qwen itself keeps working, and the hub reattaches when the file is readable again.
@@ -3866,7 +4057,7 @@ Configured kinds are cloud peers and never receive PII work. Quota and context a
3866
4057
 
3867
4058
  ## External adapter modules (#451 A4)
3868
4059
 
3869
- A bundled adapter module is authorized by the person with `adapter_modules` (at most 16 rows of `path` and `sha256`) in an untracked `.agenthub/config.local.json`; in `config.json` or a tracked file it is ignored with a notice, and settings never edit it. Only daemon startup imports a module: settings, reload, previews and CLI reads never do, so a change needs a restart. The module must be a single self-contained ESM file inside the project (no symlinked, world-writable or oversized file) whose bytes match the pin, and it may import only Node/Bun builtins and `@staix/agent-hub/adapter`; the scanner also refuses any `require`, `eval` or `Function` word token. Each import must settle within 2 s. A module that fails any check is refused on its own, with a bounded reason in the status reply, while startup and the other adapters continue; its roster row stays inert. A loaded kind is headless, cloud (never PII), off roster until `ahub roster add <kind>`, and has no controlled recovery: stop it and start it again with its recorded session id. While modules are declared, the upgrade or restart source and target must advertise `external_adapter_modules`. Bundling, the accepted definition subset and the conformance kit (`@staix/agent-hub/conformance`, for module CI only) are in [Adding an AI tool](adding-an-ai-tool.md#5-external-adapter-modules-451-a4). A pin authorizes code that runs with the daemon's rights; it is not a sandbox ([security](security.md)).
4060
+ A bundled adapter module is authorized by the person with `adapter_modules` (at most 16 rows of `path` and `sha256`) in an untracked `.agenthub/config.local.json`; in `config.json` or a tracked file it is ignored with a notice, and settings never edit it. Only daemon startup imports a module: settings, reload, previews and CLI reads never do, so a change needs a restart. The module must be a single self-contained ESM file inside the project (no symlinked, world-writable or oversized file) whose bytes match the pin, and it may import only Node/Bun builtins and `@staix/agent-hub/adapter`; the scanner also refuses any `require`, `eval` or `Function` word token. Each import must settle within 2 s. A module that fails any check is refused on its own, with a bounded reason in the status reply, while startup and the other adapters continue; its roster row stays inert. A loaded kind is headless, cloud (never PII), off roster until `ahub roster add <kind>`, and has no controlled recovery: stop it and start it again with its recorded session id. While modules are declared, the upgrade or restart source and target must advertise `external_adapter_modules`. Extra `instances` of a module kind (#754) exist only when the module opts in with `headlessInstances` (headless; implementer, verifier or continuing reviewer) and the person declares them in `config.local.json`; each needs its own worktree, is bound to what startup verified, and needs `external_adapter_instances` on both sides of a restart or upgrade ([security](security.md)). Bundling, the accepted definition subset and the conformance kit (`@staix/agent-hub/conformance`, for module CI only) are in [Adding an AI tool](adding-an-ai-tool.md#5-external-adapter-modules-451-a4). A pin authorizes code that runs with the daemon's rights; it is not a sandbox ([security](security.md)).
3870
4061
 
3871
4062
  # Headless ACP watch (#286)
3872
4063
 
@@ -3879,7 +4070,7 @@ A bundled adapter module is authorized by the person with `adapter_modules` (at
3879
4070
  # Acceptance-criteria checklist (#481)
3880
4071
 
3881
4072
  - The task reviewer/person records a head-bound `criteria` receipt with criterion ids matching the issue's AC ids, required/result fields and hashed evidence refs. This checklist belongs to the Compliance review pass. Use `ahub sdlc record <id> '<receipt JSON>'`; [schema and example](sdlc.md#acceptance-criteria-checklist).
3882
- - Deliverables (#509a, docs/sdlc.md, Deliverable records): `hub_task_done` and `hub_sdlc_record` take `deliverables: [{path, format, version?}]`; the hub hashes the files itself (a claimed hash is refused), in a plain folder with no git or PR, under the owner's execution root (its own worktree for a declared worktree instance; an unproven root refuses and reads `unknown`), and a reviewer's approval is refused with `deliverable changed since it was delivered` when a file changed since delivery (the owner calls `hub_task_done` again, or the person approves the current bytes). `ahub task show <id>` shows each file's state (`unchanged`, `changed`, `missing`, `unknown`) and `changedSinceReview`; a PII task or `pii.paths` file shows id, hash and size only. The person lists every task's delivered and approved files with `ahub deliverables [--task <id>] [--approved] [--check] [--json]`: without `--check` it reads the board's records and opens no file; `--check` measures them now and marks a row `changed since review`, `a file is missing` or `could not be measured`. The dashboard card shows a `Deliverables n` chip and the task sheet a Deliverables group from the same records; it shows a change only after a live check. An agent's shell is refused (#317).
4073
+ - Deliverables (#509a, docs/sdlc.md, Deliverable records): `hub_task_done` and `hub_sdlc_record` take `deliverables: [{path, format, version?}]`; the hub hashes the files itself (a claimed hash is refused), in a plain folder with no git or PR, under the owner's execution root (its own worktree for a declared worktree instance; an unproven root refuses and reads `unknown`), and a reviewer's approval is refused with `deliverable changed since it was delivered` when a file changed since delivery (the owner calls `hub_task_done` again, or the person approves the current bytes). `ahub task show <id>` shows each file's state (`unchanged`, `changed`, `missing`, `unknown`) and `changedSinceReview`; a PII task or `pii.paths` file shows id, hash and size only. The person lists every task's delivered and approved files with `ahub deliverables [--task <id>] [--approved] [--check] [--json]`: without `--check` it reads the board's records and opens no file; `--check` measures them now and marks a row `changed since review`, `a file is missing` or `could not be measured`. The dashboard card shows a `Deliverables n` chip and the task sheet a Deliverables group from the same records; it shows a change only after a live check. An agent's shell gets only the tasks `hub_task_show` would show it (#933).
3883
4074
  - The review request lists the recorded criteria with their ids and results, and a `hub_review` unmet item that starts with a recorded id (`AC2: ...`) is recorded on the verdict as `unmetCriteria` (#507b); `ahub task show` lists those ids in the history. A task with no checklist, and a PII task, are unchanged.
3884
4075
  - Advise shows incomplete/stale checklist blockers; enforce checks them at approval. The `criteria` gate does not need a workflow selector. Dependencies stay with `ahub task propose --after`; checklist task import is removed.
3885
4076
  - `status <id>`, `explain <id>` and `export <id>` use the same current board/config projection. Export keeps sanitized state and source hashes, with no external text, extra authority or verification claim.
@@ -3893,7 +4084,7 @@ A bundled adapter module is authorized by the person with `adapter_modules` (at
3893
4084
 
3894
4085
  # Project profiles (#259)
3895
4086
 
3896
- - Initialize new projects with `ahub init --profile research|writing|general`; default dev retains its existing templates and contracts. Profile aliases map to the existing base flow and are displayed beside it.
4087
+ - Initialize new projects with `ahub init --profile research|writing|general`; default dev retains its existing templates and contracts. Profile aliases map to the existing base flow and are displayed beside it. `--profile <pack>` applies a domain pack (`.agenthub/roles/<name>.pack.json` or a shipped one) as review-checklist and example-check overrides; `--dry-run` previews ids only, and a pack is data, never a command ([domain packs](profiles.md#domain-packs-513a), #513a).
3897
4088
  - Non-dev plans declare paths, section anchors, source ids and claims. Configure completion checks explicitly in machine-local config; selecting a profile never starts a check or study. Schema, examples and pending converter/policy slices: [profiles](profiles.md).
3898
4089
 
3899
4090
  # Private and read-only data paths (#259)
@@ -3913,7 +4104,7 @@ A bundled adapter module is authorized by the person with `adapter_modules` (at
3913
4104
  # Person catalog-role assignment (#295)
3914
4105
 
3915
4106
  - Use `ahub role assign <peer> <id>` or `ahub role clear <peer> [<id>]`, or the dashboard Roles settings group. The selection is bounded to two compatible catalog ids, writes only machine-local settings and applies at the next peer start. Inherit and explicit no roles are distinct.
3916
- - Change an open task's guidance with `ahub task role <id> <role|none>`. Agent shells and peer/tools control clients cannot use these mutation commands. Details and unconfirmed native consumption: [roles](roles.md).
4107
+ - Change an open task's guidance with `ahub task role <id> <role|none>`. Agent shells and peer/tools control clients cannot use `role assign|clear`; an agent may run `task role` on a task it owns while it is not in review, and a child, read-only or private-context connection is refused (#933). Details and unconfirmed native consumption: [roles](roles.md).
3917
4108
 
3918
4109
  # Document projection review and undo
3919
4110
 
@@ -4102,7 +4293,7 @@ The manager allows split screening, the bounded database wait and forwarding mar
4102
4293
  ## One-way GitHub intake (#263)
4103
4294
 
4104
4295
  `ahub board import github --label <label> [--milestone <milestone>]
4105
- [--repo <owner/repo>]` is person-only. Omitted repo is resolved by gh in the
4296
+ [--repo <owner/repo>]` is the person's or the configured conductor's (#933; the conductor is recorded as the actor). Omitted repo is resolved by gh in the
4106
4297
  project directory. The command reads open issues with fixed bounded argv and
4107
4298
  stages up to 32 into unoffered Backlog, retaining canonical repo/issue identity
4108
4299
  across restarts. Repeating intake does not refresh or overwrite existing cards.
@@ -4370,7 +4561,7 @@ work. `ahub status` and the dashboard show the provider, model and profile as
4370
4561
  declared, and `ahub doctor` lists the MCP servers each rostered instance's home would
4371
4562
  load.
4372
4563
 
4373
- Upgrade note (#368): the project's `.jcode/mcp.json` entry for Jcode no longer names
4564
+ Upgrade note (task 368): the project's `.jcode/mcp.json` entry for Jcode no longer names
4374
4565
  `AGENTHUB_PEER_ID`, because the file is shared by every Jcode in the project and Jcode
4375
4566
  gives an entry's own env precedence over the process env. Each hub-started Jcode now
4376
4567
  supplies its own peer id and a launch id, and the hub admits a hub-tools connection
@@ -4387,7 +4578,7 @@ An extra Claude instance is one you start yourself: declare it with `roles` (for
4387
4578
  terminal. It keeps its own `claude-*.claude-review.json` state files. A planner or reviewer instance reads the
4388
4579
  project checkout and refuses `worktree`. From #525-1 an `implementer` or `verifier` instance is accepted and
4389
4580
  requires its own `worktree` (for example `"claude-impl": { "kind": "claude", "roles": ["implementer"],
4390
- "worktree": ".worktrees/claude-impl" }`). `ahub claude --as claude-impl` validates that registered branch worktree and starts Claude there while keeping its canonical hub connection. Its private launch record stores the execution root. The channel reports its own canonical cwd (verified model-free with Claude 2.1.309); the daemon grants owner isolation only when that root, the current launch id, the recorded root and its own worktree validation all agree. Missing, legacy or mismatched evidence still permits a channel attachment, but write-owner routing is refused with `owner admission: declared write owner requires a verified isolated execution root`. Status shows the isolation refusal. A detach, replacement claim or launch-record change invalidates the proof. Facts and permission grants use the verified execution root; an unverified implementer gets no automatic grant. Recovery records retain that execution root, checked against the current declaration, separately from the Orca grouping root. This is same-user launch evidence, not kernel filesystem isolation. A Claude instance holding only planner or reviewer roles still uses the canonical checkout. A headless Claude reviewer retains its reviewer-only declaration. A headless implementer is available with the separate declaration below (#525-4).
4581
+ "worktree": ".worktrees/claude-impl" }`). `ahub claude --as claude-impl` validates that registered branch worktree and starts Claude there while keeping its canonical hub connection. Its private launch record stores the execution root. The channel reports its own canonical cwd (verified model-free with Claude 2.1.309); the daemon grants owner isolation only when that root, the current launch id, the recorded root and its own worktree validation all agree. Missing, legacy or mismatched evidence still permits a channel attachment, but write-owner routing is refused with `owner admission: declared write owner requires a verified isolated execution root`. Status renders the last verified binding from memory, without workspace-Git revalidation; this display is not permission or owner-admission authority. Detach or replacement claims invalidate the binding. A launch-record or filesystem/branch change is checked at the next routing admission, facts permission reply or recovery owner proof, which revokes a mismatch before effects. Facts and permission grants use the verified execution root; an unverified implementer gets no automatic grant. Recovery records retain that execution root, checked against the current declaration, separately from the Orca grouping root. This is same-user launch evidence, not kernel filesystem isolation. A Claude instance holding only planner or reviewer roles still uses the canonical checkout. A headless Claude reviewer retains its reviewer-only declaration. A headless implementer is available with the separate declaration below (#525-4).
4391
4582
 
4392
4583
  A headless Claude implementer (#525-4) is declared separately:
4393
4584
 
@@ -4413,7 +4604,7 @@ Project Git retains the core policy that clears credential.helper and core.askpa
4413
4604
 
4414
4605
  The [native permission receipt](verification/2026-10-23-headless-claude-permissions.md) covers Claude Code 2.1.309, and the [2.1.310 receipt](verification/2026-10-26-claude-implementer-2.1.310.md) repeats it on 2.1.310 (with 2.1.309 as a control), each with a deterministic loopback fixture and no real account/model call. It is not sandbox proof or a runtime receipt gate. The real account/model/person interaction remains a separate live leg.
4415
4606
 
4416
- A headless Claude reviewer is run by the hub instead (#223): declare
4607
+ A headless Claude reviewer is run by the hub instead (task 223): declare
4417
4608
  `"claude-review": { "kind": "claude", "roles": ["reviewer"], "start_mode": "headless" }`
4418
4609
  and start it with `ahub claude --as claude-review`, which asks the running hub to start it (nothing runs in that terminal); `ahub stop claude-review` stops it. It holds no process while idle; each
4419
4610
  review request runs one new `claude -p` session (never resumed) that may only read
@@ -4447,7 +4638,7 @@ From 0.12.37 reviews can rotate across fresh headless Claude and Codex reviewers
4447
4638
 
4448
4639
  A reviewer has a review scope (#852): `full` (reads the code, runs the affected tests, reads CI) or `code` (reads the code only). The fresh Codex and Kimi reviewers run in a sandbox that cannot run `bun test` or read CI, so they default to `code`; every other reviewer is `full`. A `code` reviewer's review request replaces the check and exact-head CI lines with one: its sandbox cannot run tests or read CI, review the code against the plan, and the merger gates on exact-head CI. Findings are only defects in the reviewed code, docs or tests; a missing check result, CI result, test run or PR/branch identity belongs in `unmet`, never in `findings`. A `code` reviewer's approval and verdicts record `reviewScope: "code"` in the task history. Its `changes_requested` is always recorded and never refused: with at least one structured finding whose disposition is `open` it counts as a rejection as usual; without one it is recorded as evidence-only, which does not count toward `rejections` or escalation, still returns the task to its owner, and sends the conductor one task notice with ids only. The review request therefore asks a `code` reviewer to report every code defect as a structured finding with an id. A person's verdict is never evidence-only. The scope is a fixed property of the definition. To change it for one instance, set `"review_scope": "full"` or `"code"` on that fresh-reviewer instance in the untracked `config.local.json` (a tracked declaration is ignored with a reason; the key is tier 3, so the dashboard and settings sessions cannot read or change it, and a hub daemon restart applies it). `ahub status` shows `review scope` on each reviewer row. A task approved by a `code` reviewer is merged only after exact-head CI is green and no review thread is unresolved, and the merge note names the CI run.
4449
4640
 
4450
- Start them with `ahub claude --as claude-review`, `ahub codex --as codex-review` and `ahub kimi --as kimi-review`; stop with `ahub stop <id>`. For the Claude and Codex reviewers the command asks the running hub to start the reviewer and returns: nothing runs in that terminal, it takes no launch arguments, and `--print-command` or `--dry-run` prints a hub-start note instead of a launch (#720). Fresh Claude and Codex variants share the project checkout; the Kimi variant runs in a clean temporary working directory and reviews only the screened brief and host facts. All fresh variants refuse a custom worktree or permission mode. Codex and Kimi require `review_mode: "fresh"`; without it, an existing headless instance with the reviewer role keeps its native app-server/ACP behavior, worktree and permission options and does not join this pool. Claude retains its #223 automatic headless-reviewer declaration. The agy reviewer slot remains automatically unavailable (`agy is an implementer peer (#485)`). A failed or unsupported fresh start never downgrades to a native continuing session. Ordinary implementer instances retain their own adapters. The default logical pool order is Claude, Codex, Kimi. Only configured kinds with at least one declared reviewer count as slots, including declared ones that are offline, paused, quota-limited or failing, so availability never renumbers them; configured kinds with no declaration are listed after the walk as `no declared reviewer`. `review.rerotate` defaults to false. Pool selection starts at PR number modulo the number of declared kind slots, or task id when there is no PR, then skips the implementer's family and unavailable candidates. Delta review prefers the preceding eligible independent family in a fresh child. A preceding same-family fallback does not outrank a newly available independent family. `ahub route explain` shows the same pure walk.
4641
+ Start them with `ahub claude --as claude-review`, `ahub codex --as codex-review` and `ahub kimi --as kimi-review`; stop with `ahub stop <id>`. For the Claude and Codex reviewers the command asks the running hub to start the reviewer and returns: nothing runs in that terminal, it takes no launch arguments, and `--print-command` or `--dry-run` prints a hub-start note instead of a launch (#720). Fresh Claude and Codex variants share the project checkout; the Kimi variant runs in a clean temporary working directory and reviews only the screened brief and host facts. All fresh variants refuse a custom worktree or permission mode. Codex and Kimi require `review_mode: "fresh"`; without it, an existing headless instance with the reviewer role keeps its native app-server/ACP behavior, worktree and permission options and does not join this pool. Claude retains its task 223 automatic headless-reviewer declaration. The agy reviewer slot remains automatically unavailable (`agy is an implementer peer (#485)`). A failed or unsupported fresh start never downgrades to a native continuing session. Ordinary implementer instances retain their own adapters. The default logical pool order is Claude, Codex, Kimi. Only configured kinds with at least one declared reviewer count as slots, including declared ones that are offline, paused, quota-limited or failing, so availability never renumbers them; configured kinds with no declaration are listed after the walk as `no declared reviewer`. `review.rerotate` defaults to false. Pool selection starts at PR number modulo the number of declared kind slots, or task id when there is no PR, then skips the implementer's family and unavailable candidates. Delta review prefers the preceding eligible independent family in a fresh child. A preceding same-family fallback does not outrank a newly available independent family. `ahub route explain` shows the same pure walk.
4451
4642
 
4452
4643
  Codex requires a verified `codex-cli` (0.146.0, 0.163.0 or 0.164.0) or one the person trusts on this machine, and a passing bounded runtime receipt before its model child starts. 0.146.0 and 0.163.0 passed the [2026-10-22 model-free production admission](verification/2026-10-22-codex-reviewer-isolation.md) and 0.164.0 the [2026-10-24 one](verification/2026-10-24-codex-0164-reviewer-isolation.md). A refusal names the installed and verified versions. Either set `codex_bin` to a verified executable, or run `ahub agents trust codex-review <version>`: trust lifts only the version pin, and every proof below still runs before each child (#691). Each launch approves exactly `hub_review` and `hub_task_show` per tool (`approval_mode = "approve"`), never through a server-wide default: without it Codex 0.164.0 listed the tools but refused the call, so reviews ended with no verdict (#783). A model-free receipt proves the inventory, not that a verdict reaches the hub; a [model-backed 0.164.0 run](verification/2026-10-25-codex-0164-reviewer-tool-call.md) recorded one verdict through that path. The producer checks native binary/config stability, configured MCP inventory with only the two hub tools, effective Apps/plugins/hooks disablement, an explicit empty legacy `notify` command and a named filesystem profile. Apps needs separate feature readback because configured inventory omits runtime connectors. Web search is requested disabled; its effective turn mode is not read back and managed requirements can select Cached (see the verification ceiling). Each launch proves public-read, exact/glob/symlink private-denial, standard host credential denial and write-denial in temporary fixtures. Standard credential and configured tool-home/XDG subtrees are denied independently of project `local.deny`. Its sandboxed shell is filesystem read-only with private paths denied and network configured disabled; it is not Claude's Read/Grep/Glob-only tool whitelist. No credential home is copied.
4453
4644
 
@@ -4457,9 +4648,9 @@ A dangling or unreadable standard/configured credential root makes the Codex rev
4457
4648
 
4458
4649
  agy is not a reviewer (person decision, 2026-10-19): it is an optional implementer peer (see [Optional Antigravity peer](#optional-antigravity-agy-peer)), and its retained reviewer slot is outside the default pool and always unavailable with `agy is an implementer peer (#485)`. A manual pool accepts only registered reviewer kinds (Claude, Codex, Kimi, agy), so local/Pi are refused. An explicit pool without declared candidates blocks for person action; an omitted pool retains legacy routing without declarations and falls back to an eligible planner when fresh candidates are exhausted. The exhausted pool walk remains recorded; without an eligible planner, the person reviews. A manual pool may name agy, or a Kimi reviewer whose preparation fails; the reason appears as an unavailable skip, and they never run or count as approval. Do not substitute plan mode for isolation.
4459
4650
 
4460
- A failed start or any child ending without a saved verdict advances to the remaining eligible candidates; no quota meaning is guessed from text. A request that went stale before its child started (a new head, review revision or reviewer) is skipped with one `hub.log` line; a still-current request whose review policy changed moves on as `no_verdict`. Neither marks the reviewer unavailable (#781). A child that ends without a verdict leaves one bounded `hub.log` line: its exit code and the hub tools it called, screened like any refusal, never tool arguments or model text. Its delivery completes only once the re-offer is confirmed; an unconfirmed re-offer stays `needs_review`. A manually named reviewer's no-verdict, and one with no declared pool, goes to the person, never to an agent the person did not choose (#453). A same-family fallback is a distinct fresh reviewer session. Exhaustion records `blocked: no reviewer can run`; with no explicit pool the eligible planner fallback is retained, while an explicit pool or absent eligible planner leaves person action. It never auto-approves. A per-review isolation preparation failure excludes that peer from later tasks until a verified restart. Its reason names one cause from a closed list (`version`, `binary`, `mcp_inventory`, `plugins_hooks_apps`, `sandbox_proof`, `credential_root`, `timeout`, `unknown`) with a fixed hint, on the peer's `reviewer` line in `ahub status` and in a `hub.log` line, and never a path, native CLI text or token (#722). Only the Codex preparation classifies its failures; other reviewers report `unknown`. Recovery targets must advertise `reviewer_pool` when a pool is explicitly configured or fresh reviewer instances are declared. Verdict history retains `reviewContext` and adds `reviewerFamily` and `reviewerReason` from the host.
4651
+ A failed start or any child ending without a saved verdict advances to the remaining eligible candidates; no quota meaning is guessed from text. A request that went stale before its child started (a new head, review revision or reviewer) is skipped with one `hub.log` line; a still-current request whose review policy changed moves on as `no_verdict`. Neither marks the reviewer unavailable (#781). A child that ends without a verdict leaves one bounded `hub.log` line: its exit code and the hub tools it called, screened like any refusal, never tool arguments or model text. Its delivery completes only once the re-offer is confirmed; an unconfirmed re-offer stays `needs_review`. A manually named reviewer's no-verdict, and one with no declared pool, goes to the person, never to an agent the person did not choose (task 453). A same-family fallback is a distinct fresh reviewer session. Exhaustion records `blocked: no reviewer can run`; with no explicit pool the eligible planner fallback is retained, while an explicit pool or absent eligible planner leaves person action. It never auto-approves. A per-review isolation preparation failure excludes that peer from later tasks until a verified restart. Its reason names one cause from a closed list (`version`, `binary`, `mcp_inventory`, `plugins_hooks_apps`, `sandbox_proof`, `credential_root`, `timeout`, `unknown`) with a fixed hint, on the peer's `reviewer` line in `ahub status` and in a `hub.log` line, and never a path, native CLI text or token (#722). Only the Codex preparation classifies its failures; other reviewers report `unknown`. Recovery targets must advertise `reviewer_pool` when a pool is explicitly configured or fresh reviewer instances are declared. Verdict history retains `reviewContext` and adds `reviewerFamily` and `reviewerReason` from the host.
4461
4652
 
4462
- Fake-CLI tests and CI cover these host contracts. [Offline isolation evidence](verification/2026-10-19-reviewer-isolation.md) and fake-CLI tests do not prove an account/model review or observed network denial. Native acceptance remains a separate [manual smoke leg](smoke.md#reviewer-pool-native-legs-519-2026-10-19); existing #223 Claude behavior has no new live proof here. Kimi's authenticated fresh-verdict receipts, and its still-open interactive-TUI coexistence and usage checks, are tracked in [Kimi smoke legs](smoke.md#kimi-reviewer-and-task-bound-mcp-533).
4653
+ Fake-CLI tests and CI cover these host contracts. [Offline isolation evidence](verification/2026-10-19-reviewer-isolation.md) and fake-CLI tests do not prove an account/model review or observed network denial. Native acceptance remains a separate [manual smoke leg](smoke.md#reviewer-pool-native-legs-519-2026-10-19); existing task 223 Claude behavior has no new live proof here. Kimi's authenticated fresh-verdict receipts, and its still-open interactive-TUI coexistence and usage checks, are tracked in [Kimi smoke legs](smoke.md#kimi-reviewer-and-task-bound-mcp-533).
4463
4654
 
4464
4655
  An additional implementer must have an existing registered Git worktree and
4465
4656
  attached branch at `.worktrees/<instance-id>`, named explicitly by `worktree` in
@@ -4498,13 +4689,13 @@ Run `ahub sdlc authorize-release <version>` from the running daemon's release-ca
4498
4689
 
4499
4690
  `ahub init --release-guard` installs a managed Git pre-push guard and release instructions (`--dry-run --json` previews them). It preserves an existing default hook as `pre-push.agent-hub-original` and passes original argv/stdin to it only after approval matches. Unknown or mismatched `v*` tag pushes fail closed, including when the daemon is down. Non-version branch pushes remain independent of the daemon. Custom `core.hooksPath` and linked/hardlinked hook files require manual integration. Re-run init after changing the Bun/package location. The person's `git push --no-verify` remains an explicit bypass; agents are instructed never to use it.
4500
4691
 
4501
- For `gh release create` and `npm publish`, use the exact literal command after `ahub sdlc release-guard --`. This preflight reads evidence and never executes the command. New GitHub tags require an explicit full commit target; alternate-repository, workspace/archive and bulk target forms refuse. GitHub/npm adapter enforcement is deferred to #312/#313. This consent record does not certify a tarball, native acceptance or live publication. Detailed limits: [S6 authorization](sdlc.md#person-release-authorization-481-s6).
4692
+ For `gh release create` and `npm publish`, use the exact literal command after `ahub sdlc release-guard --`. This preflight reads evidence and never executes the command. New GitHub tags require an explicit full commit target; alternate-repository, workspace/archive and bulk target forms refuse. GitHub/npm adapter enforcement is deferred to tasks 312/313. This consent record does not certify a tarball, native acceptance or live publication. Detailed limits: [S6 authorization](sdlc.md#person-release-authorization-481-s6).
4502
4693
 
4503
4694
  ## Planner release scheduling
4504
4695
 
4505
4696
  Use `ahub sdlc train report <release-id>` for scope readiness and recorded release metrics. The planner creates future release rows, activates one train, and assigns tasks through the release-only hub tools. A due date requests a cut review; it never clears unfinished work. Move missed tasks to the next row with a reason, then use `ahub sdlc train cut <release-id> <revision>` from the person's terminal or `hub_release_cut` as the planner. The cut retains a short host admission lease through freeze and defers on held gate slots or unknown/quota-limited peer windows with a real quota source. Accounts without a quota reader (Kimi included when `kimi_tokens_5h` is zero) report `no quota source`; manual and budget-record pauses still defer. It does not run release preparation or publish anything. Schedule bundle/check/docs work under separate heavy admission and retain the person's native verification, authorization and upgrade window. A hotfix records its enumerated cause and keeps every gate.
4506
4697
 
4507
- The per-release "gates run" record is not built: S7 shipped task metrics and bands without it, and release metrics with the gates-run record are the next slice. Person ruling 2026-10-19 supersedes the earlier S11 upgrade-admission ruling: upgrades use pre-S11 recovery readiness (no busy peers, pending approvals resolved, completion checks settled) and retain the existing operation checks. S11 adds no host-admission, quota or all-idle requirement to upgrade. An active/frozen configured train still requires recovery-target `release_trains` compatibility. Release-cut admission remains unchanged: follow its refusal's applicable exit by stopping or resuming the paused peer, adjusting `ahub budget set`, or using `ahub release drop`. Missing/shadowed `dot` requires the resource controller or a PATH correction for the cut; nested admission requires retrying the cut outside the outer lease.
4698
+ The per-release "gates run" record is not built: it needs gate-execution evidence the S6 authorization path would supply, and a cut record is not that evidence. Release interval, approval-to-release lead time and hotfix rate are shown by the release report (#442). Person ruling 2026-10-19 supersedes the earlier S11 upgrade-admission ruling: upgrades use pre-S11 recovery readiness (no busy peers, pending approvals resolved, completion checks settled) and retain the existing operation checks. S11 adds no host-admission, quota or all-idle requirement to upgrade. An active/frozen configured train still requires recovery-target `release_trains` compatibility. Release-cut admission remains unchanged: follow its refusal's applicable exit by stopping or resuming the paused peer, adjusting `ahub budget set`, or using `ahub release drop`. Missing/shadowed `dot` requires the resource controller or a PATH correction for the cut; nested admission requires retrying the cut outside the outer lease.
4508
4699
 
4509
4700
 
4510
4701
  ## Codex cancellation identity notice
@@ -4522,7 +4713,24 @@ The design for planner, implementer and reviewer instances per stage is in the s
4522
4713
  - 525-4: a headless Claude implementer runs one owned child per turn in its declared worktree, with a separate persisted session per task (`start_mode: "headless"`, permission `ask` or `ask-when-needed`; `never-ask` refused).
4523
4714
  - 525-5: terminal Claude implementer instances own write-class tasks once the daemon has verified their execution root.
4524
4715
 
4525
- The operator steps for 525-1, 525-4 and 525-5 are in "Additional headless peer instances" above. Slice 525-2 adds the planner settings, task/release bindings, independent plan review and recovery floor described in "The planner" above. Slice 525-6 (Codex planner-only visibility) remains separate.
4716
+ For a Codex planner, put a declaration such as the following in the untracked `.agenthub/config.local.json`, then restart the daemon. A planner instance holds `planner` and neither `implementer` nor `verifier`; adding `reviewer` does not grant code-write ownership. It uses the project checkout, so `worktree` is refused.
4717
+
4718
+ ```json
4719
+ {
4720
+ "instances": {
4721
+ "codex-plan": { "kind": "codex", "roles": ["planner"], "start_mode": "headless" }
4722
+ },
4723
+ "planner": ["claude", "codex-plan"]
4724
+ }
4725
+ ```
4726
+
4727
+ Run `ahub codex --as codex-plan` from the person's terminal. The hub opens the app-server proxy without creating a synthetic host thread. Each native `thread/start` or `thread/resume` is requested read-only. The host rechecks the current settled roles when each thread is admitted and before hub injection, so changing a running instance from a continuing reviewer to a planner cannot reuse an unconfirmed writable thread. A live planner transition with a declared writing worktree is refused until the declaration is corrected and the daemon restarted. A reply that does not positively confirm read-only refuses that RPC and closes the unconfirmed link before the hub adopts the thread or delivers work; missing or malformed evidence is not read-only. The attached TUI belongs to the person and can change its native sandbox later: this is an initial thread ceiling, not a claim that the hub confines the person's TUI. A planner can own `plan` work but cannot become the code owner without the implementer role.
4728
+
4729
+ A planner-only Claude instance stays a channel instance on the project checkout, with no `worktree` or headless form. Its facts hook gives no automatic grant for mutating file tools or shell, whatever the hub permission mode. The daemon also returns `ask` for those calls in the existing grant reply field, protecting older hooks that do not recognize the added ceiling flag; the person's runtime mode itself is unchanged. A native choice in that person's terminal remains the person's; the builtin `claude` keeps its existing behavior.
4730
+
4731
+ Inspect the running holders with `ahub status` and the dashboard's Agents view. Each peer's `roleInfo` includes its running `form`, `capableRoles` and effective `flowRoles`; a planner holder also names its planner source. `form` distinguishes a channel, headless variant, terminal implementer or builtin form. Capability describes the allowed ceiling, not an extra assigned role. Each declared extra shows owner isolation as `isolated` or `not isolated: <reason>`; a planner using the canonical checkout is not an isolated write owner by design. Missing proof never counts as isolation. The isolation fields and status stall projection are cached display evidence; they may still show the last verified root before an effect point checks a filesystem change. Actual assignment, facts grants and recovery never authorize from this display cache.
4732
+
4733
+ Use `ahub task show <id>`, the console Tasks panel and the dashboard task detail to inspect planner, owner and reviewer with their recorded reasons. `ahub task show <id> --json` and `ui_snapshot` carry `planner` and `plannerReason` as additive fields; a legacy null planner stays unbound. Compare the holder walk with `ahub route explain <id>`. Use `ahub task assign <id> <peer> --role planner` from the person's terminal to name another eligible holder without moving owner or reviewer. Planner settings, bindings, independent plan review and the recovery floor are described in "The planner" above; implementer/reviewer setup is in "Additional headless peer instances". The [six-instance person smoke leg](smoke.md#six-role-instances-525-6) remains unverified until it is actually run.
4526
4734
 
4527
4735
  ## Daemon responsiveness and launch connects (#719)
4528
4736
 
@@ -4532,7 +4740,7 @@ Native launcher connections retry transport timeouts/unclean closes with bounded
4532
4740
 
4533
4741
  ## amem runtime (agent-mem#13 P4)
4534
4742
 
4535
- The hub exact-pins @staix/agent-mem 0.11.0 for recall, curated evidence and local deterministic capture. No runtime call reaches a claude-mem worker; `memory.worker_url` is ignored. The person's `ahub memory import claude-mem` remains explicit and off by default. Existing profile, alias, PII and private-turn fences still apply. `ask --remember` and context checkpoints propose machine-local candidates, never automatic curated evidence. See [memory integration](memory.md#in-process-recall-and-capture-agent-mem13-p4) for changed behavior and bounded-view ceilings.
4743
+ The hub exact-pins @staix/agent-mem 0.13.0 for recall, curated evidence and local deterministic capture. No runtime call reaches a claude-mem worker; `memory.worker_url` is ignored. The person's `ahub memory import claude-mem` remains explicit and off by default. Existing profile, alias, PII and private-turn fences still apply. `ask --remember` and context checkpoints propose machine-local candidates, never automatic curated evidence. See [memory integration](memory.md#in-process-recall-and-capture-agent-mem13-p4) for changed behavior and bounded-view ceilings.
4536
4744
 
4537
4745
  ## Backlog issue identity and notes
4538
4746
 
@@ -4564,7 +4772,7 @@ Optional code work runs only while Memory is open and visible. A cold console co
4564
4772
 
4565
4773
  ## Authority by command
4566
4774
 
4567
- ### Conductor operations (#812 T2)
4775
+ ### Conductor operations (#812 T2, #933, #900)
4568
4776
 
4569
4777
  The selected project's configured conductor may run the commands below through its ordinary peer identity. Other peers retain the guidance "the person runs it in ahub console or a terminal". A missing or dead descriptor refuses promptly. For a live descriptor the connect and local authorization handshakes each keep a 3-second bound; connection unavailability is not presented as proof that the peer lacks the conductor role. Actual authority refusals are audited as refused, while ordinary operation failures retain their original cause. The daemon checks the authenticated connection, current role and project membership before effects and after asynchronous boundaries. Roster handoffs carry that same authority check through plan admission, owner delivery and each later task; a saved assignment is never rolled back when delivery is interrupted. Effectful operations and operational history reads retain an `agent_cli` admission with the initiating peer; audit failure refuses those operations. Authorization negotiation, `settings_preview` and `queue show` still check current authority but emit no extra admission record or conductor notice. Admission records are not a unique invocation counter.
4570
4778
 
@@ -4573,15 +4781,355 @@ The selected project's configured conductor may run the commands below through i
4573
4781
  | roster add/remove | Existing roster lifecycle and confirmations; no role or permission-mode grant |
4574
4782
  | stop peer | Existing hub-owned peer stop, ownership and recovery guards |
4575
4783
  | queue show/resolve/discard | Public queue projection and existing revision/confirmation guards |
4576
- | task cancel | Existing hub_task_cancel with the real peer; eligibility follows the task-board policy |
4577
- | doctor --orphans --kill | Selected project's positively identified hub-owned process only; no cross-project registrations or unsigned process files |
4784
+ | board import / intake approve | Existing import and intake admission; the actual conductor remains the actor |
4785
+ | schedule run/pause/resume | Existing machine-local schedule controls; no role or policy changes |
4786
+ | research backfill / bench run / source add / goal link | Existing bounded CLI or daemon paths, with fixed audit labels |
4787
+ | start Jcode | Launcher-only peer the conductor is configured to start |
4788
+ | task unassign <id> [--role owner\|reviewer] [--revision N] | The actual conductor is recorded; no replacement is routed and no native work is stopped |
4789
+ | restart --yes | Only with a source-issued continuation proof for this project's own operation |
4790
+ | upgrade --yes --to <exact-version> | Only with proof for this project's supported exact-version upgrade; newer coordinator handoff is person-only |
4791
+ | recovery resume\|abort [<op-id>] | Only the initiating conductor's own restart/authorized-upgrade operation; the person keeps the normal recovery path |
4792
+ | doctor --orphans --kill | Selected project's positively identified hub-owned process only; no cross-project registrations or unsigned process files. Prints the stopped pids for a person; `--json` prints the hub's reply |
4578
4793
  | logs / export | Bounded daemon projections, with unsupported/private records withheld; logs -f ends when authority ends |
4579
- | settings set | Only the twelve existing live registry keys listed below |
4794
+ | settings set | Only the thirteen existing live registry keys listed below |
4580
4795
 
4581
- The exact settings keys are `task_sweep.enabled`, `task_sweep.auto_reassign`, `task_sweep.no_writes_turns`, `task_sweep.interval_s`, `task_sweep.unaccepted_min`, `task_sweep.idle_min`, `task_sweep.review_min`, `task_sweep.ready_min`, `task_sweep.ladder_min`, `snapshots.enabled`, `research.enabled` and `approvals.notify`. New keys receive no authority by prefix. Generic roster setting writes still use the dedicated roster lifecycle. Conductor setting records name the initiating peer, and conductor writes preserve the person's pending undo record. An intervening write can still invalidate that undo through the existing file-digest guard; preserving the record does not bypass that guard. Permission modes, roles, `pet.window`, arbitrary configuration reload and undo remain person commands.
4796
+ The exact settings keys are `task_sweep.enabled`, `task_sweep.auto_reassign`, `task_sweep.no_writes_turns`, `task_sweep.interval_s`, `task_sweep.unaccepted_min`, `task_sweep.idle_min`, `task_sweep.review_min`, `task_sweep.ready_min`, `task_sweep.ladder_min`, `snapshots.enabled`, `research.enabled`, `approvals.notify` and `tasks.escalate_after`. New keys receive no authority by prefix. Generic roster setting writes still use the dedicated roster lifecycle. Conductor setting records name the initiating peer, and conductor writes preserve the person's pending undo record. An intervening write can still invalidate that undo through the existing file-digest guard; preserving the record does not bypass that guard. Permission modes, roles, `pet.window`, `models start|stop`, arbitrary configuration reload and undo remain person commands. `task cancel` remains a separate own-task `hub_task_cancel` permission, not a conductor operation.
4582
4797
 
4583
4798
  Log history is limited to the last 100 retained lines and export to the last 1000 retained events, each within a 128 KiB read. Truncation is explicit. An incomplete public projection reports one bounded reason on stderr, preserving export JSON on stdout. The complete privacy inventory has no 500-task cutoff and is cached until board or privacy/config revisions change, so log-follow does not rebuild it for every line. Export accepts closed metadata shapes; unknown fields and nested records are withheld rather than copied. Task titles, paths and private delivery bodies are not part of this projection. It does not change the person's ordinary logs/export output.
4584
4799
 
4585
4800
  Orphan cleanup requires a missing selected root, signed process identity and the host-known hub executable/entrypoint and argv. An inaccessible root or unavailable proof remains unknown; there is no offline config-based role grant. The daemon rechecks authority and process ownership before signals. A confirmed self candidate uses the existing lifecycle stop after its reply. A roster write already committed before authority ends is acknowledged as saved, without authorizing further lifecycle work or commands.
4586
4801
 
4587
- `restart --yes` and `recovery resume/abort` await a project-scoped continuation proof. `task unassign` follows #481, and legacy models start/stop awaits a project that needs it. This slice adds no task eligibility rule and no MLX/recovery-driver changes. Person authority and the separately authorized `--live-test` path retain their existing behavior.
4802
+ ### Conductor-bound restart and recovery continuation (task 535, #900)
4803
+
4804
+ The configured conductor may start a restart with `ahub restart --yes` or an
4805
+ upgrade with `ahub upgrade --yes --to <exact-version>` only after the source hub
4806
+ issues a bounded, fixed-shape private continuation record for that exact operation. The record is
4807
+ separate from public recovery JSON and its `delegatedAudits` history. A
4808
+ source-issued HMAC binds the operation id, initiating peer, project id and
4809
+ canonical root, source daemon instance, target root and package bytes, plan
4810
+ fingerprint, and a version-neutral digest of bounded source inputs and fresh
4811
+ actor/privacy evidence. A public operation entry or audit label alone grants
4812
+ nothing. The digest covers raw config-layer and routing-file texts, plus the
4813
+ source-resolved public routing template bytes when no project routing file
4814
+ exists; it does not hash expanded defaults, default roles or parsed routing.
4815
+ The public `conductorRoutingDefaults` map is advisory; the target inherits the
4816
+ sealed source fallback. The private record stores digests, not raw config,
4817
+ routing contents or the HMAC key.
4818
+
4819
+ Every planned project is preflighted for source recovery support and current
4820
+ authority before staging begins. An upgrade then stages and verifies its exact
4821
+ target; restart has no package stage. The source issues the private HMAC record
4822
+ after the target bytes and feature floor are verified, before publishing the
4823
+ public operation receipt, taking the lock or applying runtime holds. A source
4824
+ preflight refusal prevents staging; an unsupported staged target or failed
4825
+ proof prevents the public operation and its holds. A target requiring handoff
4826
+ to a newer coordinator remains person-only. At every effect boundary, the host
4827
+ rechecks the same peer's live conductor role and roster membership, project
4828
+ authority, read-only status and current non-PII/privacy policy. Offline
4829
+ continuation uses only bounded current actor/privacy checks; unrelated public
4830
+ board writes do not invalidate it. Changed or unreadable authority evidence
4831
+ stops the next effect and leaves the operation for the person.
4832
+
4833
+ An upgrade digest includes the exact staged `@staix/agent-mem` package from its
4834
+ hoisted install; a restart digest includes its verified source dependency.
4835
+ Protected or restart-bound source config/routing edits that have not been
4836
+ accepted remain person-only. The replacement validates the captured inputs
4837
+ before adapter imports and uses the sealed source fallback for startup routing.
4838
+
4839
+ Captured TUI terminal restoration remains person-only: a delayed native
4840
+ launcher cannot re-prove the conductor at its eventual spawn. Headless
4841
+ restoration uses the target-owned snapshot through a tools connection.
4842
+
4843
+ An unmanaged reconnect-only Claude channel may reattach itself without a native launch. The source keeps a SHA-256 fingerprint of its observed session identity separate from restoration metadata (the raw unmanaged session id is omitted from the plan) and verifies the exact peer, kind and attachment against the live channel. This exception requires no terminal or restoration arguments, no managed launch proof, and an absent or confirmed-ended launcher; unknown launcher evidence refuses. Other peer identities still compare exactly.
4844
+
4845
+
4846
+ Only the conductor that created the restart or authorized upgrade may run
4847
+ `recovery resume|abort` for that operation. A different conductor cannot take
4848
+ over; the person can use the ordinary recovery commands. The detached
4849
+ `recovery-run` and target launcher are internal continuations bound to the
4850
+ recorded operation and captured target arguments. They do not grant arbitrary
4851
+ console CLI commands. The existing `--live-test` path remains separate and does
4852
+ not create a conductor continuation.
4853
+
4854
+ `task unassign` runs as the conductor's own actor and does not route a
4855
+ replacement or stop native work. An owner unassign suppresses automatic owner
4856
+ offers until an authorized explicit assignment or reservation clears that
4857
+ suppression. This behavior has fixture coverage only; it does not establish a
4858
+ current installed-release or live-account receipt.
4859
+
4860
+ ## Standalone ciphertext broker (#446 X2)
4861
+
4862
+ From the operator's terminal, run `ahub broker serve --port 8788 --state-dir <private-directory>`.
4863
+ Default bind is 127.0.0.1; default storage is `broker/` under the selected hub state
4864
+ directory. Ctrl+C or SIGTERM closes the listener and SQLite handle together.
4865
+ Accepted ciphertext remains across restart. `GET http://127.0.0.1:8788/health`
4866
+ returns fixed version/status data.
4867
+
4868
+ For another machine, configure TLS termination and use
4869
+ `--public-url wss://relay.example/ --owner <owner-signing-fingerprint>`.
4870
+ Repeat `--owner` for each approved creator. Public WSS audiences, even on a loopback
4871
+ listener behind a proxy, require this allowlist. It controls new rooms only;
4872
+ existing member revocation still uses the owner's signed roster. Omitting the
4873
+ allowlist is allowed only in the loopback/no-public-audience development mode;
4874
+ do not expose that open mode through a proxy.
4875
+ The audience must be a canonical root URL without credentials, query or fragment.
4876
+ Proxy `/v1/rooms/<room>` as binary WebSocket, preserve Origin and expose WSS only.
4877
+ A non-loopback bind also needs `--hostname <address>` and an explicit WSS URL.
4878
+ The command configures no proxy, certificate, firewall or hosting account; live
4879
+ TLS and two-machine operation need their own operator receipts.
4880
+
4881
+ Keep `broker.db` and its directory outside version control. There is one process
4882
+ per database, enforced by SQLite's kernel lock; a second instance reports busy.
4883
+ A crash releases that lock. Do not delete the database as lock recovery: owner/
4884
+ epoch pins protect membership. Empty/corrupt/unsupported state refuses rather than
4885
+ forgetting pins. Restore a known valid backup or arrange a fresh room lifecycle
4886
+ explicitly; X3 clients must retain their own owner/epoch/replay state.
4887
+
4888
+ The first room connection proves its signed roster's owner identity; later ones
4889
+ prove current membership. Room create/invite/join/expose and the hub link are X3;
4890
+ ordinary chat forwarding and local policy are X4. A stored or queue_ack packet is
4891
+ transport/storage only; application delivery needs the recipient's separate signed
4892
+ receipt. Queue-full/backpressure refuses new work without evicting accepted data.
4893
+ See the [protocol and caps](specs/2026-09-19-agent-hub-design.md#encrypted-rooms-x2-blind-broker-446).
4894
+
4895
+
4896
+ ### Reclaim one broker room
4897
+
4898
+ Stop the broker first, then run
4899
+ `ahub broker room drop <room> --yes --state-dir <private-directory>` from the
4900
+ operator's terminal. Online attempts report busy; agent markers and `--live-test`
4901
+ delegation refuse. The directory/database must already exist. This forgets only
4902
+ that room's pins, sender floors and pending ciphertext, freeing its slot; it keeps
4903
+ other rooms and their pins. It is a destructive room reset, not membership
4904
+ revocation, secure erasure or confirmation that pending messages were delivered.
4905
+ Clients must retain their own owner/epoch/replay state. Remove an unwanted owner
4906
+ from the startup allowlist before restarting so it cannot recreate the dropped id.
4907
+ Never delete the whole database to free one room.
4908
+
4909
+ Existing populated state directories must contain only recognized broker files
4910
+ and already be 0700. Other populated directories, including a checkout, refuse
4911
+ without chmod. Empty directories may be tightened. Schema v1 migrates to v2 while
4912
+ preserving signed pins, floors and queued ciphertext. The 4 MiB per-room admission
4913
+ share prevents one room taking the whole aggregate byte budget; valid older larger
4914
+ queues stay available but block new admission until drained or expired.
4915
+
4916
+
4917
+ ## Outbound room membership (#446 X3)
4918
+
4919
+ Run room mutations from the person's terminal, never an agent shell or `--live-test`.
4920
+ Start a self-hosted broker as described above, then create a room with
4921
+ `ahub room create team --broker wss://broker.example/`. The owner fingerprint must
4922
+ be on a public broker's `--owner` allowlist. `room list` prints that fingerprint.
4923
+ No room joins or exposure are loaded from committed configuration.
4924
+
4925
+ Exchange these artifacts privately out of band:
4926
+
4927
+ 1. Owner: `ahub room invite team`, send the invitation token to the joining person.
4928
+ 2. Joiner: `ahub room join <token> --name team`, send the printed request back.
4929
+ 3. Owner: `ahub room invite team --accept <request>`, send the printed signed roster.
4930
+ 4. Joiner: `ahub room join team --roster <signed-roster>`.
4931
+
4932
+ Both consoles print owner and member fingerprints before installing/consuming.
4933
+ Compare them out of band, then answer the TTY's y/N prompt (default No). A non-TTY
4934
+ must pass `--confirm <member-fingerprint-prefix>` with 16..64 matching lowercase
4935
+ hex characters; it does not execute on a missing or mismatched confirmation.
4936
+ This flag does not delegate to an agent or bypass the `--live-test` refusal. Only the invitation token contains its one-time secret.
4937
+ The join request/roster contain no private key or invitation secret. Keep tokens
4938
+ out of shell transcripts, shared logs and source control. A saved pending join can
4939
+ be retried with the same token/name/label; `room leave team` cancels it if needed.
4940
+
4941
+ `room expose team codex` sets the untracked local exposure policy. On a non-owner
4942
+ it prints a signed exposure request: send it through the same owner `invite --accept`
4943
+ and joiner `join --roster` round trip. Empty exposure is the default. `room status`
4944
+ shows ids and closed states. The X4 chat workflow is below.
4945
+
4946
+ `room revoke team <fingerprint>` rotates the owner roster, giving no new key to the
4947
+ removed member. `room leave team` disconnects locally and retains replay/pin state;
4948
+ ask the owner to revoke membership as well. Hub shutdown closes links but keeps
4949
+ membership. Never delete rooms.db to repair a pin or sequence failure. Unknown or
4950
+ corrupt state disables rooms, while local hub work remains available. Real TLS,
4951
+ proxy forwarding and two-machine smoke remain unverified until the person runs X7.
4952
+
4953
+ ## Queue backpressure on automatic task offers (#800)
4954
+
4955
+ WIP and a peer's waiting messages measure different constraints. Backpressure is
4956
+ off by default. A person may opt in through untracked local settings:
4957
+
4958
+ ```sh
4959
+ ahub settings set tasks.queue_backpressure.max_queued 20
4960
+ ahub settings set tasks.queue_backpressure.max_oldest_s 600
4961
+ ```
4962
+
4963
+ The bounds are 10,000 messages and seven days; zero disables that threshold.
4964
+ Values strictly above a threshold hold new automatic owner/reviewer offers.
4965
+ `ahub route explain <task>` uses the actual assignment rule. `ahub status` shows
4966
+ queued count, oldest age and a fixed hold reason separately from active,
4967
+ review-wait and reserved WIP. Unknown age is shown as unknown and holds an offer
4968
+ when the age threshold is on. Existing queued work still drains normally.
4969
+
4970
+ After enabling age policy, old or recovered entries can lack host arrival proof;
4971
+ resaving/restarting does not make them fresh. Drain them or explicitly assign the
4972
+ particular task as the person/conductor. That override applies only to the named
4973
+ role, not other automatic choices, and does not waive privacy/independence/runtime
4974
+ admission. Automatic reserved planner pulls are still guarded.
4975
+
4976
+ Queue-origin holds are retried after committed queue/state/policy changes, without
4977
+ routing work a person explicitly unassigned. Automatic decline/budget reroutes
4978
+ retain their retry intent across planning updates; a named proposal keeps its
4979
+ target. A person reservation waits under pressure rather than moving to another
4980
+ peer. Self-claims enqueue no owner request and skip only that queue guard.
4981
+ Continuing reviewers remain bound unless their own queue refuses the new offer.
4982
+ Setting both values to zero returns
4983
+ to ordinary automatic assignment. This PR changes no live repository setting or
4984
+ routing template; real-use adoption metrics remain separate from fixture checks.
4985
+
4986
+
4987
+ ## Screened room chat (#446 X4)
4988
+
4989
+ After joining and confirming fingerprints, expose each receiving native peer with
4990
+ `ahub room expose team codex`. A non-owner returns the signed exposure request to
4991
+ the owner and installs the resulting roster as described above. Exposure is empty
4992
+ by default; this repository enables no room or exposure automatically.
4993
+
4994
+ `ahub room status team` lists remote addresses such as `codex@hub-guest`. Send to
4995
+ one explicitly:
4996
+
4997
+ ```sh
4998
+ ahub say @codex@hub-guest "Please inspect the public example"
4999
+ ```
5000
+
5001
+ A local exposed agent can use `hub_send` with that exact address. Native peers
5002
+ can exchange round-trips. X5b below adds the person's durable inbox for replies
5003
+ to `user@label`. An older X4-only receiver returns signed `failed_safe` with
5004
+ "person inbox not supported yet (X5)", rather than claiming admission. One remote plus
5005
+ local recipients is supported. X4 refuses multiple remote recipients before any
5006
+ delivery: send separately. Ordinary broadcasts stay local. Unknown or ambiguous
5007
+ addresses refuse. Private turns, PII task references and private envelopes do not
5008
+ cross; secret/path/pattern/model-screen failures produce an ids-only stub.
5009
+
5010
+ Room status and `ahub queue show <delivery-id>` distinguish accepted ciphertext
5011
+ from **completed (admitted)**, meaning the remote hub saved the chat to its local
5012
+ peer queue. Native/model execution is independent. Incoming messages remain
5013
+ untrusted text and grant no local task, role, approval, tool or control authority.
5014
+
5015
+ Offline queues are capped at 200 per room. In untracked config.local.json, optional
5016
+ `rooms.ack_timeout_s` defaults to 600 and `rooms.queue_ttl_h` to 24. A restart,
5017
+ expired deadline or uncertain publication leaves `needs_review`. It is not
5018
+ replayed automatically. A late signed receipt is visible as evidence in queue
5019
+ inspection; the person's disposition is still required. Old text cannot be
5020
+ retried to a replacement fingerprint reusing a label. An unproved terminal retry
5021
+ refuses and requires a deliberately authored new message.
5022
+ An expired or missing original enqueue deadline likewise refuses retry without
5023
+ recording a successful disposition. Send a new message deliberately instead.
5024
+
5025
+ The ACK timeout starts when the application is handed to the room link, even if
5026
+ the broker never confirms storage. A reconnect or restart requests fresh signed
5027
+ presence from the other hub without waiting for its peer state to change.
5028
+ Capacity refusal of one incoming chat retains that pending chat while allowing
5029
+ later packets and receipts through; a later online or person-disposition event
5030
+ rechecks admission. Merely reading room status applies no disposition.
5031
+ Up to eight matching receipt payloads per room have a separate 1,024-byte-per-row
5032
+ durable reserve so a full chat inbox can still settle outstanding deliveries.
5033
+ The normal chat cap is unchanged; unrelated receipts cannot use that reserve.
5034
+ Presence retired at the ordinary cap is re-fetched once after capacity frees,
5035
+ so that a dropped state update does not leave the remote view stale indefinitely.
5036
+ Fresh authenticated chat that reaches the full ordinary inbox is refused and
5037
+ transport-ACKed without admission or a signed receipt. The sender's application
5038
+ expires to `needs_review`; receipt traffic behind the refused chat continues.
5039
+
5040
+ For an incoming uncertain admission, copy its frame id from room status:
5041
+
5042
+ ```sh
5043
+ ahub room resolve team --frame FRAME_DIGEST --action accept
5044
+ ahub room resolve team --frame FRAME_DIGEST --action discard
5045
+ ```
5046
+
5047
+ Both forms show the owner/member fingerprints before asking y/N. Non-TTY use
5048
+ requires `--confirm` with a matching member fingerprint prefix. Accept records
5049
+ that the person vouches the local peer got it; discard records dropping it.
5050
+ Neither sends, re-admits, or produces a signed completion. The displayed state is
5051
+ **disposed (accept|discard, by person)**. These mutations refuse agent shells and
5052
+ `--live-test`, including after leaving a room.
5053
+
5054
+ Application-id and trace metadata retain 30 days/10,000 per room, evicting settled
5055
+ rows first. Protected uncertainty remains until person disposition. Outside that
5056
+ retention no historical app-id or trace claim is made; permanent seq floors and
5057
+ MAX_HOP 3 still apply. Status prioritizes uncertainty and bounds each direction's
5058
+ rows to 200; queue inspection remains the delivery-history entry point. Broker,
5059
+ TLS/proxy and real two-machine/account receipts remain separate live proof.
5060
+
5061
+ ## Room metadata views (#446 X5)
5062
+
5063
+ Press `8` in the panel console for Rooms, or open Agents > Rooms in the dashboard.
5064
+ The views show local room membership, owner/self fingerprints, member count,
5065
+ link state and exposed remote peer ids/states. They are hidden when an older hub
5066
+ supplies no room field or no rooms are configured. An unavailable read clears
5067
+ old metadata and shows an unavailable state. Reading status creates no room
5068
+ storage or keys.
5069
+
5070
+ Relay figures are **since daemon startup**, not lifetime totals: outbound is
5071
+ successful handoffs, inbound is confirmed local admissions, and withheld is a
5072
+ subset of outbound handoffs. Unknown values display as unknown, not zero. A
5073
+ daemon restart resets the counters; neither a handoff nor these totals means a
5074
+ native agent completed its work.
5075
+
5076
+ These views contain no message body/title, reason, broker URL, key material or delivery
5077
+ history. Use the existing queue inspection workflow for delivery uncertainty.
5078
+ The metadata-only views never show inbox bodies; use the separate person-only
5079
+ CLI below.
5080
+
5081
+ ## Person-only room inbox (#446 X5b)
5082
+
5083
+ List metadata for all rooms or one named room:
5084
+
5085
+ ```sh
5086
+ ahub room inbox [<name>] [--json]
5087
+ ```
5088
+
5089
+ The list contains local inbox ids, room/sender metadata, times, byte counts and
5090
+ states, never body previews. One operand is always a room name, even `show`,
5091
+ `done` or `discard`. Only an action plus an exact local UUID acts on a message:
5092
+
5093
+ ```sh
5094
+ ahub room inbox show <id>
5095
+ ahub room inbox done <id>
5096
+ ahub room inbox discard <id>
5097
+ ```
5098
+
5099
+ Only list accepts `--json`; none of these commands accepts `--confirm` or asks
5100
+ for a fingerprint. All inbox forms refuse agent shells, conductors and
5101
+ `--live-test`. They also refuse during recovery or a Bus hold. Show prints one
5102
+ sanitized, terminal-safe untrusted body and does not mark it done. Done/discard
5103
+ immediately purge the body and keep a metadata stub. Their acknowledgement is
5104
+ sent before any background room retry. Expiry does the same;
5105
+ there is no archive or read-after-done command. Capacity released by done/discard
5106
+ or a real TTL purge wakes already-deferred X4 traffic; no new receipt is sent for
5107
+ the purged entry, and idle ticks never poll queued plaintext. Dashboard, status, logs, memory,
5108
+ models and observers never receive the body.
5109
+
5110
+ The sender's **completed (inbox)** receipt means durable admission, not that the
5111
+ person read or completed the request. Reading, disposing or expiring the local
5112
+ copy never changes that receipt. Inbound session counts increment once at
5113
+ admission. Older X4 senders cannot parse the new inbox receipt reasons and stay
5114
+ uncertain; do not treat mixed-version traffic as verified success.
5115
+
5116
+ Limits are 100 unread messages per room, 64 KiB decoded body per message and
5117
+ 4 MiB canonical payload bytes retained globally, including JSON overhead and
5118
+ escaping (the displayed size remains decoded UTF-8 body bytes). The unchanged 64 KiB entire-wire cap
5119
+ leaves less than 64 KiB for a sendable body. Schema 4 starts the 30-day TTL at the
5120
+ first durable local crypto arrival, not remote time or restart. Legacy pending
5121
+ person traffic without that age gets fixed `failed_safe/inbox_age_unavailable`;
5122
+ native peer traffic is unchanged. Full, oversized or already-expired person
5123
+ admissions return fixed `inbox_full`, `inbox_oversize` or `inbox_expired` refusals.
5124
+
5125
+ Up to 1,000 settled stubs per room remain for 30 days after settlement, oldest
5126
+ first. Unread messages and their admission identities are protected, along with
5127
+ unsettled identities at the shared 10,000-per-room application cap. One loaded
5128
+ daemon expiry hook runs every 30 seconds, including for left rooms; it creates
5129
+ no empty room database or keys. Bodies remain plaintext in the protected local
5130
+ store, with no new encryption-at-rest or secure-erasure guarantee.
5131
+
5132
+ Lists are complete up to 1,100 rows. If an all-room list exceeds that limit, the
5133
+ CLI explicitly asks you to choose a room name; it never silently truncates.
5134
+ ponytail: pagination is later work; a filtered room's 100 unread plus 1,000
5135
+ settled stubs fit the current response ceiling.