@staix/agent-hub 0.29.0 → 0.31.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 (154) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.md +15 -7
  3. package/docs/agent-notes/README.md +13 -0
  4. package/docs/agent-notes/adapters.md +7 -3
  5. package/docs/agent-notes/budget.md +1 -0
  6. package/docs/agent-notes/bus.md +10 -4
  7. package/docs/agent-notes/daemon.md +16 -8
  8. package/docs/agent-notes/local-worker.md +1 -0
  9. package/docs/agent-notes/tasks.md +10 -4
  10. package/docs/agent-notes/tests.md +8 -4
  11. package/docs/design/adapters.md +108 -0
  12. package/docs/design/agent-hub-architecture.md +58 -0
  13. package/docs/design/bus.md +147 -1
  14. package/docs/design/task-board.md +100 -0
  15. package/docs/dogfood.md +29 -8
  16. package/docs/events.md +3 -0
  17. package/docs/memory.md +17 -1
  18. package/docs/operations.md +410 -86
  19. package/docs/profiles.md +35 -0
  20. package/docs/quickstart.md +25 -5
  21. package/docs/sdlc.md +1 -1
  22. package/docs/security.md +182 -62
  23. package/docs/smoke.md +13 -2
  24. package/docs/specs/2026-09-19-agent-hub-design.md +84 -14
  25. package/docs/specs/2026-09-19-multi-project-design.md +7 -2
  26. package/docs/specs/2026-10-12-office-view-design.md +86 -3
  27. package/docs/verification/2026-11-03-0.30.0.md +54 -0
  28. package/docs/verification/2026-11-03-agy-1.4.12-launcher-nonce.md +42 -0
  29. package/docs/verification/2026-11-03-agy-1.4.12.md +90 -0
  30. package/docs/verification/2026-11-03-agy-tui-close.md +49 -0
  31. package/docs/verification/2026-11-03-grok-1.0.62-t0.md +150 -0
  32. package/docs/verification/2026-11-03-pi-150-compat.md +40 -0
  33. package/docs/verification/2026-11-03-pi-title-api.md +26 -0
  34. package/docs/verification/2026-11-03-qwen-0265-acp.md +55 -0
  35. package/docs/verification/2026-11-03-qwen-0265-dual-output.md +40 -0
  36. package/docs/verification/2026-11-04-1171-channel-client-evidence.md +31 -0
  37. package/docs/verification/2026-11-05-0.31.0.md +182 -0
  38. package/docs/verification/2026-11-05-claude-implementer-2.1.318.md +70 -0
  39. package/docs/verification/2026-11-05-hardcoded-values-classification-rules.md +257 -0
  40. package/docs/verification/2026-11-05-hardcoded-values-ratchet.md +55 -0
  41. package/docs/verification/2026-11-06-rooms-presence-cost.md +152 -0
  42. package/docs/verification/claude-shell-wrapper-native.py +1 -1
  43. package/docs/verification/dogfood/2026-11-03.md +3095 -0
  44. package/docs/verification/pi-150-model-free.ts +57 -0
  45. package/docs/verification/pi-150-native.mjs +21 -0
  46. package/docs/verification/pi-150-title-api.mjs +31 -0
  47. package/docs/verified.json +14 -14
  48. package/package.json +3 -4
  49. package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
  50. package/plugins/agent-hub/server.js +4565 -3617
  51. package/src/adapters/acp.ts +5 -4
  52. package/src/adapters/channel-peer.ts +72 -3
  53. package/src/adapters/claude-channel-evidence.ts +52 -0
  54. package/src/adapters/claude-channel.ts +61 -18
  55. package/src/adapters/claude-implementer.ts +29 -7
  56. package/src/adapters/claude-print-child.ts +12 -2
  57. package/src/adapters/codex-appserver.ts +13 -12
  58. package/src/adapters/compat.ts +13 -13
  59. package/src/adapters/definitions/builtin-metadata.ts +13 -13
  60. package/src/adapters/definitions/qwen.ts +1 -0
  61. package/src/adapters/local-worker.ts +82 -23
  62. package/src/adapters/pi.ts +140 -16
  63. package/src/adapters/registry-types.ts +16 -0
  64. package/src/adapters/registry.ts +4 -0
  65. package/src/adapters/reviewer-print.ts +50 -11
  66. package/src/broker/server.ts +44 -13
  67. package/src/broker/store.ts +38 -8
  68. package/src/cli/answer-args.ts +50 -0
  69. package/src/cli/attention.ts +17 -2
  70. package/src/cli/autonomy.ts +66 -1
  71. package/src/cli/backlog-return-args.ts +37 -0
  72. package/src/cli/console-question.ts +110 -0
  73. package/src/cli/console-state.ts +82 -17
  74. package/src/cli/console.ts +54 -1
  75. package/src/cli/handoff-args.ts +15 -5
  76. package/src/cli/handoff-output.ts +15 -2
  77. package/src/cli/help.ts +10 -5
  78. package/src/cli/identity.ts +21 -4
  79. package/src/cli/main.ts +167 -16
  80. package/src/cli/memory.ts +107 -3
  81. package/src/cli/output.ts +29 -6
  82. package/src/cli/pet.ts +4 -4
  83. package/src/cli/results-args.ts +30 -0
  84. package/src/cli/runtime-version-warning.ts +50 -0
  85. package/src/cli/status-lines.ts +41 -1
  86. package/src/cli/tail-render.ts +1 -0
  87. package/src/cli/upgrade-interactive.ts +14 -2
  88. package/src/cli/worktree-tidy.ts +158 -0
  89. package/src/cli/worktrees-output.ts +43 -0
  90. package/src/hub/action-class.ts +9 -1
  91. package/src/hub/board.ts +396 -20
  92. package/src/hub/bus.ts +497 -57
  93. package/src/hub/completion-detail.ts +50 -0
  94. package/src/hub/completions-store.ts +168 -0
  95. package/src/hub/completions.ts +120 -0
  96. package/src/hub/conductor.ts +4 -3
  97. package/src/hub/config-apply.ts +8 -5
  98. package/src/hub/config-reload.ts +10 -1
  99. package/src/hub/control-client.ts +40 -4
  100. package/src/hub/daemon.ts +1795 -142
  101. package/src/hub/delivery-failure.ts +14 -0
  102. package/src/hub/delivery-hints.ts +14 -4
  103. package/src/hub/delivery-journal.ts +751 -42
  104. package/src/hub/delivery-superseded.ts +4 -1
  105. package/src/hub/dogfood-read.ts +62 -9
  106. package/src/hub/envelope.ts +3 -3
  107. package/src/hub/events.ts +6 -0
  108. package/src/hub/handoff.ts +145 -6
  109. package/src/hub/hub-tools.ts +66 -11
  110. package/src/hub/manager.ts +67 -3
  111. package/src/hub/memory-policy.ts +44 -1
  112. package/src/hub/memory-sync-policy.ts +109 -0
  113. package/src/hub/memory-sync-state.ts +145 -0
  114. package/src/hub/no-writes.ts +1 -1
  115. package/src/hub/operational-settings.ts +11 -2
  116. package/src/hub/operational-view.ts +12 -1
  117. package/src/hub/peer-doing.ts +33 -3
  118. package/src/hub/peers.ts +32 -2
  119. package/src/hub/planning.ts +6 -1
  120. package/src/hub/question-desk.ts +201 -0
  121. package/src/hub/question-detach.ts +6 -0
  122. package/src/hub/questions.ts +260 -0
  123. package/src/hub/research-evidence.ts +106 -0
  124. package/src/hub/restart.ts +14 -0
  125. package/src/hub/review-anchor.ts +181 -0
  126. package/src/hub/review-brief.ts +13 -0
  127. package/src/hub/review-comments.ts +115 -0
  128. package/src/hub/review-findings.ts +7 -2
  129. package/src/hub/review-material.ts +76 -0
  130. package/src/hub/routing.ts +3 -1
  131. package/src/hub/settings.ts +3 -0
  132. package/src/hub/task-sweep.ts +2 -0
  133. package/src/hub/tasks.ts +607 -71
  134. package/src/hub/ui.ts +2 -0
  135. package/src/hub/worktree-prs.ts +100 -0
  136. package/src/hub/worktree-shelf.ts +246 -0
  137. package/src/memory/capture.ts +5 -2
  138. package/src/pet/config.ts +25 -13
  139. package/src/pi/extension.ts +43 -3
  140. package/src/rooms/link.ts +209 -21
  141. package/src/rooms/remote-peer.ts +186 -19
  142. package/src/rooms/service.ts +338 -22
  143. package/src/rooms/store.ts +437 -65
  144. package/src/rooms/wire.ts +24 -6
  145. package/src/ui/index.html +271 -17
  146. package/src/ui/needs.ts +10 -4
  147. package/src/ui/office-model.ts +64 -8
  148. package/src/ui/office-space.ts +1 -0
  149. package/src/ui/plugin-questions.ts +56 -0
  150. package/src/ui/plugin-results.ts +81 -0
  151. package/src/ui/plugin.ts +219 -28
  152. package/src/pet/window.html +0 -114
  153. package/src/pet/window.jxa.js +0 -122
  154. package/src/pet/window.ts +0 -154
package/CHANGELOG.md CHANGED
@@ -4,6 +4,57 @@ Issue and pull request numbers in the entries for 0.7.7 and earlier refer to the
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.31.0
8
+
9
+ - Delivery failures carry one bounded diagnostic cause: peer offline, adapter refused, push error, turn aborted or timeout (#1178, PR #1224, task 714). `ahub queue show` and JSON listings expose it without message text; legacy rows say when no cause was recorded. Status shows each peer's unresolved failed count and the discard next step, excluding explicitly resolved rows; inspect the causes with `ahub queue list` or `show`. Causes do not authorize replay or alter settlement.
10
+ - Record one convention for hub length bounds and truncations and relabel 44 existing entries by name (PR #1213, task 739, Refs #1180). Peer/model/cross-project text bounds are safety floors, wire/config shape bounds are contracts, and own-list/batch/display caps are operational. This records the calibrated rule without classifying a new baseline batch; #1180 remains open.
11
+ - Cross-project handoff failures are named and repairable (#1203 T3, task 734, PR #1215). `ahub task handoff --list` shows each handoff's age and whether its target is reachable, unreachable (with the cause and the command to run) or moved (the project id is no longer registered), and `--sync` asks every target now, for at most 20 seconds: with slow or down targets it prints the list so far and "catch-up still running for N targets" instead of timing out. A target on another wire version is refused as `older_target` with the upgrade step, an instance mismatch as the new code `mismatch`, wire mismatch names the upgrade step, while instance mismatch names `ahub projects` and `ahub up` only if the intended target is not running. A failed notify or catch-up writes one `hub.log` line per target and cause every 15 minutes instead of nothing. A catch-up request the target does not answer now counts as a failure (the target reads unreachable) instead of clearing it, and `--sync` says `synced: false` when its pass fails. Completes #1203: T1 made the request idempotent, T2 made the follow-back converge, T3 makes failures visible.
12
+
13
+ - A cross-project handoff's follow-back now converges (#1203 T2, #733). Each state event carries the target task's flow revision, and the origin ignores one that is not newer, so an `in_review` that arrives after `approved` cannot reopen the row. The origin's catch-up runs every 5 minutes and when a target restarts, over every open handoff with no 200-row cap, so a state change that missed the first notify arrives within one period. A reviewer's `changes requested` and a reopen are now reported to the requesting peer. Additive feature `task_handoff_seq`: an older origin is sent no sequence and not the two new events.
14
+ - Classify the first 65 baseline keys of the hard-coded values inventory by mechanical rules (#738, refs #1180): 43 `*_MS` constants, 8 timer-call literals, 4 reduce and loop starts and 3 config-shaped defaults as operational, and 3 benchmark error-text truncations and 4 `*_MS` constants that bound replay, revocation or rate as safety floor. The rules and their calibration are in `docs/verification/2026-11-05-hardcoded-values-classification-rules.md`; that batch reduced the baseline to 1157 keys. The remaining baseline is handled by later calibrated batches; PR #1213 records the bound convention and existing-label corrections.
15
+ - Gate the hard-coded values inventory (#719, refs #1180). Nothing ran `scripts/inventory-hardcoded-values.ts` after #764, so unclassified keys grew from 22 to 1438 unnoticed. `bun scripts/inventory-hardcoded-values.ts --check` now runs in `scripts/check.sh` and fails on any unclassified key outside `scripts/inventory-hardcoded-values.baseline.json`, which only shrinks. 36 stale keys were removed and 216 keys were classified from precedent in the file; the other 1222 stay unclassified in the baseline, and #1180 stays open for them (cause, calibration and the batch rules: `docs/verification/2026-11-05-hardcoded-values-ratchet.md`).
16
+ - A cross-project handoff that times out no longer reads as refused, and a retry no longer files a second task (#1203 T1, #731). The origin sends each request with a `handoffKey`; the target stores it with the task in one board transaction and answers a replayed key with the task it already made. The origin sends the same key once more, asks the target by key, and otherwise reports `outcome_unknown` with the key to retry with (`ahub task handoff --key <uuid>`, `handoffKey` in `hub_task_handoff`). The follow-back message now names the target's task (`refs.handoff`) instead of a local task number, so a cancelled or approved local task of the same number cannot drop it. Additive feature `task_handoff_key`: an older target is sent no key and keeps today's behavior. The periodic catch-up, sequence numbers and `--list` reachability are the next two tasks of #1203.
17
+ - A redirect to `/dev/null` or `/dev/tty` is no longer a destroy (#725, #1193). `2>/dev/null` on an otherwise read-only command raised an "always-ask: destroy" prompt whenever `action_policy.floor` was on. Every other target keeps its class, including `/dev/stdout`, `/dev/stderr` and `/dev/fd/<n>` (on Linux they reopen the file fd 1 or 2 already points at and a `>` truncates it), `/dev/sda`, a lookalike such as `/dev//null`, and any real target beside a device one. The redirect still closes the read-only git fast path. A release is needed before the installed plugin picks it up.
18
+ - Tell the person to update the global CLI and the plugin after `ahub upgrade` (#718, #1179). `ahub upgrade` upgrades one project's daemon only, so a stale CLI later failed with a message that did not say why. A completed or scheduled upgrade now ends with `bun add -g @staix/agent-hub@<version>` and `ahub setup` when the CLI or the plugin is older than the daemon it installed (the comparison `ahub doctor` already prints), the 0.30.0 upgrade note carries the step, `release-prepare` writes it into every new `Upgrading to <v>` section, and `release-tag` refuses a release whose section lacks it.
19
+ - Claude Code 2.1.318 is a receipt-verified headless implementer version (#888): the model-free permission-transport and host shell-wrapper producers pass on it with 2.1.316 as the control (`docs/verification/2026-11-05-claude-implementer-2.1.318.md`). 2.1.317 (installed, not run here) stays unverified.
20
+ - Move the floating pet to acon Mini (#1012). `ahub pet --window [--close]` and the retired `--palette <value>` now print the `acon ui` pointer and exit 0 without writing configuration; the standalone JXA host, assets, automatic startup and console `o` shortcut are removed. Existing `pet.window` is ignored with one notice, and `pet.palette` remains validated compatibility input with no rendering effect. Terminal line/mini/full pets remain, and the existing acon plugin **Console message** view supplies the composer.
21
+ - Questions for the person (#1053 T2-T6: #1170, #1183). The new `hub_ask` tool lets a peer ask the person structured questions. They are stored in a `questions` table with one compare-and-set terminal transition, and the call waits a bounded time per adapter (`questions.wait_ms`) before it answers `pending`. A later answer arrives as an envelope of kind `answer`, and an unanswered question expires after `questions.ttl_s`. Only the person can answer: with `ahub answer`, with the console keys, or through the acon inbox form path. An open question is a need in `ahub status` and the console. Question and answer text stay in hub.db and in the envelope sent to the asker; hub.log, events.jsonl, the console tail, memory capture and the progress observer stub them. AC1 live receipts and the T1 timeout measurement stay open in #1053.
22
+ - Settled runs and completion receipts (#1054: #1185, #1186, #1190, #1195). A run the person started settles into an id-only completion record with outcome `done`, `failed`, `limit` or `interrupted`. That happens when the adapter's settle signal fires and no continuation, permission, question, child, check or held delivery remains. A result stays unread until the person marks it read with `ahub results [--unread] [--read <id> | --read-all | --unread-last]` or in the dashboard's Results section. An unread result is a `watch`-level need. The acon inbox shows it only to a console that advertises `inbox.result` and `action.meta`. Receipts are accepted only from the console role with the person's verified meta, and a snapshot never writes one. Canary tests keep result text out of hub.db rows, hub.log and events.jsonl. A recovery target without the `completions` feature is refused while a run is open or a result is unread.
23
+ - Anchored review findings and person comment packages (#1056: #1181, #1192, #1205). Agent findings and console comments carry immutable acon anchors. A confirmed Send delivers one structured package to the task owner, and drafts start no agent turn. A retry reuses the saved package identity and never replays a published effect. Review-derived text reaches only its named task or current input, and that state survives a restart through session-bound receipts. Sessionless peers now receive review-scoped input without capture, progress, titles or memory writes. The devDependency `@staix/agent-console` is pinned at 0.1.3, and the hub imports only its type contract. #1056 stays open.
24
+ - Mark person-run backlog legs (#1176, #1188). `ahub backlog person <id> <revision> true|false <reason...>` (or `needsPerson` in `plan`) sets or clears the mark, and only the person may do either. Auto-pull, offers, assignment, the sweep and planner WIP never give a marked card to an agent. `ahub backlog list` shows `<peer> (person)` and `ahub status` shows `WAITING ON YOU`. An unmarked reserved card behaves as before.
25
+ - `ahub backlog return <task> <revision> <reason...> [--reserved <peer> | --unreserve]` passes `fields.reserved` to `hub_backlog_return_untouched` (#1177, #1191). With neither flag it sends what it always did.
26
+ - Cross-project handoff: a handoff whose spec issue already has an open task on the receiving board is refused, with the existing ids, unless the sender passes `duplicate_ok` with a reason (`ahub task handoff --duplicate-ok <reason>`). The receiving conductor may cancel an unaccepted handoff task with a reason, which the follow-back reports (#1175, #1182).
27
+ - Research owners record claim-bound evidence with `hub_evidence_record` (#931 T1, #1169). `hub_task_show` and `ahub task show --json` now expose text-free `claims: [{slot, claimId, claimRevision}]`, and recovery carries the new `research_evidence` feature. T2 and T3 of #931 stay open.
28
+ - Memory exact-pins `@staix/agent-mem` 0.21.0 (PR #1228, task 743, Refs #1173). The person-only `ahub memory declined [--json]` uses bounded physical-row inspection to list the complete declined IDs and safe rule categories, including forgotten originals; incomplete input remains explicit. Complete-set fingerprints distinguish changed failures. Existing standalone-policy screening of long writes and promotion, refusal before fallback and log-once behavior remain. Status and the dashboard project verified withheld/exported counts from a successful SDK placement result, but automatic v3 sync is unsupported until agent-mem#281 supplies supported activation and fresh host attestation; no activation proof is fabricated. #1173 remains open.
29
+ - Rooms delivery (#1204 T1-T2: #1206, #1214, #1216). Ephemeral presence is coalesced for supporting brokers, and a broker `queue_full` is retried with backoff from 250 ms to 30 s instead of waiting for timeout. Room messages resend after a reconnect, a restart or an acknowledgement timeout, under the same application identity and the original TTL. Admission receipts are committed to the durable outbox before the chat transport ACK. Transient pause and resume no longer persist an unchanged bus snapshot, which cut the 1,000-presence-flip test from 1.8 s to 0.5 s. T3 and T4 of #1204 stay open.
30
+ - A Claude session that inherits `AGENTHUB_CHANNEL=1` without the parent's native launch evidence attaches tools-only and reads its messages through `hub_inbox`, instead of dropping every push while reporting it accepted (#1171, #1184). Status reports the attachment mode and a closed evidence label. Evidence: `docs/verification/2026-11-04-1171-channel-client-evidence.md`.
31
+ - An accepted push-channel delivery that is never confirmed moves to `needs_review` after `bus.accepted_settle_ms` (default 10 minutes, 1 second to 24 hours) (#1172, #1187). The configured conductor gets one ids-only notice, and `hub_inbox` shows the session's own accepted and needs-review metadata without bodies. The expiry never replays the work or stops native execution.
32
+ - The acon inbox snapshot asks the running projects concurrently, each within a 2 s budget. It serves a fresh or briefly stale cached result and returns at most 50 items as `{ items, more }`, decisions first (#1196: #1200, #1209). #1196 stays open until the live console is measured.
33
+ - `Tasks.splitBacklog` validates every part's plan sources before the board write, so an unregistered `src:` id is refused and nothing is written (#1161, #1165).
34
+ - A second channel session no longer replaces the attached one (#1174, #1217). It stays connected as a non-owning standby, and `ahub status` lists each waiting connection's id, reported PID and age. A person hands the peer over with `ahub peer takeover <peer> [standby-id]` or `o` in the console's Peers panel; the old session's hub connection is detached, its native process keeps running, and its next hub tool call says the person detached it. When the owner leaves, the oldest standby attaches. Standby connections run no peer operation, and their ids and PIDs are display only. A plugin that does not opt in (an older plugin, or tools mode) keeps the newest-hello behavior.
35
+ - Rooms presence: an ordinary presence flip reuses a pending coalesced republish instead of sealing a new frame per flip, a chat reserves its FIFO slot before awaiting earlier presence work, and a closed bus publishes nothing (#1204, #1222). This cut a macOS CI test from about 18 s to under 1 s. Evidence: `docs/verification/2026-11-06-rooms-presence-cost.md`.
36
+ - Tests: `untilSeen` in `test/control-client.test.ts` waits on time, not on 200 event-loop turns (#1163, #1166). A failing fake-ripwire call now reports where its time went (#1197).
37
+ - 0.31.0 N-1: 0.30.0, the latest published 0.30.x (`@staix/agent-mem` 0.20.1, protocol 17). `test/fixtures/release-n-1` now pins it.
38
+ - Wire/protocol: unchanged at 16 through 17, with descriptor floor 16. The new daemon features (`questions`, `completions`, `review_comments`, `review_observer_input_state`, `accepted_delivery_settlement`, `research_evidence`, `task_handoff_key`, `task_handoff_seq`, `peer_takeover`) are additive, and a client sends what needs them only to a daemon that advertises them.
39
+ - Files: the hub.db schema version stays at 2 and format floors remain enforced, but this release adds the nullable `deliveries.failure_cause` column. The N-1 recovery reader ignores the extra diagnostic snapshot key; legacy snapshots may omit it. Other new tables and columns remain additive. A recovery target without `completions` is refused while a run is open or a result is unread.
40
+ - Memory: this release pins agent-mem 0.21.0 instead of 0.20.1. [Agent-mem's 0.21.0 N-1 notes](https://github.com/STAIxBWLB/agent-mem/blob/v0.21.0/CHANGELOG.md) compare against 0.20.3, not the 0.20.1 bundled by hub 0.30.0. They retain curated/capture/hook-spool/candidate/work-list/setup receipt v1/v2 formats, shared-v1/generation/v1-v2 shard sync, index version and index filename. Their activated-v3 refusal proof applies to 0.20.3; this record does not extend that test claim to 0.20.1. V3 activation has no command/writer in that package release, and the hub's automatic v3 sync also awaits supported fresh attestation in agent-mem#281.
41
+ - CLI: the 0.30.0 control clients interoperate. `ahub answer`, `ahub results`, `ahub backlog person`, `ahub memory declined`, `backlog return --reserved | --unreserve` `task handoff --duplicate-ok | --key` and `ahub peer takeover` need a 0.31.0 runtime.
42
+ - MCP tools: the 0.30.0 channel plugin interoperates but has no `hub_ask` or `hub_evidence_record`. Ceilings: a handoff that a 0.30.0 origin sends to a 0.31.0 target is refused when its spec issue already has an open task there, because a 0.30.0 sender cannot pass `duplicate_ok`. A split whose part plans name unregistered `src:` ids, which 0.30.0 stored, is refused. A 0.30.0 plugin does not opt in to standby, so its second session still replaces the attached one; a 0.31.0 plugin against a 0.30.0 daemon keeps the prior standby reconnect behavior.
43
+ - 0.30.0 N-1 (recorded after release on 2026-11-04, #1164): 0.29.0, the latest published 0.29.x. Wire/protocol: 16 through 17, including descriptor floor 16. Files: existing database and recovery format floors remain enforced; incompatible newer formats refuse. CLI: previous-minor control clients interoperate; new commands and flags require their runtime capabilities. MCP tools: the previous-minor channel plugin interoperates, but 0.30.0 (#1151) requires registered `src:` ids with 16 hexadecimal digits in `plan.sources` for `hub_task_propose` and `hub_task_accept`. Free-form citation/DOI/URL ids accepted by 0.29.0 are refused. Accept also revalidates the stored plan when no new plan is sent, so an existing task with such ids must replace or remove unsupported source ids before acceptance. This is a post-release record, not a statement carried by the v0.30.0 tag or npm tarball.
44
+ - Add frozen previous-minor runtime interoperability tests alongside the retained protocol-floor fixture, and document fixture maintenance and release compatibility statements (#1164).
45
+
46
+ ## 0.30.0
47
+
48
+ - Worktree shelf (#1057: #1156, #1158, #1159, #1160). The hub derives at read time which worktrees are live and which are settled (its PR merged or closed with no newer work, or quiet for `worktrees.quiet_days`, default 7), and the person can Park or Pin a row. `ahub worktrees [--json]` lists both; `ahub worktrees tidy` previews what is safe to remove, with every refused row's reasons, and `ahub worktrees tidy --confirm <fingerprint>` removes only those, in the person's own CLI process, with `git worktree remove` and `git branch -d` and never a force flag. Dirty, locked, unpushed, unmerged, bound-to-open-task, main and instance worktrees are refused. The dashboard's Agents panel gets a Worktrees section with Park, Pin and an explicit "Refresh PR states" (at most 8 PRs); no read makes a GitHub call, and an inbox alert fires above `worktrees.alert_over` (default 10). The shelf adds no removal to the daemon: it still removes only an empty hub-made task worktree on decline or cancel (0.29.0).
49
+ - Pi and the local worker name their own session with `hub_set_title` (#1157, #1055). The first name is final; the person replaces it with `ahub title <peer> <text...>` or the console's `t` editor. An unnamed session shows a screened label from its first incoming message to the console and the conductor only, `unnamed` to everyone else. Claude, Codex, Kimi and ACP tool schemas are unchanged.
50
+ - Office view phase 5 (#264: #1142, #1152): a Backlog wall before To do (rank order, at most 8 cards, reserved, pinned and waiting marks), up to three release docks with approved/scope and cut date, and the unplanned count on each planner's desk. Everything comes from the existing snapshot; no daemon change.
51
+ - Plan sources are validated (#1144, #1151): a plan's `sources` must name registered `src:<16 hex>` ids (`ahub source list`), or the proposal or acceptance is refused before any effect. The dashboard Plan sheet lists source metadata and stages natural-language requests through the same held-draft intake as the CLI.
52
+ - `ahub autonomy status` is readable (#1137, #1138): a peer table first, identical floor verdicts and unknowns once with their peer lists, and class enforcement counts. `--json` is unchanged.
53
+ - `hub_backlog_return_untouched` takes `fields.reserved` (a peer id or null), applied in the same write as the return, so a parked card is not offered straight back under auto-pull (#1154, #1155).
54
+ - The weekly dogfood scorecard reads PR flow on a board over 500 tasks (#1147, #1153): a separate, text-free PII inventory decides privacy, and task rows are read for the scorecard window only. The 2026-11-03 scorecard and the #788 progression measures are recorded (#1149).
55
+ - Compatibility receipts (#888): Pi 1.5.0 (#1148), qwen ACP 0.26.5 (#1150; qwen-dual stays at 0.26.1) and agy 1.4.12 (#1141, #1146) join `verified`. Run agy with `AGY_CLI_DISABLE_AUTO_UPDATE=1`: without it agy replaced its own binary during a launch (#1140). The `ahub agy` launcher close leg (#1140) and an isolated Grok 1.0.62 T0 (#1139, #635) are recorded.
56
+ - Pin `@staix/agent-mem` 0.20.1 (was 0.20.0, #1136). A setup receipt written by 0.20.1 can be v2, which 0.20.0 refuses to apply or undo; undo with the release that wrote it.
57
+
7
58
  ## 0.29.0
8
59
 
9
60
  - The hub makes the task worktree for a hub-sandboxed owner (Pi, the local worker) and commits and pushes its work at `hub_task_done` (#1115, #1127). The commit goes through a private index and a compare-and-swap ref update, only on top of the recorded base or the hub's own last commit, after the changed paths pass the deny and grant policy; a moved branch or HEAD, a refused path or a changed gitlink refuses the commit with its cause. Decline and cancel remove an empty worktree and keep one with uncommitted work.
package/README.md CHANGED
@@ -5,7 +5,7 @@ Code, Codex, Kimi Code, Pi and a hub-owned local worker (plus Qwen Code, Jcode a
5
5
  Antigravity if you want them) share a message bus, a task board and a memory, each
6
6
  connected through its own native interface.
7
7
 
8
- Status: 0.29.0, control protocol 17 (accepts 16). Bun + TypeScript, one loopback daemon per
8
+ Status: 0.31.0, control protocol 17 (accepts 16). Bun + TypeScript, one loopback daemon per
9
9
  project. Site: [agent-hub.staix.net](https://agent-hub.staix.net).
10
10
 
11
11
  ## Quick start
@@ -30,9 +30,11 @@ ahub ui # the web dashboard
30
30
 
31
31
  `ahub setup` also registers agent-hub as a plugin of the family console
32
32
  ([agent-console](https://github.com/STAIxBWLB/agent-console), `acon ui`): the same dashboard
33
- appears as a panel for every registered project, and pending approvals and reviews assigned
34
- to you reach the console inbox. The plugin is `@staix/agent-hub/ui-plugin`; it uses the
35
- dashboard manager's own listing and forwarding, so it adds no protocol and no action.
33
+ appears as a panel for every registered project, and pending approvals, reviews assigned
34
+ to you, questions agents ask you and, where acon supports them, unread results reach the
35
+ console inbox. The plugin is `@staix/agent-hub/ui-plugin`; it uses the dashboard manager's
36
+ own listing and forwarding, and an answer, a result receipt or a review comment sent from
37
+ acon goes through a manager port of its own that needs acon's verified person.
36
38
  The plugin also feeds the console's Space view (Flat, Room and 3D): agents become actors
37
39
  at their desks, the task board's columns become zones, and task links and recent messages
38
40
  become links. The scene carries labels and states only, never message bodies or details.
@@ -100,7 +102,12 @@ defaults. The
100
102
  worktree with a machine-wide view, live settings reload, machine-local schedules
101
103
  that propose tasks by interval or by a `cron` expression in a time zone
102
104
  (`ahub schedule list`), a one-confirmation autonomy
103
- preset (`ahub autonomy on|off|status`), structural config
105
+ preset (`ahub autonomy on|off|status`), a worktree shelf that lists live and
106
+ settled worktrees and removes the safe ones only on your confirmation
107
+ (`ahub worktrees`), questions an agent asks you with `hub_ask` (answered with
108
+ `ahub answer`, the console's `A` or the acon inbox), the results of runs you
109
+ started (`ahub results`), a person-requested handover of a peer to a waiting
110
+ standby session (`ahub peer takeover`), structural config
104
111
  (instances, review, lifecycle) edited with a validated diff and a backup from the
105
112
  console or a dashboard settings session, token usage and quota
106
113
  history (`ahub usage` and the dashboard's Usage view), and `ahub upgrade`, which
@@ -111,7 +118,8 @@ defaults. The
111
118
  Run `ahub upgrade` in a project while its hub is running (`--project` selects
112
119
  another); other projects keep their version. Update the global CLI
113
120
  (`bun add -g @staix/agent-hub@<version>`) and run `ahub setup` for the plugin
114
- yourself. Read the notes for your version in
121
+ yourself; `ahub upgrade` names that step when either is older than the daemon it
122
+ installed. Read the notes for your version in
115
123
  the [Quickstart](docs/quickstart.md) ("Upgrading to ...") and the
116
124
  [Changelog](CHANGELOG.md) first: some releases need a one-time step, such as
117
125
  refreshing an agent's MCP entry.
@@ -135,7 +143,7 @@ uses the home must run a version that understands the new layout.
135
143
  [#258](https://github.com/STAIxBWLB/agent-hub/issues/258)).
136
144
 
137
145
  Live checks that still need a real account or a person are listed in the
138
- [release verification report](docs/verification/2026-11-03-0.29.0.md) and the
146
+ [release verification report](docs/verification/2026-11-05-0.31.0.md) and the
139
147
  [smoke checklist](docs/smoke.md).
140
148
 
141
149
  ## Documentation
@@ -2,6 +2,18 @@
2
2
 
3
3
  Human explanation. Agent instructions: the area notes in this directory.
4
4
 
5
+ In the #1055 review, the new title-binding lookup preceded invocation,
6
+ pending-delivery and supervision bookkeeping in `Bus.onDelivered`. The bus's
7
+ outer observer catch protected settlement but could not finish bookkeeping
8
+ after a journal lookup threw. This is the background for isolating that
9
+ fallible observer work in the bus area note.
10
+
11
+ Two #1055 refusal tests exposed Bun 1.4.2 replacing a received `error` string
12
+ with the `StringContaining` object supplied to `toMatchObject`. A minimal
13
+ same-version reproduction showed `typeof error` changing from `string` to
14
+ `object`. Preserving the original value keeps later error-text and canary
15
+ checks meaningful; changing production messages would not address this cause.
16
+
5
17
  On 2026-10-13, two PR #368 reviews found that a missing delivery id or a missing visible row could select another Queue row. The console navigation rule in daemon.md records the exact-row contract. The missing-row and ID-less secondary-delivery regression tests demonstrate both cases.
6
18
 
7
19
  Background behind some of the rules, moved from the top-level README on 2026-10-16:
@@ -52,6 +64,7 @@ Background behind the 0.18.0 rules:
52
64
  Background behind the 0.19.0 rules:
53
65
 
54
66
  - Code-scope verdicts (#852, tasks.md): two review rounds showed that any classifier reading a `code` reviewer's unmet text files real defects, such as "the CI workflow is missing the trigger", as verification-only, so the evidence-only rule reads only the structured findings.
67
+ - VM closure dependencies (#1056, tests.md): review refresh and durable observer checks added cache, snapshot and scope dependencies to existing functions. Searches for the new dependency names missed fixtures that extracted their callers, causing ReferenceError failures or fail-closed paths that pre-empted the behavior under test.
55
68
  - Policy-table fixtures (#843, tests.md): a fixture with a fixed list of keys or versions, which a planned `COMPAT` entry was not in, was reported twice in #843.
56
69
  - Suite watchdog (#516, tests.md): a total-time kill cut suites that were still passing tests line by line (hosted ubuntu runs at 1080 s, the macOS leg of PR #857 at 1440 s), so a lost-progress bound became the hang detector and the total bound only its backstop.
57
70
 
@@ -29,7 +29,7 @@ Scope: the Codex, ACP and Pi adapters and the Pi extension (turn `generation` an
29
29
  - Agent shell authority (#812, `classifyPeerCommand` in `src/cli/identity.ts`, `src/hub/operational-authority.ts`): the Allowed read forms live in `peerReadForms` and are admitted only when the daemon also admits the same form for an ordinary tools connection; the conductor family is the closed `conductorOperation` list, each admitted by `admitOperational` (role recheck, closed audit label, no argument text; `queue show`, a settings preview and the authorize handshake recheck through `operationalCurrent` without an audit record). Throw `OperationalAuthorityError` only from an authority fence: only it is audited as refused and gets the conductor or person guidance, and an ordinary operation failure keeps its own error (#839). A new CLI form is `console` until it is added to one of those tables with a test in `test/identity.test.ts`. #933: `console` is only trust, money, attribution and interactive terminals; a conductor form joins `CONDUCTOR_DAEMON_OPERATIONS` (its own daemon request calls `admitOperational`) or `CONDUCTOR_LOCAL_OPERATIONS` (its effect runs in the CLI, so the `admit` handshake is its one audit row, sent only after its `LOCAL_OPERATION_ARGS` check in `src/cli/main.ts` passed), never both; a form whose write records the person as actor (amem's `user`) or whose runner inherits the agent markers stays `console`, except the recovery-bearing daemon forms (`restart --yes`, `upgrade --yes --to <version>`, `recovery resume|abort`, #900), which also require their source-issued continuation proof (rule: `daemon.md`), and every reclassified form changes its daemon check in the same PR.
30
30
  - A headless ACP Kimi or Codex whose mode lives in the native agent joins the bus only through `reconcileNativeStart` (`src/hub/daemon.ts`): after `start()` the daemon's current mode is applied, then `bus.add` (it can drain at once), then the state `start()` reached is replayed. Every mode change runs inside `permissionChange`, one per peer at a time.
31
31
  - Generic permission and generation dispatch in the daemon reads the optional `PeerAdapter` ports (`src/hub/peers.ts`, #667): `permissionModeState`, `setPermissionMode` (resolves only after native acknowledgement; changes approval frequency, never the sandbox), `sessionGeneration` and `checkpointUsesSessionGeneration`; never add a native-class predicate (`isAcpPeer`, `isCodexPeer`) for it. An absent `sessionGeneration` keeps the recovery-session/owner-token fallback; a present but undefined one is unknown and refuses. `BasePeer` installs no default generation, and only an explicit `checkpointUsesSessionGeneration = false` (Pi) skips the checkpoint generation fence. A `permissionModeState` of `unknown` or `unmanaged` refuses a change.
32
- - Delivery, lifecycle and session dispatch in the daemon reads the same optional ports (#625 slices 2b-2c): `deliveryOnly` (absent or `false` permits `hub_delivery_retry`; `true` or a present `undefined` refuses), `hubOwned` (only `true` lets `peer_stop` stop the runtime), `pendingOwnedProcess`, `form` (a present `undefined` is unknown: `peer_stop` refuses, and a definition with `permission.unmanagedForms` treats it as a TUI), `captureResume`, `detachForRoster`/`resumeFromRoster`, `pullOnly`, `recoveryReady` and `nativeSession` (rules: `daemon.md`). An adapter the hub owns and may stop sets `hubOwned = true`, a delivery-only transport sets `deliveryOnly = true`; never add an `is*Peer` call for any of them (ratchet above).
32
+ - Delivery, lifecycle and session dispatch in the daemon reads the same optional ports (#625 slices 2b-2c): `deliveryOnly` (absent or `false` permits `hub_delivery_retry`; `true` or a present `undefined` refuses), `hubOwned` (only `true` lets `peer_stop` stop the runtime), `pendingOwnedProcess`, `form` (a present `undefined` is unknown: `peer_stop` refuses, and a definition with `permission.unmanagedForms` treats it as a TUI), `captureResume`, `detachForRoster`/`resumeFromRoster`, `pullOnly`, `recoveryReady` and `nativeSession` (rules: `daemon.md`). An adapter the hub owns and may stop sets `hubOwned = true`, a delivery-only transport sets `deliveryOnly = true`, and only a live push channel (`WsPeer`) sets `acceptedSettlement = "explicit"`, the one port that lets a peer settle or discard its own `accepted` row (`Bus`, #1172: `accepted` is transport acceptance only, and an unconfirmed row moves to `needs_review` after `bus.accepted_settle_ms`, never replayed); never add an `is*Peer` call for any of them (ratchet above).
33
33
  - Codex takes a mode as an `approvalPolicy` override on `turn/start`, and the override persists on the thread: pass the hub's and the TUI's `turn/start` alike through `applyPermissionPolicy`, and return to `ask` only by sending the captured native policy once. With none captured, refuse while the TUI is attached; never just stop sending the override. Keep the captured native policy and the restoration debt per thread (`nativeApprovalPolicies`, `overriddenApprovals`) across detach and `thread/start` or `thread/resume`; clear a debt only on a successful response to its restoring `turn/start` while the mode is `ask` (`settlePermissionRestore`), so a rejected turn keeps it. While a thread owes a restoration with a known baseline, in any mode, never replace that baseline: a `thread/resume` response and the TUI's own `turn/start` can both echo the sticky override, and the restoring turn sends the kept baseline whatever the TUI supplied. That protection is one turn: after the restoration succeeded, a policy the TUI sends is taken as the person's choice, captured and passed through, because the proxy cannot tell a repeated echo from one. A detached proxy's `clearPermissionMode` is never refused; if the native policy is unknown, return a CLI-visible note that the thread keeps its last policy. Attached refusal directs the person to close the TUI, then run `ahub permission codex ask`.
34
34
  - Codex planner threads (#525-6, `threadSandbox` in `codex-appserver.ts`, live from `isPlannerOnly` for a declared instance): every `thread/start`/`thread/resume` goes up with `sandbox: "read-only"` and is adopted only when its response positively confirms the `readOnly` policy (`confirmsReadOnlySandbox`). An unconfirmed or malformed reply, a reused RPC id, an `unavailable` policy (a planner with a declared worktree) or a throwing reader refuses that RPC and closes the TUI link before adoption or delivery, and `deliver`, `steer` and `steerText` recheck `threadSandboxReady` right before they send. Never count missing evidence as read-only, and never let a role change promote an unconfirmed thread.
35
35
  - An unanswered `session/set_mode` leaves the native mode unknown, not unchanged: `AcpPeer` invalidates the turn, goes offline, stops its process group and refuses further changes until restarted. Never report the old or the requested mode after a timeout.
@@ -41,6 +41,7 @@ Scope: the Codex, ACP and Pi adapters and the Pi extension (turn `generation` an
41
41
 
42
42
  ## Turns, watchdogs and hub tools
43
43
 
44
+ - Delivery failure diagnostics (#1178): carry the optional typed `failureCause` from `src/hub/delivery-failure.ts` through `DeliveryReceipt` and legacy `onFailed`, including pending RPC, steer and watchdog paths. Classify from structured host facts, never error, reason or body text. Channel receipt causes are assigned by the host, never trusted from the caller. A known cause never downgrades an uncertain delivery to `failed_safe` or changes settlement.
44
45
  - A peer must set `busy` synchronously inside `deliver()`, otherwise the bus drains the next envelope into a running turn (Kimi answers `turn.agent_busy`). Outside a turn the adapter accepts an occupancy diagnostic (a `context-used` `usage_update` naming its current session) and nothing else: no token total, no chunk, nothing unnamed or for an offline peer (#285).
45
46
  - A watchdog-cancelled turn still reports later. Anything a turn does on completion must check it is still the current turn (`turn` generation in `acp.ts`, `activeTurns` in `codex-appserver.ts`). A rejected prompt other than a recognised quota refusal, or an abnormal prompt end, reports through the adapter's `onTurnFailure` (same shape in `acp.ts` and `pi.ts`), even when ACP streamed partial text; a stale cancelled turn never reports it; a rejected watchdog-cancelled ACP prompt during its journal-less wait is discarded with one log line, not requeued as on the earlier adapter.
46
47
  - After an ACP watchdog cancels a prompt on a bus without a journal, keep the peer busy with its inactivity watchdog cleared until that prompt settles; suppress its turn-scoped updates and permissions while still accepting session-bound occupancy diagnostics. Bound the wait by the existing 30 s ACP handshake deadline, go offline, never idle, with queued work retained if it expires, and revoke the wait on stop or transport loss; a durable delivery keeps its existing offline/needs_review path.
@@ -51,6 +52,7 @@ Scope: the Codex, ACP and Pi adapters and the Pi extension (turn `generation` an
51
52
  - A durable Codex steer stays pending while its native turn runs. Its RPC may answer after `turn/completed`: bound unanswered replies from that turn's end, requeue a definitive late refusal, and hold a late acceptance as uncertain without attaching it to a later turn's audience (#296).
52
53
  - Codex reports each thread it adopts, started or resumed (`onThread`, the `native_thread` event), before any turn on it starts: the upgrade planner (Codex's `session.zeroTurn` in `src/adapters/definitions/native-recovery.ts`, read through `nativeZeroTurn` in `src/cli/upgrade-runtime.ts`, #215) gives each codex `turn_start` to the thread adopted last before it and plans a thread it saw start with no turn (and no rollout) as a fresh start.
53
54
  - Peer DOING (#878): an adapter reports a native session title or its plan's in-progress item only through `onDoing` (set by `Bus.add`), with `reset` when it binds a new session or thread; never put that text in a log line, event, message, status notice or memory capture. Codex reads only `thread/name/updated` and `turn/plan/updated` of the bound thread, ACP only a `plan` update inside the current turn; never guess a value for a peer without such a source.
55
+ - Session titles (#1055, spec `docs/design/adapters.md` "Session title slots"): only a fixed builtin may declare `title` or `titlePersistence` (`assertNativeCapabilityScope`), and only a `title: "hub"` definition (Pi, local) is offered `hub_set_title` (`sessionTitleTools`); keep it off Claude, Codex, Kimi and ACP tool lists and native auto-approval names. Write a title only through `setSessionTitle` (unnamed-only for an agent, `current()` rechecked right before the effect) and report it only through `onDoing("title", ...)`; never pass a title call to `observeTool` or capture, and never store a derived unnamed label.
54
56
  - Codex 0.154.0 `agentMessage` items carry `text` and `phase` (not `content[]`); only the last non-`commentary` message of a turn is shared.
55
57
  - Hide `hub_delivery_retry` from delivery-only ACP/local tool lists and omit its retry hint. Treat `AGENTHUB_DELIVERY_ONLY` only as an exposure hint; the daemon must refuse by the actual attached adapter, and native children must not inherit the marker. A Claude push that cannot reach its model reports `failed_safe`; if the daemon refuses that optional receipt state, try `needs_review` and retain uncertainty when neither receipt is confirmed. Keep `hub_delivery_resolve` on every list, delivery-only included: settling one's own `needs_review` row is not a retry (#1112).
56
58
  - ACP `onActivity` is a closed, transient person-only projection. Never publish it through the bus, log/event writers, UI, tools or memory. Emit only for an active turn; latch private envelopes and associated PII work, including unreadable privacy state, and discard pending chunks on turn teardown. Native terminal references do not authorize filesystem/terminal RPC reads.
@@ -106,7 +108,7 @@ Scope: the Codex, ACP and Pi adapters and the Pi extension (turn `generation` an
106
108
  - Jcode MCP set (#572): `jcode acp` and `jcode serve` start every server named in `<JCODE_HOME>/mcp.json` and the project's `.jcode/mcp.json`, `.mcp.json` and `.claude/mcp.json`, and no flag or variable selects them (`--mcp-tools`, `JCODE_MCP_TOOLS`, `JCODE_DISABLED_TOOLS` and `JCODE_DISABLE_CLAUDE_MCP` do not). Do not paper over it: `ahub doctor` lists the extra names through `jcodeExtraMcpServers` (`src/adapters/definitions/native-cli-jcode-mcp.ts`), names only, those four paths only, no home search, no link followed. Below the project root or home, `.jcode` and `.claude` must be real directories and the file a regular file (per-component `lstat`, an `O_NOFOLLOW` open and an `fstat` identity recheck); a system alias at or above the configured root stays allowed. Revisit if upstream adds a server selector.
107
109
  - Extra Jcode instances (#685, #368): one `JcodePeer` per declared id (at most 8), each with its own `jcode_home`, socket and state. The person authenticates inside Jcode; the hub passes `-p`, `--model` and `--provider-profile` exactly as declared, handles no model credential or URL, and only checks that the home is a directory the person owns (`jcodeHomeRefusal`: no read or write under it). Never put `AGENTHUB_PEER_ID` in the shared `.jcode/mcp.json` entry (`jcodeMcpEntry`): every owned launch supplies it through `peerChildEnv(<id>)` or the TUI launch env, together with its `AGENTHUB_LAUNCH_ID`. The MCP server with no `AGENTHUB_PEER_ID` in tools mode refuses every tool and never defaults to a peer id. The daemon admits a `tools` hello for a peer whose definition is `owned-acp` only when it carries the launch id of that peer's current owned launch (`JcodePeer.ownsLaunch`, from `jcodeLaunches`); never trust the env alone. `jcode --as <id>` never opens a TUI. Declare the semantic `instances.nativeHome` on a definition, never compare the kind name (`registry-guard`); it is a fixed-builtin capability that `assertNativeCapabilityScope` refuses elsewhere. `checkJcodeInstanceHomes` (`src/hub/instances.ts`, run once at daemon startup) refuses a home equal to or nested in the base Jcode's or another instance's; compare homes only through its `canonicalHome` (`realPath`, case-folded on a volume that folds case, inode), never `resolve()` text. The preview of `--as <id>` passes the instance to `previewJcode` so it shows the daemon's real flags.
108
110
  - Jcode defers MCP tools (#912, `JCODE_DEFERRED_TOOLS_INSTRUCTION` in `src/hub/envelope.ts`): a Jcode peer lists only its built-in tools until the model searches for an MCP tool, so without a hint it reports the hub tools missing and answers in its final message. The guidance is written in exactly one place, that constant, and `JcodePeer` sends it through `jcodePreamble` (`src/adapters/jcode.ts`) ahead of the role contract, once with a session's first prompt, for the base peer and every extra instance. Never copy it into `STANDING_INSTRUCTION`, `templates/AGENTS.block.md` or a profile template (the Jcode bullet there is the same text in every template, and `test/jcode-deferred-tools.test.ts` pins both), and never change Jcode's own deferral or cache settings from the hub. The tools it names must exist, and the server name must be `JCODE_MCP_NAME`.
109
- - Antigravity P1 (`src/adapters/agy/`): keep it optional, cloud, off roster by default, implementer/verifier only and pull-only. Until per-mode native prompt behavior is verified, reject hub permission/unattended mappings, headless starts and resume selectors; do not infer a quota source. The launcher refuses extra instances, any `agy --version` that compat `agy` refuses (1.4.2 and 1.4.7 are T0-verified; other 1.4.x patches launch with a stderr warning, #589; another version needs the person's trust, #691), and a hub without the `native_hook_launch` feature.
111
+ - Antigravity P1 (`src/adapters/agy/`): keep it optional, cloud, off roster by default, implementer/verifier only and pull-only. Until per-mode native prompt behavior is verified, reject hub permission/unattended mappings, headless starts and resume selectors; do not infer a quota source. The launcher refuses extra instances, any `agy --version` that compat `agy` refuses (1.4.2, 1.4.7 and 1.4.12 are T0-verified; other 1.4.x patches launch with a stderr warning, #589; another version needs the person's trust, #691), and a hub without the `native_hook_launch` feature.
110
112
  - Only the person's `ahub agy` launcher manages `.agents/plugins/agent-hub/{plugin.json,mcp_config.json,hooks.json}`. Refuse linked/foreign entries before any write; cleanup only the matching launch. Never write project-level `.agents/hooks.json` or personal Gemini settings. The plugin MCP server is named `agent-hub_agent-hub` by the pinned native client.
111
113
  - Managed native facts use the definition's typed `NativeFactsPort` and the daemon's ephemeral `native_hook_launch` authority. Bind every observation to the daemon, launch, native session, project and current socket generation. Stop requires `fullyIdle`, normal termination and a fresh validated completion row; a missing readback keeps the peer busy. Native fact acknowledgement accepts only SDK ephemeral-message rows in the exact session transcript path. `decodeAgyHook` classifies as `Read` only the observed native read (`view_file` `AbsolutePath`, or 1.4.3 `read_file` `Path`); a `StartLine`/`EndLine` range becomes `offset`/`limit`, so a partial read never counts as a whole-file view. Unobserved edits get no scoped grant or effect attribution. The daemon records Pre/PostToolUse through `Facts.preTool`/`postTool` and offers nothing on them (#555).
112
114
  - `terminalOwner(launchId)` (`src/hub/peers.ts`, #482) answers only for the adapter's current native TUI owner bound to that launch (Codex: the claimed link with that `launchId`, both sockets open; the launcher sends the id as the upgrade's bearer token through `--remote-auth-token-env`, because `codex --remote` refuses any path or query (#586); Pi: a started, verified TUI owner on its requested session); a start acknowledgement is never ownership. A Codex link that is not the claimed one or has a closed socket never adopts a thread.
@@ -120,6 +122,8 @@ Scope: the Codex, ACP and Pi adapters and the Pi extension (turn `generation` an
120
122
  - Bind every running print review to its immutable envelope head and review revision. Recheck `Tasks.reviewBindingCurrent` for task-MCP operations, legacy print reads and the verdict authority after awaits; an old child never gains a replacement request. A committed verdict acknowledgement does not authorize subsequent reads or verdicts.
121
123
  - `ReviewerPrintPeer` (`src/adapters/reviewer-print.ts`, #223, #519; `ClaudePrintPeer` is its Claude wrapper) runs one fresh child per review and clears `reviewing` at the leader's exit and first in `kill()`. A stop or watchdog settles `needs_review` at once, before the kill; `completed`, `idle` and `offline` come only after the whole process group is confirmed gone, and a failed cleanup keeps the child handle and stays `busy`. The daemon's `reviewAuthority` compares `reviewToken`: never widen it.
122
124
  - Every print child (reviewer and Claude implementer) is spawned, bounded and stopped only through `OwnedPrintChild` (`src/adapters/claude-print-child.ts`): its `done` settles only after the whole group is confirmed gone, and a failed stop rejects with `PrintChildCleanupError` while the owning peer keeps the handle. Never add a second spawn path to an adapter.
125
+ - Owned input observers (#1056): `beforeInput`/`inputReleased` (`OwnedInputObserver` in `src/adapters/claude-print-child.ts`) carry review-observer provenance only. `observerSessionId` (Claude implementer, reviewer print, local) is never `nativeSession` and never grants budget, resume, permission or task authority. `beforeInput` runs with the final target session before any prompt reaches the child or model, and a throw releases no prompt; `onInputReleased` fires only after stdin flushed for the current generation, and an observer throw never changes settlement. A fresh reviewer calls `inputFinished` only after its group is confirmed gone, its task MCP is closed and launch cleanup succeeded.
126
+ - Retained standby (#1174, `WsPeer` in `src/adapters/channel-peer.ts`): an opt-in (`retainStandby`) hello while another session holds the peer is kept as one of at most `MAX_STANDBY_SESSIONS` (8) standbys; `standbySessions` ids and PIDs are display only, never native-session provenance or authority. Ownership changes only through `takeover` (the person's `peer_takeover`, or the oldest standby when the owner leaves), guarded by the host's `channelTakeoverAdmission` and `claim.isCurrent()` across the async preface; the old socket closes 4405 (opt-in) or 4403 "detached by the person" and its native process is not stopped. A standby socket runs no peer operation until `ownsSocket` confirms it. A hello without the opt-in keeps newest-hello replacement.
123
127
  - A reviewer backend is a fixed builtin `ReviewerBackend` in a definition's `reviewer`, never config. Its `prepare` must return a complete isolation receipt (matching backend id, binary, version, profile, evidence and `readOnly` true, plus `strictMcp` and `ephemeral` true, or for a `nativePolicy` backend `enforcement: "native-policy"`, `freshSession` and `hubToolsOnly` true) before every child. A `taskMcp` backend refuses without the host's `taskMcp` port and gets only that task's MCP config, never the hub-wide one. `admit(env)` and `launch.current()` are rechecked right before spawn, and a `watchCurrent` launch is polled while the review runs: a change aborts it as `review policy changed`; a missing or failed proof refuses, never a weaker launch or a continuing native session. The daemon puts the reviewer on the bus only after `start()` proved isolation; a failed start or a `start_failed` report marks it in `reviewerUnavailable`, which routing reads as `failing`. An unavailable reviewer variant has no reviewer backend or launch code; `startPeerBody` refuses it before preflight, independently of any native TUI launcher.
124
128
  - Report reviewer failures (`onUnavailable`: `start_failed`, `quota`, `no_verdict`) only after the child's group is confirmed gone and only while its generation is current: a person's stop owns its hold and never reroutes. Complete a normal no-verdict delivery only after the host confirms a current re-offer; an uncertain or failed fallback stays `needs_review`, with its `REVIEWER_FALLBACK_UNCONFIRMED` reason excluded from automatic moved-review settlement; ordinary superseded holds keep their existing rule. Log one bounded no-verdict line from exit code and allowlisted hub-tool metadata only, with full refusal privacy/secret screening before sanitization and truncation; retain the first full host refusal across native echoes, and withhold differing later host refusals rather than replacing or accumulating them; stale/private context is withheld. Record only allowlisted tool names and refusal text; never retain argument objects, task/diff fields or model-message events as trace data. `quota` comes only from the backend's structured `failure(event)`, never from vendor text. An NDJSON frame over 256 KiB or more than 4 MiB of output aborts the review as `needs_review`.
125
129
  - Codex reviewer isolation (`src/adapters/codex-review-isolation.ts`): accept only a `codex-cli` version that compat `codex-review` admits (verified or the person's trust, #691), and still run every proof below after trust; pass `-c notify=[]` (legacy notify off) on every launch; prove model-free that Apps, plugins and hooks are disabled (`features list`), ambient MCP servers are replaced by disabled stubs, and the hub server alone is enabled with exactly `hub_review` and `hub_task_show`; approve exactly those two MCP tools per tool, never with a server-wide approval default; before marking a reviewer backend/version verified, record a successful model-backed hub tool call as well as the model-free inventory (inventory alone is not verdict-delivery proof); run the native sandbox fixture probe before every launch; hash the binary once and fence binary and config files by metadata. Credential denies come only from the bounded `HOST_CREDENTIAL_PATHS` and `HOST_CREDENTIAL_ENV` lists, denied at the lexical and canonical path; refuse glob syntax or a root inside the checkout. A root that is dangling or unreadable refuses too; the reason names only its path class (`~/.docker`, `$DOCKER_CONFIG`), never a path or contents. Never pass the control token in a reviewer child's argv, env or MCP config: keep the hub state dir in its `privatePaths`. A refused `prepare` carries a `causeClass` from the closed `REVIEWER_CAUSE_CLASSES` (`version`, `binary`, `mcp_inventory`, `plugins_hooks_apps`, `sandbox_proof`, `credential_root`, `timeout`, `unknown`; `reviewer-print.ts`, #722): `prepare` tracks the proof leg it is on, and `timeout` means a metadata command ran out of its 2 s or the 12 s budget, never an abort. The host builds the reason only through `reviewerUnavailableReason(cause, detail)`: a fixed sentence per class, plus a credential-root path class (`~/.docker: a dangling link`) as the only detail. The one exception is a failed `start()` whose error is a version refusal (`VERSION_REFUSAL`) or comes from a `nativePolicy` backend: the daemon shows that sanitized message, capped at 1000 characters (#691). `ReviewerPrintPeer` classes a missing or invalid receipt as `unknown`, a `launch.current()` change before spawn as `binary`, and any other start error as `unknown`. Keep admission refusals separate from start failures: skip only a stale-bound request with one task-id log and no reroute; reroute a still-current request whose policy changed as `no_verdict`, without marking the reviewer unavailable. The daemon writes that reason to the `reviewerUnavailable` map (read by routing and shown as the `reviewer` line of `ahub status`), and a `hub.log` line names the class. Never add native CLI text, a path, a version output or a token to the class, the reason or the log.
@@ -141,5 +145,5 @@ Scope: the Codex, ACP and Pi adapters and the Pi extension (turn `generation` an
141
145
  - The class-budget choice is the hub's own option (`ACTION_BUDGET_OPTION`), answered to the agent as its own `allow_once`; only that choice calls `ActionBudgets.approve`, and a refusal answers cancelled. Never let an ordinary allow seed the ledger. Claude never consults the ledger. `ActionBudgets.approve` consumes a slot when another approval landed since its challenge was issued (requests are concurrent; a reset would let N+1 calls through), and the daemon revokes a peer's entry on its `offline` transition and before creating a new owned adapter, so a resumed session id never revives an allowance.
142
146
  - Do not claim enforcement a native mode bypasses: Kimi's `yolo` and `auto` send no request for a call they allow, so `ahub status` lists that peer as not enforceable until it is back in `ask`.
143
147
  - The floor (#999 T3, `action_policy.floor`) is the only place the classifier is told a push or pull request is ordinary: `classifyAction(..., floor?: FloorFacts)` runs `floorPush` and `floorPullRequest` only for a bare command (an `unwrapped` wrapper, `-c`, `--git-dir`, `--work-tree`, an expansion or a `cd` it cannot place keeps the class), and without a `FloorFacts` argument it is byte-identical to before. The flag is read only through `ActionBudgets.floorOn()`; the daemon's `floorFacts(peer)` supplies the facts per execution root (the default branch from the same authoritative remote read as the task shell, cached and refreshed at every `action_policy` reload; `inOwnRepository` only when git proves the top level is that root and its origin is the project's repository). A fact that is missing or unproven is the strict case; never add an allowed form to `floorRefspec` or `floorPullRequest` without a test that pushes the matching unforced and forced, default-branch, tag and opaque variants. A new allowed class change belongs there and in `test/action-floor.test.ts`, never in the shared `classify` branches.
144
- - Floor facts hold only until the first earlier segment on the line that could change them. In `floorInert` (`src/hub/action-class.ts`), `FLOOR_INERT_PROGRAMS` and `FLOOR_INERT_GIT` are allowlists: any other program or git subcommand, a `FIND_WRITERS` primary, `rg --pre` or a glob argument ends the facts for the rest of the line. Before adding a program or git subcommand to either allowlist, show that no form of it can write a file, git config or a ref, or run another command, and add a test in `test/action-floor.test.ts` where its writing form still asks. Adding to `FIND_WRITERS` only makes the floor stricter. Revisit if the facts are ever re-read per segment.
148
+ - Floor facts hold only until the first earlier segment on the line that could change them. In `floorInert` (`src/hub/action-class.ts`), `FLOOR_INERT_PROGRAMS` and `FLOOR_INERT_GIT` are allowlists: any other program or git subcommand, a `FIND_WRITERS` primary, `rg --pre` or a glob argument ends the facts for the rest of the line. Before adding a program or git subcommand to either allowlist, show that no form of it can write a file, git config or a ref, or run another command, and add a test in `test/action-floor.test.ts` where its writing form still asks. Adding to `FIND_WRITERS` only makes the floor stricter. Revisit if the facts are ever re-read per segment. A redirect to a device file is not an output for the destroy rule (#725, #1193): `DEVICE_OUTPUTS` in `src/hub/action-class.ts` lists exactly `/dev/null` and `/dev/tty`, matched as written and never resolved, prefix-matched or normalized. `/dev/stdout`, `/dev/stderr` and every `/dev/fd/<n>` (1 and 2 included) stay destroys, because on Linux they reopen the file fd 1 or 2 already points at and a `>` truncates it (`sh -c 'echo x >/dev/stdout' >>/tmp/log`; the person's ruling after review of PR #1202); so do `/dev/sda`, `/dev//null` and `/dev/null/x`, and a device target never hides a real one beside it. The redirect is still recorded (`redirected`), so the read-only fast path in `floorInert` stays closed. Add a device to the list only with proof that opening it for writing cannot change a file and that no descriptor stands behind it, and a test in `test/action-class.test.ts` beside the lookalike rows.
145
149
  - 2026-11-02 (#1089): a terminal the hub opens for a start through Orca (`src/hub/terminal-open.ts`, #269) is opened in the worktree `resolveStartTerminalWorktree` finds by asking `orca worktree list` for the one whose path equals this project's root, never in `ORCA_WORKTREE_ID`: that variable names the terminal this process was started from, which is another project's when `ahub --project`, the dashboard manager or an upgrade brought the project up. `startTerminalCandidate` (the environment or a recorded launch) only answers the synchronous "could Orca be used" question in `available()`. The match is exact because `ahub <peer>` compares the terminal's worktree root with the project root by equality; a parent, a child and an archived entry are not matches, two matches and a cut-off list that lacks the root are refusals, and each refusal names the cause and the next step and carries no path. Verified against Orca 1.4.245: `worktree list --json` rows carry `id` and `path`, with `truncated` and `totalCount` beside them. A change to that shape is found by the fake-Orca test in `test/start-modes.test.ts` only if its fixture is updated with it, so recheck the real output when Orca moves (the read-only probe is `orca worktree list --json`; never create or write to a live pane).
@@ -7,6 +7,7 @@ Scope: quota and native context readings, checkpoints, pause, handoff and resume
7
7
  - A reading has its own timestamp. Numbers that arrive through a file (`claude-usage.json`) carry the file's `at`; a window whose `resetsAt` has passed says nothing any more.
8
8
  - On resume the notice is published before the peer is released, so it leads the first delivery.
9
9
  - The coordinator only lifts its own pauses: `manualPaused` in the daemon keeps a `ahub pause` in place, and `ahub resume` refuses while a budget record is open.
10
+ - `Bus.pause` and `Bus.resume` are transient and write no bus snapshot (#1204): the bus's `paused` set is not in `BusSnapshot`. Each owner persists and re-applies its own pause (`budget_pauses` for a budget record, `setManualPaused` for `ahub pause`, `conductorHolds` for a conductor hold); never restore a pause from the bus snapshot or add a `persist()` back for one.
10
11
  - The status line tee must never fail or slow the render: no throw, original command run with the same stdin, 5 s cap.
11
12
 
12
13
  - Keep native context occupancy (`src/hub/context-window.ts`, readers in `src/adapters/definitions/native-context.ts`) separate from quota accounting; never use Codex's accumulated `total` as occupancy or estimate Pi counters. Host code reads quota and context only through `readNativeQuota` (`budget.ts`) and `readNativeContext` on the resolved definition's declared `budget.reader` / `budget.context`; no declared reader is unknown, never an empty reading (rule: `adapters.md`).
@@ -6,12 +6,13 @@ Scope: delivery, digests and condensation, reply addressing, priority and limits
6
6
  - Lifecycle `deliveryHeld` fences both drain/pull and important steering before owner teardown awaits; retain newly queued work for an explicit restart without altering manual, budget or conductor pauses.
7
7
  - Apply reloaded `batch_max`, `batch_ms` and `queue_cap` only through `Bus.configureScheduling` (#439): it validates the values and changes later batching and admission only. Never re-arm an existing batch deadline, trim or reorder queued work, or touch prefaces, pauses, recovery holds or in-flight delivery from it.
8
8
  - A failed digest is retried one envelope at a time, so a poison envelope cannot take its neighbours down with it.
9
+ - Preserve typed `failureCause` through journal transitions, direct holds, recovery and explicit retry; an absent cause on an older row stays unknown, never inferred from its reason or body. Read unresolved failed counts only through `failedDeliveryCounts()` / `unresolvedFailedCounts()`, without hydrating payload history; exclude explicitly resolved rows, including failed retry parents, and keep unavailable counts distinct from zero. These diagnostics never authorize replay or change delivery disposition (#1178).
9
10
  - An `important` envelope being steered is not in the queue while the steer is in flight; queue it first and an idle transition delivers it twice.
10
11
  - `WsPeer.steer` exists only for a push channel session: set at attach, cleared on detach, roster detach and a pull-only attach, and it refuses anything but important work in the current busy turn (#493).
11
12
  - `replyParent()` decides what a reply answers (highest hop, never the `hub` preface). Use it for deliveries and steers alike, or the hop cap can be reset.
12
13
  - Report a send's outcome from `Bus.publishResult`, never from `publish()`'s bare target list: a hop-capped send answers `ok: true, dropped: "hop"` with the hop-cap notice, and the channel tool and `ahub say` print that notice, never "no peers attached" (#546). A native peer's `onMessage` returns that `PublishResult` on a hop drop, its send tool (Pi, the local worker) reports it as not delivered, and no off-roster retention note follows a hop drop (#550).
13
14
  - What a peer is handed (`out`) and what it stands for (`originals`) differ once a delivery is condensed: the bus keeps both, registers `out` so `reply_to` resolves, puts the originals back on a `failed_safe` receipt and holds any other failure (a thrown `deliver`, a later `onFailed`, a `needs_review` receipt) as `needs_review` with its originals, for `ahub queue resolve`, and counts no attempt when the peer merely got busy while the delivery was prepared. A delivery with an `important` envelope, an invocation kind or a remote sender is never condensed; a remote recipient (`remoteId`, rooms #941) gets one envelope per delivery with its queue deadline, never a preface, batching delay, condensation or steer.
14
- - Invocations (#445 C2, #986): an `invocation` or `invocation_result` (`isInvocationKind`) goes alone in `take` and `takeBudget`, is never condensed, and is never steered into: both steer sites and `steerQueued` ask `invocationTurnOpen` first. In `onMessage`, a peer's answer goes to `onAnswer` before `parseMarker` and before any publish, when it names the request (`inReplyTo` in the handed delivery) or names nothing while the invocation turn is open. A taken answer, or a throwing hook, publishes, stores and emits nothing.
15
+ - Invocations (#445 C2, #986): an `invocation` or `invocation_result` (`isInvocationKind`) goes alone in `take` and `takeBudget`, is never condensed, and is never steered into: both steer sites and `steerQueued` ask `invocationTurnOpen` first. In `onMessage`, a peer's answer goes to `onAnswer` before `parseMarker` and before any publish, when it names the request (`inReplyTo` in the pushed `lastDelivery` or the pulled `lastPulled`) or names nothing while the invocation turn is open. A pull is kept in `lastPulled`, never in `lastDelivery`, so it cannot replace a pushed invocation's answer parent (#1192). A taken answer, or a throwing hook, publishes, stores and emits nothing.
15
16
  - An invocation is never replayed through ordinary retry (#991). `retryDelivery` refuses a row that holds one. An uncertain or thrown delivery holds the peer (`recoveryHeldPeers`) and calls `onInvocationUncertain` instead of `failed()`. `failed()` allows at most one automatic retry, and only when `canRetryInvocation` returns true; unknown or a throw refuses.
16
17
  - Add an envelope kind only to `KINDS` (`envelope.ts`); the journal, restart reader, deferred-hub check and held hints read that one list. Test room provenance with `remoteEnvelope(env)`, never `from.includes("@")`: a relayed room invocation arrives `from: hub` with `refs.invocationRemote` (#992).
17
18
  - `Envelope.session` (agent-hub#1036) marks a sender that is a distinct session, `from` being its minted `<peer>~<origin>` id (rules: `daemon.md`). It grants nothing; keep it on queued and journaled originals, because the `session_identity` recovery guard reads it from unsettled deliveries.
@@ -23,8 +24,9 @@ Scope: delivery, digests and condensation, reply addressing, priority and limits
23
24
  - Record exact task-offer reads only through `DeliveryJournal.completeDelivered` on an active-fenced adapter completion, correlated reply or actual pull, atomically with completion. Capture original offers before terminal pruning and record their ids, never condensed ids; accepted receipts, generic transitions, operator/hub dispositions and restored completed rows are not read evidence.
24
25
  - The `onImportantQueued` nudge for a pull-only peer (#398) is advisory: it never dequeues, steers, journals or settles, and a throw from it never stops an enqueue.
25
26
  - Merge recall and restored prefaces through `mergePrefaces`/`uniqueMemoryLines`; preserve distinct context and new notes, deduplicate memory identities, and keep colliding short prefixes distinct. Use `recallDisplayText`/`recallMemoryLines` before display caps: suppress only matching automatic approval outcomes, retain nonempty rejection details, preserve id headers and complete-word excerpts, and keep token trimming at line boundaries. Keep these changes display-only.
26
- - Observe memory rendering from final `out`, not `originals`, and suppress telemetry when either contains private envelopes. A pull emits rendered/delivered observations only after its journal transaction commits; observer exceptions must never change delivery settlement.
27
- - Classify automatic held-row completion only through `supersededDeliveryReason`: every original must be a hub presence or a provably moot hub task/review notice. An owner offer (`refs.taskOffer`) is moot only while that recipient owns the task in `in_progress`, `in_review` or `changes_requested` and its own `accepted` entry follows the latest ownership event within the 4096-entry bound; a person's acceptance, an unmarked reminder or missing proof keeps the hold (#434). Unknown/live/mixed rows stay held; keep `deliveryTaskContext` display-only. Tag only current-owner notices as owner audience; leave former-owner withdrawals untagged until the task closes. For cancelled tasks, settle earlier workflow notices but retain the stop notice whose `refs.taskCancelled` matches the cancellation revision. Preserve the requested-stop exclusion in `daemon.md`. Record the hub actor through `resolveDelivery`, restoring presence originals through `restorePreface` only for hub completion (person completion stays consumed); run on hold entry, restore, recovery release and board changes, never replay uncertain work. List every hold once in status, console notices and route explanations (#434).
27
+ - Observe memory rendering from final `out`, not `originals`, and suppress telemetry when either contains private or `isReviewObserverExcluded` envelopes or `reviewObserverInputExcluded` holds for the peer (#1056). A pull emits rendered/delivered observations only after its journal transaction commits; observer exceptions must never change delivery settlement.
28
+ - Read `Bus.onDelivered`'s optional persisted delivery ID only at its positive-ACK observation site; isolate fallible observers so they cannot skip invocation, pending-delivery or supervision bookkeeping.
29
+ - Classify automatic held-row completion only through `supersededDeliveryReason`: every original must be a hub presence or a provably moot hub task/review notice. An owner offer (`refs.taskOffer`) is moot only while that recipient owns the task in `in_progress`, `in_review` or `changes_requested` and its own `accepted` entry follows the latest ownership event within the 4096-entry bound; a person's acceptance, an unmarked reminder or missing proof keeps the hold (#434). Unknown/live/mixed rows stay held; keep `deliveryTaskContext` display-only. An owner-audience `review` (`refs.taskRecipient: "owner"`: a review package or verdict) is never moot (#1181). Tag only current-owner notices as owner audience; leave former-owner withdrawals untagged until the task closes. For cancelled tasks, settle earlier workflow notices but retain the stop notice whose `refs.taskCancelled` matches the cancellation revision. Preserve the requested-stop exclusion in `daemon.md`. Record the hub actor through `resolveDelivery`, restoring presence originals through `restorePreface` only for hub completion (person completion stays consumed); run on hold entry, restore, recovery release and board changes, never replay uncertain work. List every hold once in status, console notices and route explanations (#434).
28
30
  - `resolveDelivery` mutates queues in place; a refused or failed resolution puts back only that peer's queue and preface and keeps every pause. Never reload a snapshot on a live bus. Keep the selected delivery id and journal revision fixed in console row actions; retry/discard/completed never change a task role or cancel a native turn.
29
31
  - Recovery-deferred hub notices (`deferredHub`, #478) are accepted work kept outside the queues and the carried-queue integrity projection: never put them in `queues` or the integrity view while held. Every read of pending work includes them (`pendingEnvelopes`, `withdraw`, `revokeSupervision`, `queuedBacklogIds`, `get`, reset). Past `MAX_DEFERRED_HUB` refuse the publish; never drop a kept notice.
30
32
  - Release moves each deferred notice through `publishNow` and checkpoints it before delivery, events or nudges run. A failed checkpoint restores only publication state (the affected queue arrays in place, deferred, seen and withdrawn ids, pending outcomes) and keeps the hold. Never reload a snapshot or touch prefaces, pauses or the recovery mode from it. A successful release clears the deferral mode (`deferHub`) after `flushDeferredHub()` and before dropping `recoveryHeld`, so a later bare hold or `fenceRecovery` queues hub notices; a failed flush keeps both the hold and the deferral mode (#541).
@@ -43,4 +45,8 @@ Scope: delivery, digests and condensation, reply addressing, priority and limits
43
45
  - Concurrent-edit fan-out is linear in affected tasks: retain each notice's own-task stale/owner fence, but never persist the same change-set notice once for every task pair. Preserve per-task-pair research evidence separately (#576).
44
46
 
45
47
  - Completed-change notices (#713): `Tasks.tellCompleted` publishes one notice per recipient per done through `whileOpenFor`, naming all of that recipient's overlapping tasks, and only for tasks the recipient accepted (`ownerAcceptance` plus an in-work state): never one per overlapping task, never for an offered or proposed task, never again for a repeat done without superseding. The envelope carries `refs.completedFor` (the done task); `Bus.enqueue` replaces an earlier queued, never-attempted notice for the same `completedFor` and recipient through `discardQueued` (a `superseded completed-change notice for task #N` journal outcome), and never touches a user message or another hub notice. `relevant` keeps a grouped notice while any of its tasks is still open for the recipient. Do not widen `completedFor` to anything but the done task id.
46
- - `resolveDelivery` (#713): a `queued` delivery was never handed to the peer, and a `failed` one was refused before it ran (#1103), so a busy peer blocks discarding neither; `dispatching` and `accepted` stay refused as `active delivery`, and unrelated resolutions keep the busy fence. Only the recipient may explicitly complete/discard its own `needs_review` row through `resolveOwnDelivery` with an observed revision and reason (#1079), recording the peer actor through the existing journal audit. Another peer, remote-room disposition and active dispatching/accepted row refuse; this never replays the originals. Every other operator resolution stays fenced while busy. `resolveOwnDelivery` refuses with its own `PreEffectToolRefusal`, which names the cause and the next step, never through `resolutionRefusal`. A row that carries an invocation stays person-only (#1112). The daemon names the refusal from a closed list (`resolutionRefusal`), never from free error text. `queue discard --peer` goes through `resolveDelivery` row by row, reading each row's current revision just before its discard, never the revision listed at the start. Without `--state` it takes the peer's `queued` rows and its unsettled `failed` rows (wire `states`, sent only to a hub whose `features` list `queue_discard_states`; `needs_review` still needs an explicit `--state`). A row a resolution already settled (`settledIds()`) is history: `queue list` hides it unless `all`, and resolving it again is refused as `already resolved by <action>`, never `stale revision` (#1098, #1103).
48
+ - `resolveDelivery` (#713): a `queued` delivery was never handed to the peer, and a `failed` one was refused before it ran (#1103), so a busy peer blocks discarding neither; `dispatching` stays refused as `active delivery`, and so does `accepted` except for its own explicit channel's completed/discard (#1172, below); unrelated resolutions keep the busy fence. Only the recipient may explicitly complete/discard its own `needs_review` row, or for a peer with `acceptedSettlement: "explicit"` its own `accepted` row, through `resolveOwnDelivery` with an observed revision and reason (#1079), recording the peer actor through the existing journal audit. Another peer, remote-room disposition and active dispatching/accepted row refuse; this never replays the originals. Every other operator resolution stays fenced while busy. `resolveOwnDelivery` refuses with its own `PreEffectToolRefusal`, which names the cause and the next step, never through `resolutionRefusal`. A row that carries an invocation stays person-only (#1112). The daemon names the refusal from a closed list (`resolutionRefusal`), never from free error text. `queue discard --peer` goes through `resolveDelivery` row by row, reading each row's current revision just before its discard, never the revision listed at the start. Without `--state` it takes the peer's `queued` rows and its unsettled `failed` rows (wire `states`, sent only to a hub whose `features` list `queue_discard_states`; `needs_review` still needs an explicit `--state`). A row a resolution already settled (`settledIds()`) is history: `queue list` hides it unless `all`, and resolving it again is refused as `already resolved by <action>`, never `stale revision` (#1098, #1103).
49
+ - Accepted channel deliveries (#1172): an `accepted` receipt means the channel transport took the notification, never that the session handled it. `expireAcceptedDeliveries`, run from the daemon's 5 s invocation sweep with the host-only `DaemonOptions.acceptedSettlementNow` clock, moves an explicit channel's `accepted` row older than `bus.accepted_settle_ms` (`acceptedSettleMs`, default 10 min) to `needs_review` with `ACCEPTED_SETTLE_TIMEOUT_REASON` under a revision fence. It holds the peer, routes an invocation through `onInvocationUncertain` and never replays. A repeated `accepted` receipt never renews the deadline. `settleSupersededDeliveries` and supervision skip timeout rows; an important, task or review timeout gets one ids-only conductor status notice unless `conductor.feed` is `off`. The explicit channel's `inbox` reply, a refused one included, carries `held` metadata for its own accepted and `needs_review` rows (`heldDeliveryHints(peer, true)`), or `{ unavailable: true }`, never an empty claim.
50
+ - Review provenance (#1056): `reviewResponse` runs on every publish (`publishResult` and the daemon's channel `send`). It strips a caller's `refs.reviewPackage`/`refs.reviewResponse` and stamps a host-derived `reviewResponse` only on a chat or status that answers a review-scoped parent (`lastDelivery`, `lastPulled`, the bounded `reviewParents`, or `reviewObserverScope`, whose failure counts as scoped); never trust a peer's marker or match content. `beforeReviewInputHandoff` runs before every native handoff (steer, drain, pull); a throw leaves the work queued as `ReviewInputHandoffError` and is never a storage error. `publishReviewEnvelope` republishes a saved package only when `reviewEnvelopeStatus` is `absent`, refuses `failed`, `discarded` and `unknown`, and never replays a published one. Terminal pruning keeps hub review-package rows (`refs.reviewPackage` `review-*`). The bus constructor restores from `transportSnapshot()`; only recovery uses the strict `snapshot()`, which refuses malformed review provenance.
51
+ - Room delivery rows (#1204): a remote (`remoteId`) `needs_review` row holds its peer only when it carries an invocation; an ordinary room chat uncertainty never blocks later queued work. Settle, requeue, hold or resume a remote row only through the room adapter's host ports (`settleRemoteReceipt`, `requeueRemoteNotSent`, `holdRemoteMissingIntent`, `holdInvalidatedRemoteOutcome`, `resumeRemotePending`). Each is bound to the current adapter, room and member, revision-fenced and refused after a person disposition. `deliveryReceipt` never calls `completeDelivered` or `deliveryFailed` for a remote peer, and an uncertain invocation stays person-only. At journal open, `recoverInterrupted` keeps an active single-envelope, non-invocation remote row active only while the bus snapshot still holds its room, member and an unexpired queue deadline; every other `dispatching`/`accepted` row becomes `needs_review`.
52
+ - Closed publication (#1222): once the `Bus` is closed, `publish` returns `{ targets: [] }` before touching the journal or observers, so a late room or presence publication after shutdown writes nothing; a live storage error still propagates. Keep the closed check first when you touch the publish path.