@staix/agent-hub 0.30.0 → 0.32.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 (130) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +13 -7
  3. package/docs/agent-notes/README.md +1 -0
  4. package/docs/agent-notes/adapters.md +5 -2
  5. package/docs/agent-notes/budget.md +1 -0
  6. package/docs/agent-notes/bus.md +11 -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 +7 -3
  10. package/docs/agent-notes/tests.md +6 -4
  11. package/docs/design/adapters.md +52 -0
  12. package/docs/design/agent-hub-architecture.md +46 -0
  13. package/docs/design/bus.md +178 -1
  14. package/docs/design/task-board.md +82 -0
  15. package/docs/dogfood.md +23 -4
  16. package/docs/events.md +3 -0
  17. package/docs/memory.md +17 -1
  18. package/docs/operations.md +355 -78
  19. package/docs/profiles.md +35 -0
  20. package/docs/quickstart.md +24 -5
  21. package/docs/security.md +178 -60
  22. package/docs/specs/2026-09-19-agent-hub-design.md +127 -14
  23. package/docs/specs/2026-09-19-multi-project-design.md +7 -2
  24. package/docs/verification/2026-11-04-1171-channel-client-evidence.md +31 -0
  25. package/docs/verification/2026-11-05-0.31.0.md +182 -0
  26. package/docs/verification/2026-11-05-claude-implementer-2.1.318.md +70 -0
  27. package/docs/verification/2026-11-05-hardcoded-values-classification-rules.md +311 -0
  28. package/docs/verification/2026-11-05-hardcoded-values-ratchet.md +55 -0
  29. package/docs/verification/2026-11-06-0.32.0.md +149 -0
  30. package/docs/verification/2026-11-06-rooms-presence-cost.md +152 -0
  31. package/docs/verification/claude-shell-wrapper-native.py +1 -1
  32. package/docs/verified.json +14 -14
  33. package/package.json +3 -4
  34. package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
  35. package/plugins/agent-hub/server.js +4393 -3609
  36. package/src/adapters/acp.ts +5 -4
  37. package/src/adapters/channel-peer.ts +72 -3
  38. package/src/adapters/claude-channel-evidence.ts +52 -0
  39. package/src/adapters/claude-channel.ts +61 -18
  40. package/src/adapters/claude-implementer.ts +29 -7
  41. package/src/adapters/claude-print-child.ts +12 -2
  42. package/src/adapters/codex-appserver.ts +13 -12
  43. package/src/adapters/compat.ts +3 -3
  44. package/src/adapters/definitions/builtin-metadata.ts +13 -13
  45. package/src/adapters/definitions/qwen.ts +1 -0
  46. package/src/adapters/local-worker.ts +61 -18
  47. package/src/adapters/pi.ts +48 -6
  48. package/src/adapters/registry-types.ts +12 -0
  49. package/src/adapters/registry.ts +2 -0
  50. package/src/adapters/reviewer-print.ts +50 -11
  51. package/src/broker/server.ts +44 -13
  52. package/src/broker/store.ts +38 -8
  53. package/src/cli/answer-args.ts +50 -0
  54. package/src/cli/attention.ts +17 -2
  55. package/src/cli/backlog-return-args.ts +37 -0
  56. package/src/cli/console-question.ts +110 -0
  57. package/src/cli/console-state.ts +78 -17
  58. package/src/cli/console.ts +54 -1
  59. package/src/cli/handoff-args.ts +15 -5
  60. package/src/cli/handoff-output.ts +15 -2
  61. package/src/cli/help.ts +8 -5
  62. package/src/cli/identity.ts +13 -3
  63. package/src/cli/main.ts +128 -16
  64. package/src/cli/memory.ts +107 -3
  65. package/src/cli/output.ts +29 -6
  66. package/src/cli/pet.ts +4 -4
  67. package/src/cli/results-args.ts +30 -0
  68. package/src/cli/room.ts +20 -10
  69. package/src/cli/runtime-version-warning.ts +50 -0
  70. package/src/cli/status-lines.ts +41 -1
  71. package/src/cli/tail-render.ts +1 -0
  72. package/src/cli/upgrade-interactive.ts +14 -2
  73. package/src/hub/action-class.ts +9 -1
  74. package/src/hub/board.ts +369 -18
  75. package/src/hub/bus.ts +511 -53
  76. package/src/hub/completion-detail.ts +50 -0
  77. package/src/hub/completions-store.ts +168 -0
  78. package/src/hub/completions.ts +120 -0
  79. package/src/hub/config-apply.ts +7 -5
  80. package/src/hub/config-reload.ts +10 -1
  81. package/src/hub/control-client.ts +40 -4
  82. package/src/hub/daemon.ts +1576 -139
  83. package/src/hub/delivery-failure.ts +14 -0
  84. package/src/hub/delivery-hints.ts +14 -4
  85. package/src/hub/delivery-journal.ts +751 -42
  86. package/src/hub/delivery-superseded.ts +4 -1
  87. package/src/hub/envelope.ts +3 -3
  88. package/src/hub/events.ts +6 -0
  89. package/src/hub/handoff.ts +145 -6
  90. package/src/hub/hub-tools.ts +56 -7
  91. package/src/hub/manager.ts +59 -3
  92. package/src/hub/memory-policy.ts +44 -1
  93. package/src/hub/memory-sync-policy.ts +109 -0
  94. package/src/hub/memory-sync-state.ts +145 -0
  95. package/src/hub/no-writes.ts +1 -1
  96. package/src/hub/operational-settings.ts +11 -2
  97. package/src/hub/operational-view.ts +12 -1
  98. package/src/hub/peers.ts +25 -2
  99. package/src/hub/planning.ts +6 -1
  100. package/src/hub/question-desk.ts +201 -0
  101. package/src/hub/question-detach.ts +6 -0
  102. package/src/hub/questions.ts +260 -0
  103. package/src/hub/research-evidence.ts +106 -0
  104. package/src/hub/restart.ts +14 -0
  105. package/src/hub/review-anchor.ts +181 -0
  106. package/src/hub/review-brief.ts +13 -0
  107. package/src/hub/review-comments.ts +115 -0
  108. package/src/hub/review-findings.ts +7 -2
  109. package/src/hub/review-material.ts +76 -0
  110. package/src/hub/routing.ts +3 -1
  111. package/src/hub/task-sweep.ts +2 -0
  112. package/src/hub/tasks.ts +577 -63
  113. package/src/memory/capture.ts +5 -2
  114. package/src/pet/config.ts +25 -13
  115. package/src/rooms/commands.ts +61 -16
  116. package/src/rooms/link.ts +338 -36
  117. package/src/rooms/liveness.ts +11 -0
  118. package/src/rooms/remote-peer.ts +186 -19
  119. package/src/rooms/service.ts +475 -35
  120. package/src/rooms/status.ts +8 -1
  121. package/src/rooms/store.ts +447 -65
  122. package/src/rooms/wire.ts +24 -6
  123. package/src/ui/index.html +142 -9
  124. package/src/ui/needs.ts +10 -4
  125. package/src/ui/plugin-questions.ts +56 -0
  126. package/src/ui/plugin-results.ts +81 -0
  127. package/src/ui/plugin.ts +207 -27
  128. package/src/pet/window.html +0 -114
  129. package/src/pet/window.jxa.js +0 -122
  130. package/src/pet/window.ts +0 -154
@@ -1,6 +1,6 @@
1
1
  # Operations guide
2
2
 
3
- This guide describes ahub 0.30.0 and control protocols 16 through 17. Live verification
3
+ This guide describes ahub 0.31.0 and control protocols 16 through 17. 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
@@ -211,8 +211,7 @@ Home/End sequences is that many key presses, so a held key repeats its move. Hom
211
211
  or `<` jumps to a panel's first row and End or `>` to its last; the Events tab
212
212
  follows new events while the last row is selected and stays on a row you moved to.
213
213
  `w` opens the dashboard outside the Peers tab; in Peers it edits the selected
214
- peer's WIP limit. `o` opens the pet window; `:ui --all` and `:pet --window --close`
215
- work on the command line too. In the Peers tab the planner's row reads `(planner)`, and
214
+ peer's WIP limit. `:ui --all` works on the command line too. In the Peers tab the planner's row reads `(planner)`, and
216
215
  `l` then `y` makes the selected peer the planner (`ahub settings set planner <peer>`). In the Events
217
216
  tab a row's header line is tokenized and coloured exactly as the same event in
218
217
  the stream; only the header is tab-flattened before tokenizing. Body lines retain
@@ -397,8 +396,11 @@ observed revision from the bounded held-row metadata returned with `hub_inbox`.
397
396
  Use `completed` after handling the delivery, or `discard` when it should not be
398
397
  acted on, with a nonempty reason. The journal records the recipient actor and
399
398
  reason. Another peer's row, a stale revision, remote-room disposition, active
400
- `dispatching`/`accepted` rows, a paused or held peer, recovery, shutdown or storage
401
- holds, and a reason that is private or sensitive all refuse. A row that carries an
399
+ `dispatching` rows, a non-channel peer's `accepted` row, a paused or held peer,
400
+ recovery, shutdown or storage holds, and a reason that is private or sensitive
401
+ all refuse. An explicit push-channel session may also settle its own `accepted`
402
+ row as described under [accepted channel delivery settlement](#accepted-channel-delivery-settlement-1172).
403
+ A row that carries an
402
404
  invocation stays person-only (`ahub queue show <id>`, then `ahub queue resolve
403
405
  <id>`), because the invocation may still be running. This never retries a message, changes a task
404
406
  role or cancels the native turn; recipient retry keeps its existing one-use rules.
@@ -1011,11 +1013,18 @@ plugin stale when the module changed after setup, so run `ahub setup` again afte
1011
1013
  - **Panels.** The plugin's panels are the dashboard's seven views, and the panel is the `ahub ui`
1012
1014
  page itself, following acon's theme. It reaches project hubs only through the manager's own
1013
1015
  listing and forwarding (`ahub ui --all`): registered project and instance ids, the manager's
1014
- closed action list and the seven view names. There is no new protocol or action.
1016
+ closed action list and the seven view names. There is no new protocol. An answer, a result
1017
+ receipt or a review comment sent from acon goes through a manager port of its own (`answer`,
1018
+ `receipt`, `consoleControl`) that needs acon's verified person `meta`.
1015
1019
  - **Inbox.** acon's inbox lists each running project's pending approvals (Allow once / Deny,
1016
- answered with the hub's own `permit` and an option the request offers) and in-review tasks whose
1017
- reviewer is you (Open board). A local-worker or Pi approval stays deny-only there, as on the
1018
- dashboard: answer it with `ahub permit` after `ahub tail`.
1020
+ answered with the hub's own `permit` and an option the request offers), open questions and unread
1021
+ results (see the Questions and Results sections), the settled-worktree alert and in-review
1022
+ tasks whose reviewer is you (Open board). A local-worker or Pi approval stays deny-only there, as on the
1023
+ dashboard: answer it with `ahub permit` after `ahub tail`. A reply holds at most 50 items, most
1024
+ urgent first (decisions, reviews, results, candidates, then alerts), and counts the rest (#1196).
1025
+ Running projects are asked at once, each within about 2 s; a project that misses that keeps the
1026
+ items it last returned unless an action has happened since. A poll within 1 s reuses the last
1027
+ reply, one within 10 s gets it at once while a refresh runs, and any action drops it.
1019
1028
  - **Confirmation.** `stop`, `permit`, `send`, the settings writes (`setting`, `setting_undo`,
1020
1029
  `setting_reload`, `config_edit_apply`), `start_peer`, `backlog_split` and the inbox's Allow once
1021
1030
  and Deny answer `needsConfirm` with a preview first; acon runs them after you confirm.
@@ -1255,6 +1264,10 @@ to look for a queued or needs_review delivery with
1255
1264
  with `ahub task assign <id> <owner>`. Nothing resends automatically: a blind resend can
1256
1265
  duplicate a delivery that actually left.
1257
1266
 
1267
+ #### Accepted channel delivery settlement (#1172)
1268
+
1269
+ An explicit Claude push-channel session can settle or discard its own accepted row with the shown revision and a public-safe reason, but an `accepted` receipt still means transport acceptance only. If the session sends neither native completion nor an explicit disposition within `bus.accepted_settle_ms`, the next maintenance sweep moves the row to `needs_review` with a fixed timeout cause; no replay occurs. The default is ten minutes (range 1,000-86,400,000 ms). A live `hub_inbox` result includes that session's own accepted and held row ids and revisions, including when the accepted delivery blocks pulling, so it can inspect the row and use `hub_delivery_resolve`. If held metadata cannot be read, the response carries a fixed unavailable sentinel; the plugin keeps any pulled messages and directs the session to `ahub queue list --peer <peer-id>`, without exposing the metadata error. Important, task and review timeouts publish one status notice to the configured conductor with only delivery id, peer id and revision when `conductor.feed` is on; `feed: off` remains the existing opt-out, and no conductor role is forced. The timeout row is excluded from task-state superseding and remains visible until a person or the explicit channel session records a disposition. Recovery refuses a target without `accepted_delivery_settlement` while such a timeout row is retained; projects without one remain compatible with older targets. The row transition is durable before notice publication; if the hub crashes or notice publication is refused by journal storage after the transition, the held row remains authoritative and visible and the host does not invent a replay path.
1270
+
1258
1271
  Reviewer candidates follow `[classes.review].peers`, then attached peers with a
1259
1272
  `reviewer` role in `roles`. The owner cannot review its own task. If none is available,
1260
1273
  the assignment output and console notice say that `done` will approve without review
@@ -1791,9 +1804,10 @@ ahub study experiments --campaign rc-0123456789ab [--after 50] # read the reco
1791
1804
  ids, counts, states and provenance and stubs its text as `[pii]`. Study tool arguments and results are kept out
1792
1805
  of hub-managed memory capture.
1793
1806
  - **Upgrades.** The daemon advertises `research_records`, `research_dispatch`, `research_dropped_offers`,
1794
- `research_experiments`, `research_comparisons` and `research_runners`. While the matching rows exist (for
1795
- `research_runners`, stored runner receipts or any `research.runners` block), a restart, upgrade or
1796
- recovery onto a release without the feature is refused.
1807
+ `research_experiments`, `research_comparisons`, `research_runners` and `research_evidence` (#931). While the
1808
+ matching rows exist (for `research_runners`, stored runner receipts or any `research.runners` block; for
1809
+ `research_evidence`, stored claim evidence links), a restart, upgrade or recovery onto a release without the
1810
+ feature is refused.
1797
1811
 
1798
1812
  ## Hub memory
1799
1813
 
@@ -2451,6 +2465,7 @@ A guarded file edit that the daemon keeps at its accepted value remains visible
2451
2465
  | 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 |
2452
2466
  | 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 |
2453
2467
  | 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 |
2468
+ | Routing | `bus.accepted_settle_ms` (1000-86400000 ms, default 600000) | `config.local.json` | live; the next accepted-channel timeout sweep, approximately every five seconds |
2454
2469
  | 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 |
2455
2470
  | 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 |
2456
2471
  | 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 |
@@ -2530,7 +2545,7 @@ The H0 operational items of `tasks.ts`, `routing.ts` and `task-sweep.ts` that st
2530
2545
 
2531
2546
  #### Bus and delivery conversions (#489 H3)
2532
2547
 
2533
- The five `bus.*` rows above use the H1 descriptor, schema, settings writer and live
2548
+ The five pre-existing #489 `bus.*` rows above use the H1 descriptor, schema, settings writer and live
2534
2549
  reload path. Unset defaults equal `MAX_ATTEMPTS`, `DIGEST_MAX`, `PULL_BUDGET_BYTES`,
2535
2550
  `NOTES_KEPT` and `TERMINAL_CAP`. Only `bus.max_attempts` may rise; the other four
2536
2551
  can only tighten today's ceilings. A reload does not trim/reorder queues or
@@ -2538,6 +2553,8 @@ prefaces, reset attempts, re-arm batch deadlines, prune history, or change a pau
2538
2553
  or in-flight delivery. Terminal history is a target: protected retry parents and the current mutation receipt may
2539
2554
  keep the count above it. The settings add no conductor authority.
2540
2555
 
2556
+ `bus.accepted_settle_ms` (#1172) adds a timeout for explicit push-channel acceptance, rather than converting an existing constant. Its ten-minute default allows normal session initialization, compaction and handling while bounding a silently lost notification; the channel can confirm earlier, before a long task finishes. The setting is read at the next maintenance sweep. Expiry changes only the delivery journal to `needs_review` and never replays the message; native completion, task completion and task approval remain separate.
2557
+
2541
2558
  The [H3 H0 ledger](verification/2026-10-30-bus-delivery-operational-settings.md)
2542
2559
  lists all 59 unique inventory identities (5 converted and 54 excluded). The
2543
2560
  persisted `SEEN_CAP`/dedupe contract and withdrawn cancellation evidence stay
@@ -3072,6 +3089,21 @@ The states are `queued`, `dispatching`, `accepted`, `completed`,
3072
3089
  the native bridge received the delivery; it does not prove that the agent
3073
3090
  executed it. Only native completion or a correlated reply can settle a receipt.
3074
3091
 
3092
+ Failed deliveries carry a public-safe `failureCause` from the fixed classes
3093
+ `peer offline`, `adapter refused`, `push error`, `turn aborted`, and `timeout`
3094
+ (#1178). `ahub queue show` and `ahub queue list --json` expose the class, never
3095
+ message text as the cause. Older rows without it have an unknown cause; the hub
3096
+ does not reconstruct one from their reason or body. This diagnostic does not
3097
+ change settlement: a possibly effected delivery remains `needs_review`, not a
3098
+ safe failure merely because its cause is known.
3099
+
3100
+ `ahub status` shows each peer's unresolved failed count above zero, with
3101
+ `ahub queue discard --peer <id> --state failed` as the next step. Inspect the
3102
+ rows first, then supply the discard command's reason and confirmation. A row
3103
+ already settled by an explicit resolution is excluded, including a failed
3104
+ parent left behind by `retry`; its linked attempt has its own state. Older hubs
3105
+ without this count and unavailable delivery storage do not imply a zero count.
3106
+
3075
3107
  The console interface is:
3076
3108
 
3077
3109
  ```text
@@ -3141,12 +3173,47 @@ them while the hub was down does not keep the short list (#490). A status probe
3141
3173
  that is unavailable right after a restart is retried, then the list is
3142
3174
  announced anyway.
3143
3175
 
3176
+ ### Hand a peer to a standby session (#1174)
3177
+
3178
+ `ahub status` lists live standby connections with their connection identity and
3179
+ reported PID when available. From the person's terminal, request a handover:
3180
+
3181
+ ```sh
3182
+ ahub peer takeover claude
3183
+ ```
3184
+
3185
+ The Peers panel in `ahub console` offers the same takeover action with `o`. An explicit
3186
+ console selection stays bound to the displayed standby id; the CLI's peer-only
3187
+ form chooses the oldest eligible live standby. The result reports that a claim
3188
+ was requested. Refresh status to confirm attachment. If no eligible standby
3189
+ remains, the command refuses without detaching the current owner.
3190
+
3191
+ Use `ahub peer takeover claude <standby-id>` to select the exact connection shown
3192
+ by status. If a request times out, inspect status before retrying; the handover
3193
+ may already have started.
3194
+
3195
+ This detaches the old hub connection, not its native process. The old plugin's
3196
+ next hub tool explains that the person detached it; it cannot reconnect and take
3197
+ the peer back. Unconfirmed deliveries follow their normal settlement rules, and
3198
+ takeover itself never marks them completed. A reported PID or connection UUID is
3199
+ not native session or permission evidence. Agent and delegated live-test writes
3200
+ are refused by the same person-only path.
3201
+
3202
+ A previous plugin may display a generic terminal-admission refusal after takeover;
3203
+ run `ahub setup` and restart that plugin for the specific person-detachment text.
3204
+ With an older daemon the plugin keeps the prior standby reconnect behavior, and
3205
+ an unsupported takeover request names the unavailable operation. No live account
3206
+ or native process handover is certified by the fixture tests.
3207
+
3144
3208
  ### Replaced or ended Claude sessions (#551)
3145
3209
 
3146
3210
  A replaced session says it is standing by. Its reconnect requests cannot reclaim
3147
3211
  an attached or claiming peer, even if its status-file read missed that holder. A
3148
3212
  channel-capable session may still replace a pull-only holder once its claim has
3149
- finished. Starting a new session intentionally keeps the newest-hello behavior.
3213
+ finished. A session whose plugin does not opt in to standby (an older plugin, or
3214
+ tools mode) keeps the newest-hello behavior; an opted-in session started while
3215
+ another holds the peer stays a standby until the person runs `ahub peer takeover`
3216
+ or the holder leaves (#1174).
3150
3217
 
3151
3218
  A plugin carrying an older launch id than this daemon's current per-peer Claude
3152
3219
  launch is refused with close 4430. It stops reconnecting, and hub tools report that
@@ -3494,18 +3561,55 @@ are not active pruning references.
3494
3561
  A scoped upgrade leaves the global CLI and shared Claude plugin unchanged.
3495
3562
  Update the CLI separately with `bun add --global @staix/agent-hub@<version>`, then
3496
3563
  run `ahub setup` for the plugin. `ahub doctor` reports either tool when its known
3497
- version is older than the selected running daemon; it installs nothing.
3564
+ version is older than the selected running daemon; it installs nothing. After a
3565
+ completed or scheduled upgrade, `ahub upgrade` prints the same step when the CLI
3566
+ or the plugin is older than the daemon it installed (#1179). It prints nothing with
3567
+ `--json` or for `ahub restart`, and the advice never fails an upgrade.
3498
3568
 
3499
3569
  | Runtime | Control protocol window | Legacy status field |
3500
3570
  | --- | --- | --- |
3501
3571
  | 0.12.20 through 0.25.0 | 16 only | `protocol: 16` |
3502
- | 0.26.0 through 0.30.0 | 16 through 17 | `protocol: 16`, `protocolMin: 16`, `protocolMax: 17` |
3572
+ | 0.26.0 through 0.32.0 | 16 through 17 | `protocol: 16`, `protocolMin: 16`, `protocolMax: 17` |
3503
3573
 
3504
- New clients choose the highest common protocol. The runtime supports N-1, so the
3505
- published protocol-16 client and channel plugin remain usable; unrelated feature
3574
+ New clients choose the highest common protocol. The protocol window remains 16
3575
+ through 17, so the published protocol-16 client and channel plugin remain usable; unrelated feature
3506
3576
  capabilities still require their own labels. A disjoint window refuses with 4426.
3507
3577
  The compatibility-floor `protocol` field remains readable by old clients.
3508
3578
 
3579
+ Release N-1 is counted in minor versions, separately from this protocol window
3580
+ (#1164, family contract entelecheia/ai-workspace#70). For 0.32.x, N-1 is 0.31.0,
3581
+ the latest published patch of 0.31.x (exact `@staix/agent-mem` 0.21.0, control
3582
+ protocol 17 with descriptor floor 16). Every patch on 0.32.x keeps that release
3583
+ baseline. A change that cannot retain it needs a new minor release. The 0.31.x
3584
+ baseline remains 0.30.0 (agent-mem 0.20.1); for 0.30.x it was 0.29.0.
3585
+
3586
+ In the minor release PR, together with its `package.json` version bump, query
3587
+ the published versions of `@staix/agent-hub`,
3588
+ select the latest stable patch of the preceding minor line, and mechanically
3589
+ bump `test/fixtures/release-n-1/package.json` and its own `bun.lock`. Do not
3590
+ merge this bump before or separately from the minor version bump: the guard
3591
+ requires the fixture minor to be the package minor minus one. Update
3592
+ the corresponding release entry in `test/published-protocol-compat.test.ts`
3593
+ with the package version, exact amem dependency and protocol read from that
3594
+ published package. Generate the lock with the pinned Bun, then commit it. The gate installs it separately
3595
+ with `bun install --frozen-lockfile`; the compatibility test uses its unchanged
3596
+ published runtime, client and plugin in both directions. Keep the separate
3597
+ `test/fixtures/protocol-v16` fixture at 0.25.0 while protocol 16 is promised;
3598
+ the previous-minor fixture does not replace protocol-floor coverage.
3599
+
3600
+ Each new minor release's CHANGELOG entry states the selected N-1 version and the
3601
+ compatibility of each surface: wire/protocol, files and format floors, CLI,
3602
+ and MCP tools. State any capability or format ceiling explicitly. Patch release
3603
+ entries contain no new N-1 statement and inherit their minor line's baseline.
3604
+ `release-prepare` moves the Unreleased text mechanically; the release author
3605
+ adds this minor-only statement during preparation and checks the fixture bump
3606
+ in that release PR. Never amend a tagged release section. The 0.30.0 N-1
3607
+ record added by #1164 is an explicitly dated post-release note, published in the
3608
+ 0.31.0 section; it was not part of the published 0.30.0 tag or tarball. Its MCP ceiling includes
3609
+ 0.29.0 free-form `plan.sources` values: 0.30.0 requires registered source ids
3610
+ and rechecks existing stored plans even when accept sends no new plan.
3611
+ No compatibility statement grants release consent.
3612
+
3509
3613
  Guard-aware runtimes read `hub.db` format metadata before startup effects and
3510
3614
  before writable database opens. A newer incompatible format refuses with
3511
3615
  `this project was opened by agent-hub X; use X or later`. The format counter
@@ -5099,6 +5203,15 @@ person/planner, roster, exclusion and capability checks; a planner cannot clear
5099
5203
  or replace a person's reservation. The ordinary `ahub backlog return` form keeps
5100
5204
  its existing reservation-preserving behavior.
5101
5205
 
5206
+ The CLI has the same two edits (#1177): `ahub backlog return <task> <revision>
5207
+ <reason...> [--reserved <peer> | --unreserve]` sends `fields.reserved` as the peer
5208
+ (`--reserved kimi` or `--reserved=kimi`) or `null` (`--unreserve`) with the
5209
+ return, so the reservation moves or clears in the same operation and under the
5210
+ same checks. The flags may stand anywhere among the reason's words and are not
5211
+ part of the reason; at most one of them, and a malformed one is refused before
5212
+ anything is sent. With neither flag the command sends exactly what it always
5213
+ did and the reservation is kept.
5214
+
5102
5215
  Capacity defaults to one per peer. Each declared instance has its own `tasks.wip_limits.<instance>` key. `ahub settings set tasks.wip_limits.codex 2`
5103
5216
  uses the existing machine-local registry path; integers 0..32 apply live (the next
5104
5217
  admission reads the current value), with 0 disabling planner admission for that
@@ -5224,13 +5337,14 @@ built-ins and off for the current console only. `?` lists this under Pet, even
5224
5337
  while drawing is off. The command prompt keeps its original row and width.
5225
5338
 
5226
5339
  `pet.glyphs` selects auto/ascii/blocks; unsupported locales and dumb/linux
5227
- terminals retain the ASCII floor. `pet.blink:false` is static. `pet.palette`
5228
- selects blue, teal (default), violet or ochre (`a`/`b`/`c`/`d`), and `pet.window`
5229
- controls standalone window opt-in; both `pet` (`false` or an option block) and its
5230
- fields validate identically at startup and reload in `.agenthub/config.json` and
5231
- `.agenthub/config.local.json`, apply live as console-local presentation (including
5232
- an open console's next refresh and the standalone pet window), and grant no
5233
- authority (#483). Neither
5340
+ terminals retain the ASCII floor. `pet.blink:false` is static. Both `pet`
5341
+ (`false` or an option block) and its fields validate identically at startup and
5342
+ reload in `.agenthub/config.json` and `.agenthub/config.local.json`, apply live
5343
+ as console-local presentation on an open console's next refresh, and grant no
5344
+ authority (#483). Legacy `pet.palette` still validates blue, teal, violet or
5345
+ ochre (`a`/`b`/`c`/`d`) for configuration compatibility, but has no rendering
5346
+ effect after the window retirement. A legacy `pet.window` value is ignored with one notice; a
5347
+ window-only local block preserves inherited `pet:false`. Neither
5234
5348
  choosing nor drawing a character changes authority, config files, task state or
5235
5349
  polling. Explicit `ahub pet` and text status use the configured line face; prompt
5236
5350
  tokens remain character-independent. Native glyph width and tmux are unverified.
@@ -5259,8 +5373,9 @@ receive fixed structural guidance, with no peer or first-row fallback. No
5259
5373
  workflow action is issued and no filter is changed by attention navigation.
5260
5374
  Invalid/legacy attention hides its badge and restores the base title.
5261
5375
 
5262
- The in-page pet, its SVG and character/pixel payload are removed. The standalone
5263
- macOS window owns character presentation. Existing snapshot poll, session,
5376
+ The in-page pet, its SVG and character/pixel payload are removed. Floating pet
5377
+ presentation belongs to acon Mini; the standalone macOS host is retired (#1012).
5378
+ Existing snapshot poll, session,
5264
5379
  request types and CSP hashes remain; no route, request or timer is added.
5265
5380
  Browser screenshots/CSP-console verification are separate manual evidence on
5266
5381
  the implementation PR; VM/HTTP fixtures do not establish human visual results.
@@ -5316,45 +5431,23 @@ Release cut audits return exact counts with at most 32 example IDs per category;
5316
5431
 
5317
5432
  Release cards include task states in their refresh key, so completion of an outside prerequisite updates blocked counts even when the release and audit preview are unchanged.
5318
5433
 
5319
- ## Standalone macOS pet window (#391)
5320
-
5321
- From a person's terminal, `ahub pet --window` starts one floating, transparent
5322
- JXA panel per project; `ahub pet --window --close` closes it. Other platforms
5323
- print unsupported and retain `ahub pet` / `ahub pet --short`. An agent-marked
5324
- shell may read the terminal pet but cannot start or close the window.
5325
-
5326
- Set `pet.window: true` in `.agenthub/config.json` or `config.local.json` to start
5327
- it with `ahub up`. A window-only local opt-in preserves inherited `pet:false`
5328
- for terminal/status/dashboard presentation; explicit local character options
5329
- retain their existing presentation override behavior. The
5330
- window uses the configured built-in character, full pixel frame and blink
5331
- choice. Its body opens `ahub ui`; the pencil opens an inline console message
5332
- box (Enter sends through `ahub say`, Esc cancels). Drag the body to move it;
5333
- `pet-window-pos.json` remembers the position, with an on-screen fallback if a
5334
- saved display has disappeared. The panel joins all Spaces without a Dock icon.
5335
- The 150x150 panel keeps the status pill and message composer hidden until the
5336
- pointer is over the character. Deep state colors and a dark rim shadow keep the
5337
- pixel silhouette readable on light backgrounds. `pet.palette` selects the base
5338
- color: teal (default, b), blue (a), violet (c) or ochre (d), each with its own
5339
- rim line; the waiting/blocked/review/offline colors stay fixed. While the
5340
- window is key, 1/2/3/4 switch blue/teal/violet/ochre and save the choice.
5341
- From a terminal, `ahub pet --palette <blue|teal|violet|ochre>` writes the
5342
- choice to `pet.palette` in `.agenthub/config.local.json`.
5343
- While working, the pixel art bobs in a 0.7-second stepped CSS loop; reduced-motion preferences keep it still.
5344
-
5345
- A detached Bun host refreshes `pet-view.json` once per second using `readPet`
5346
- and `loadPetConfig`. The file contains only state/counts/character/blink and
5347
- built-in pixels. It uses no dashboard session or HTTP listener. An unavailable
5348
- hub displays Offline for two seconds, then the panel and host exit. Closing
5349
- JXA exits the host too. `pet-window.pid` holds the host's process signature;
5350
- an uncertain owner is retained rather than signalled. A stale owner requires
5351
- an explicit `--close` before restarting; a malformed pidfile requires operator
5352
- inspection. A send failure is unconfirmed: check the stream before retrying.
5353
-
5354
- Native transparency/floating and the bridge were verified in the macOS spike.
5355
- Real keystrokes, dragging and persistence, Spaces, message delivery and body
5356
- click need the manual PR checklist before merge; automated fixtures do not
5357
- establish those native results.
5434
+ ## Floating pet in acon Mini (#1012)
5435
+
5436
+ Run `acon ui` and open Mini for the floating pet. The former
5437
+ `ahub pet --window [--close]` command prints that pointer and exits successfully
5438
+ from a person terminal, an agent shell or `--live-test`. It starts no window,
5439
+ host, JXA process or network connection. The console's `o` shortcut and automatic
5440
+ window startup from `ahub up` are retired.
5441
+
5442
+ The terminal pet remains available through `ahub pet`, `ahub pet --short` and
5443
+ the console's line/mini/full frames. `ahub pet --palette <blue|teal|violet|ochre>`
5444
+ is also retired: it validates the argument, prints the same Mini pointer and
5445
+ writes no configuration. Existing `pet.window` settings are ignored with one notice;
5446
+ remove the obsolete key when updating configuration.
5447
+
5448
+ The composer already lives in the agent-hub console plugin's **Console message**
5449
+ view (#935), available through `acon ui`. It sends through the existing plugin
5450
+ `send` action and retains that action's audience and authority checks.
5358
5451
 
5359
5452
  ## Additional headless peer instances
5360
5453
 
@@ -5465,7 +5558,7 @@ Default-branch capture runs only at daemon startup when a Claude implementer is
5465
5558
 
5466
5559
  Project Git retains the core policy that clears credential.helper and core.askpass; an HTTPS push needing those helpers may fail, rather than importing credentials or relaxing that policy. Task branch/config proof reads are bounded synchronous reads (1.5 seconds per reader); slow or unknown evidence asks. The existing sandbox profile discovery is also synchronous; moving it to an asynchronous inventory proof with a shared deadline remains a follow-up. The nonce is visible in a permission response, so it proves a bounded grant rather than native-dispatch provenance.
5467
5560
 
5468
- 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), and the [2.1.314 receipt](verification/2026-10-31-claude-implementer-2.1.314.md) repeats it and the host-wrapper procedure on 2.1.314 (with 2.1.310 as a control), and the [2.1.316 receipt](verification/2026-10-31-claude-implementer-2.1.316.md) repeats both on 2.1.316 (with 2.1.314 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.
5561
+ 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), and the [2.1.314 receipt](verification/2026-10-31-claude-implementer-2.1.314.md) repeats it and the host-wrapper procedure on 2.1.314 (with 2.1.310 as a control), and the [2.1.316 receipt](verification/2026-10-31-claude-implementer-2.1.316.md) repeats both on 2.1.316 (with 2.1.314 as a control), and the [2.1.318 receipt](verification/2026-11-05-claude-implementer-2.1.318.md) repeats both on 2.1.318 (with 2.1.316 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.
5469
5562
 
5470
5563
  A headless Claude reviewer is run by the hub instead (task 223): declare
5471
5564
  `"claude-review": { "kind": "claude", "roles": ["reviewer"], "start_mode": "headless" }`
@@ -5603,7 +5696,22 @@ Native launcher connections retry transport timeouts/unclean closes with bounded
5603
5696
 
5604
5697
  ## amem runtime (agent-mem#13 P4)
5605
5698
 
5606
- The hub exact-pins @staix/agent-mem 0.20.1 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.
5699
+ The hub exact-pins @staix/agent-mem 0.21.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.
5700
+
5701
+ ## Person-run backlog legs (#1176)
5702
+
5703
+ A leg only the person can run (live receipts, their accounts, machines or approvals) is marked with `ahub backlog person <id> <revision> true <reason...>` (`false` clears it; `ahub backlog plan <id> <revision> '{"needsPerson":true}' <reason...>` is the same write). The mark is the person's alone: a planner or any agent that sends `fields.needsPerson`, or plans a marked card, is refused with the reason. A marked card keeps its `reserved` peer as the "who will run it" hint, and:
5704
+
5705
+ - `tasks.auto_pull` and a planner's `hub_backlog_pull` never pull it (the pull refuses with "person-run leg"), and the planner notice never names it as ready;
5706
+ - `assign()` gives it no owner and no reviewer, whatever its reservation says, and `ahub route explain` prints the same `person-run` line (it is the same function);
5707
+ - the idle sweep never escalates it, and a marked card in To Do is not reported as stalled for lacking an owner;
5708
+ - it counts toward no planner's WIP;
5709
+ - a planner's `hub_backlog_split` of a marked card is refused (its children would be unmarked cards an agent could take); the person's own split marks every child person-run, so person-only work stays person-only until the person clears each mark;
5710
+ - an agent's `accept` and `acceptReviewed` of a marked card are refused, a queued offer for a card marked since is dropped at delivery, and `hub_task_assign` names no agent owner of a marked card (the conductor, a planner and the person alike: clear the mark first);
5711
+ - marking a card an agent already owns (an offer in its queue, or accepted work) is refused with the way out (the owner declines, or `ahub task cancel`), so a mark never leaves an agent on the card;
5712
+ - `ahub backlog list` shows it as `<peer> (person)` and ends with `waiting on you: #N...`, and `ahub status` carries a `WAITING ON YOU #N` line (ids only).
5713
+
5714
+ An unmarked reserved card behaves exactly as before. The column is additive (`needs_person`, default 0): an older hub ignores it, and a card marked by this hub is then offered by the older one as an ordinary reserved card.
5607
5715
 
5608
5716
  ## Backlog issue identity and notes
5609
5717
 
@@ -5656,7 +5764,7 @@ The selected project's configured conductor may run the commands below through i
5656
5764
  | logs / export | Bounded daemon projections, with unsupported/private records withheld; logs -f ends when authority ends |
5657
5765
  | settings set | Only the thirteen existing live registry keys listed below |
5658
5766
 
5659
- 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.
5767
+ 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, `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.
5660
5768
 
5661
5769
  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.
5662
5770
 
@@ -5705,6 +5813,8 @@ restoration uses the target-owned snapshot through a tools connection.
5705
5813
 
5706
5814
  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.
5707
5815
 
5816
+ Claude channel push mode also requires the plugin's direct parent process row to begin with the exact `claude --dangerously-load-development-channels <plugin-or-server-target>` argv prefix. The inherited `AGENTHUB_CHANNEL=1` marker alone is insufficient: Claude's `daemon run` and `bg-spare` children inherit it but do not load the development channel, and print forms ignore it. The bounded `ps -ww` read is display/attachment evidence, not authentication; unreadable, truncated, changed-parent or unrecognized rows attach tools-only and keep messages for `hub_inbox`. `ahub status` shows the attach mode and the closed evidence label.
5817
+
5708
5818
 
5709
5819
  Only the conductor that created the restart or authorized upgrade may run
5710
5820
  `recovery resume|abort` for that operation. A different conductor cannot take
@@ -5847,6 +5957,73 @@ to ordinary automatic assignment. This PR changes no live repository setting or
5847
5957
  routing template; real-use adoption metrics remain separate from fixture checks.
5848
5958
 
5849
5959
 
5960
+ ### Presence and full broker queues (#1204 T1)
5961
+
5962
+ Unchanged presence snapshots are skipped. Presence is ephemeral: an offline
5963
+ member does not accumulate observations, and the broker keeps only the latest
5964
+ per sender for connected members. Explicit refresh exchanges still work.
5965
+
5966
+ On `queue_full`, the link retains the sealed frame and retries with exponential
5967
+ backoff from 250 ms to at most 30 s. Applications stay in the existing local
5968
+ outbox, and later sequences wait for broker storage acceptance. Stop/refresh
5969
+ clears connection timers. The broker storage ACK remains transport metadata;
5970
+ application receipt and retry rules are described below. No layer cap is raised.
5971
+
5972
+
5973
+ A hub uses the ephemeral bit only after the broker advertises it to that
5974
+ connection. Against an older broker, presence uses the legacy wire shape and
5975
+ chat stays usable; upgrade the broker to avoid offline presence retention.
5976
+ The supporting broker withholds ephemeral frames from older member hubs; they
5977
+ must upgrade to see upgraded hubs' presence. Chat wire shapes remain unchanged. See [wire compatibility](design/bus.md#room-wire-compatibility-1204-t1).
5978
+
5979
+ ### Room liveness and graceful stop (#1204 T3)
5980
+
5981
+ On an ephemeral-capable broker, idle hubs request fresh signed presence every
5982
+ 30 seconds. After three missed periods (90 seconds), room presence becomes
5983
+ `unknown` in exposed-peer status. This expiry is advisory only and never blocks
5984
+ outbound transport: current signed roster, exposure, link and Bus proofs still
5985
+ control sending, including broker queueing for members that are not live. An
5986
+ explicit signed offline presence keeps its existing handling. Saved presence after
5987
+ restart is not a fresh liveness observation.
5988
+ The optional room advisory presence is separate from native peer state; neither
5989
+ an offline observation nor expiry stops the other person's agent process.
5990
+
5991
+ With a legacy broker, status says `liveness not tracked (broker without ephemeral)`.
5992
+ Upgrade the broker to enable periodic refresh and aging. Legacy connections keep
5993
+ identical-snapshot dedupe and ordinary state-change publication, with no
5994
+ periodic refresh or freshness expiry. Older clients may ignore the new advisory
5995
+ status fields. Full capability advertisement remains #1204 T4.
5996
+
5997
+ A graceful room stop immediately fences new publications and attempts one signed
5998
+ offline snapshot behind an already attempted FIFO frame, bounded to two seconds
5999
+ before disconnect. Durable sends and receipts remain for recovery. If that
6000
+ attempt fails or the broker is blocked, supporting recipients expire their last
6001
+ observation through aging. A crash has the same aging fallback. A legacy broker
6002
+ has no such expiry guarantee.
6003
+
6004
+ ### Inspect outstanding work before rotating an epoch (#1204 T3)
6005
+
6006
+ Before an epoch-changing owner `expose`, `invite --accept`, `revoke` or an
6007
+ existing-room `join --roster`, the person preview prints sorted outstanding
6008
+ sealed delivery ids, without message text. It excludes work still queued only
6009
+ in the local Bus. The operation rechecks the exact id set and roster digest
6010
+ with link application publication quiesced; if either changed, obtain a fresh
6011
+ preview rather than reuse the old confirmation.
6012
+
6013
+ The epoch commit quarantines old active sealed intents and removes their
6014
+ sendable ciphertext. Their local delivery becomes `needs_review` with reason
6015
+ `room epoch rotated; inspect ahub queue show <id>`; `ahub room status team` labels
6016
+ its `holdReason` as `epoch rotated`. Inspect each id with
6017
+ `ahub queue show <delivery-id>` and choose a person disposition. Rotation does
6018
+ not assert completion, cancel a native turn or replay old ciphertext. Existing
6019
+ terminal results and person dispositions remain unchanged. T2 already fences
6020
+ the crash gap between the Store commit and local journal update.
6021
+
6022
+ If the command says `epoch rotated but delivery journal holds are unavailable`,
6023
+ the epoch has committed and old ciphertext is fenced. Inspect `ahub room status`
6024
+ and the affected queue ids before taking further action; do not assume the
6025
+ command rolled the roster back.
6026
+
5850
6027
  ## Screened room chat (#446 X4)
5851
6028
 
5852
6029
  After joining and confirming fingerprints, expose each receiving native peer with
@@ -5876,10 +6053,15 @@ peer queue. Native/model execution is independent. Incoming messages remain
5876
6053
  untrusted text and grant no local task, role, approval, tool or control authority.
5877
6054
 
5878
6055
  Offline queues are capped at 200 per room. In untracked config.local.json, optional
5879
- `rooms.ack_timeout_s` defaults to 600 and `rooms.queue_ttl_h` to 24. A restart,
5880
- expired deadline or uncertain publication leaves `needs_review`. It is not
5881
- replayed automatically. A late signed receipt is visible as evidence in queue
5882
- inspection; the person's disposition is still required. Old text cannot be
6056
+ `rooms.ack_timeout_s` defaults to 600 and `rooms.queue_ttl_h` to 24. The ACK
6057
+ deadline schedules another attempt under the same application id, while the
6058
+ original queue TTL stays fixed. Active sends resume after restart. Only queue
6059
+ expiry or a permanent refusal ends automatic sending; an ordinary remote chat
6060
+ `needs_review` row does not hold the rest of that peer's queue. A valid late signed
6061
+ receipt can settle an unresolved ordinary remote row once, with an ids-only event.
6062
+ Invocation uncertainty keeps its person-only hold; a late transport receipt is
6063
+ advisory and does not assert the invoked operation's outcome. The person's prior
6064
+ disposition is never overridden. Old text cannot be
5883
6065
  retried to a replacement fingerprint reusing a label. An unproved terminal retry
5884
6066
  refuses and requires a deliberately authored new message.
5885
6067
  An expired or missing original enqueue deadline likewise refuses retry without
@@ -5888,6 +6070,20 @@ recording a successful disposition. Send a new message deliberately instead.
5888
6070
  The ACK timeout starts when the application is handed to the room link, even if
5889
6071
  the broker never confirms storage. A reconnect or restart requests fresh signed
5890
6072
  presence from the other hub without waiting for its peer state to change.
6073
+ The receiver commits receipt ciphertext to its local durable outbox before
6074
+ transport-ACKing an admitted chat, then replays the receipt until broker storage
6075
+ confirmation. This stays safe at broker capacity one and across receiver restart;
6076
+ transport settlement still proves admission rather than native execution.
6077
+ An application awaiting local admission may instead ACK after its crypto inbox
6078
+ entry commits durably. Later admission uses the same durable receipt outbox;
6079
+ the waiting chat does not pin broker capacity.
6080
+ Pauses and lifecycle/recovery holds also block resends. A pause after local
6081
+ enqueue retains the same delivery id and original expiry. Missing local durable
6082
+ intent after restart is held as `needs_review`; inspect the delivery named by
6083
+ `ahub queue show` before choosing a disposition.
6084
+ Restart and inbound-receipt recovery apply any previously recorded person
6085
+ disposition before automatic settlement. A known signed transport outcome remains
6086
+ evidence alongside the decision; it does not undo the person's disposition.
5891
6087
  Capacity refusal of one incoming chat retains that pending chat while allowing
5892
6088
  later packets and receipts through; a later online or person-disposition event
5893
6089
  rechecks admission. Merely reading room status applies no disposition.
@@ -5916,7 +6112,10 @@ Neither sends, re-admits, or produces a signed completion. The displayed state i
5916
6112
 
5917
6113
  Application-id and trace metadata retain 30 days/10,000 per room, evicting settled
5918
6114
  rows first. Protected uncertainty remains until person disposition. Outside that
5919
- retention no historical app-id or trace claim is made; permanent seq floors and
6115
+ retention no historical app-id or trace claim is made. Automatic resend can reach
6116
+ this ceiling and admit an application again after settled eviction; the guarantee
6117
+ is "exactly once within dedupe ceiling". Pinning retryable identities or #737
6118
+ receipt confirmation would extend it. Permanent seq floors and
5920
6119
  MAX_HOP 3 still apply. Status prioritizes uncertainty and bounds each direction's
5921
6120
  rows to 200; queue inspection remains the delivery-history entry point. Broker,
5922
6121
  TLS/proxy and real two-machine/account receipts remain separate live proof.
@@ -6002,19 +6201,24 @@ settled stubs fit the current response ceiling.
6002
6201
  Work that belongs to another repository that runs agent-hub on this machine goes onto that repository's board, not into this session. Its own roles plan, implement and review it, and each state change it reaches is reported back here. The first case: after a library you maintain publishes a release, raising its exact pin in a consumer belongs on the consumer's board.
6003
6202
 
6004
6203
  ```
6005
- ahub task handoff --project <root|id> [<class>] <title...> [--path <p>]... [--issue <n>] [--task <n>] [--detail <text>]
6204
+ ahub task handoff --project <root|id> [<class>] <title...> [--path <p>]... [--issue <n>] [--task <n>] [--detail <text>] [--duplicate-ok <reason>] [--key <uuid>]
6006
6205
  ```
6007
6206
 
6008
6207
  - `--project` is the other project's canonical root or its id from `ahub projects`. It must be registered and its hub must be running (`ahub up` in that project). Nothing is queued: a hub that is not running is refused with that action.
6009
6208
  - A feature or behavior change first gets a spec issue in the other repository: pass its number as `--issue`. A mechanical chore (an exact version pin, a regenerated file) goes straight to a task.
6209
+ - A spec issue that already has an open task on the other board is refused with those task ids (#1175): a task counts when its lifecycle receipt names the issue, its title has a bare `#N` or an `owner/repo#N` of that repository, or its detail holds the `spec issue: #N in this repository` line a handoff keeps; a closed (approved or cancelled) task and a `[pii]` task do not count, so a private task's id is never named to another project. Use the open task, or pass `--duplicate-ok <reason>` (the tool's `duplicate_ok`, at most 300 characters) when the work is genuinely separate; the reason is kept as a line of the new task. The check runs on the target right before the task is written and stores nothing when it refuses, so a refused duplicate is never offered to an owner or escalated by the idle sweep. A handoff with no `--issue` has nothing to compare and is not refused.
6010
6210
  - `--path` names files in the other project, repository-relative (no absolute path, `~` or `..`). `--task` names your own task this comes from, so a `[pii]` one is refused.
6011
6211
  - `ahub task handoff` is the person's command (an agent's shell is refused it as a console command). Agents use `hub_task_handoff` (the same fields) when they hold the planner or conductor role; `hub_task_handoffs` and `ahub board --handoffs` list what this project handed over and the last state each target reported.
6012
6212
 
6013
- What the other project sees: an ownerless proposed task, made by its hub, whose detail ends with where it came from; `ahub board` and `ahub task show` there name the origin project and peer. Its own routing offers it as it would a local proposal. The requesting peer has no identity or authority there: it cannot accept, review, approve, assign or cancel.
6213
+ What the other project sees: an ownerless proposed task, made by its hub, whose detail ends with where it came from; `ahub board` and `ahub task show` there name the origin project and peer. Its own routing offers it as it would a local proposal. The requesting peer has no identity or authority there: it cannot accept, review, approve, assign or cancel. The receiving project's configured conductor can cancel such a task with `hub_task_cancel` (or `ahub task cancel` as the person), with a reason, while nobody has accepted it and it has no branch, commit, PR, check, done or verdict (#1175); the history records the conductor as the actor, and the origin hears `agent-hub task N cancelled` through the follow-back, never the reason. Any other peer keeps the planner's own-work rule.
6214
+
6215
+ What comes back: each change is recorded in `ahub board --handoffs`, and an agent that handed the task over also gets one `kind: task` message per change, `agent-hub task 123 accepted`, `in review`, `approved: PR #7 at <sha>`, `declined` or `cancelled` (a handoff you made from the terminal sends you no message: read the list). It carries an event name, a PR number and a full head, never the other project's text. If this project's hub was stopped meanwhile, it asks each running target hub where its open handoffs stand when it starts again; a target that is down then is asked at the next start. From #1203 T2 the origin also asks every 5 minutes, and at once when a target's hub restarts or moves, for every handoff that is not cancelled (an approved one for a week), in requests of 200, so a state change that missed the first notify arrives within one period; an event that arrives out of order is ignored by its sequence, and a reviewer's `changes requested` and a reopen are reported (`agent-hub task 123 changes requested`, `reopened`).
6014
6216
 
6015
- What comes back: each change is recorded in `ahub board --handoffs`, and an agent that handed the task over also gets one `kind: task` message per change, `agent-hub task 123 accepted`, `in review`, `approved: PR #7 at <sha>`, `declined` or `cancelled` (a handoff you made from the terminal sends you no message: read the list). It carries an event name, a PR number and a full head, never the other project's text. If this project's hub was stopped meanwhile, it asks each running target hub where its open handoffs stand when it starts again; a target that is down then is asked at the next start.
6217
+ A handoff the target did not confirm (#1203 T1): a timeout or a closed connection does not mean refused, because the target may have filed the task. Each request carries a key (`handoffKey`, a UUID the origin mints), the target files it once, and the origin sends the same key once more and then asks the target by key before it gives up. The command waits for that whole recovery (up to about 90 seconds when the target is slow), not the usual 30. If it still cannot tell, the refusal is `outcome_unknown` and names the key: run the same `ahub task handoff` again with `--key <uuid>` (an agent passes `handoffKey`), and the target answers with the task it already made instead of a second one. The same key with a different request is refused. A target without `task_handoff_key` gets no key: it keeps the old behavior, and `outcome_unknown` then says to check its board (`ahub board` there) before handing the task off again.
6016
6218
 
6017
- Refusals, each named with the action: an unregistered target, this very project, a hub that is not running, a request over 8 KiB or with an unknown field, a caller that is not a planner, the conductor or the person, a private (PII) turn, a `[pii]` task named with `--task`, text that matches the target's PII patterns or that the target's screen cannot clear, a target hub in recovery or shutting down (hand it off again after that ends), and an older target hub (it lacks the `task_handoff` feature: run `ahub upgrade` in that project).
6219
+ Diagnostics and repair (#1203 T3): `ahub task handoff --list` shows every handoff this project made with its age since the last report and whether its target can be reached now: `reachable`, `unreachable` with the cause and what to run (`ahub up` in its root, `ahub upgrade`, `ahub status`), or `moved` when the project id is no longer in `ahub projects` (its root moved or it was removed: run `ahub up` in the new root, then hand the work off again; the old id is not repaired). It reads the registry and the target's descriptor, so it answers at once. `ahub task handoff --sync` (the person or the conductor) asks every target now instead of at the next 5-minute pass and prints the refreshed list; it waits at most 20 seconds, and if several targets are slow or down it prints the list so far with "catch-up still running for N targets: run `ahub task handoff --list` in a minute" (the pass goes on in the background) instead of timing out. A target on another wire version is refused as `older_target` with the upgrade step. An 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 (`handoff catch-up of <project> failed: <cause>`). A catch-up request that gets no usable reply (the target refused it, timed out or dropped the connection) is a failure like any other: its target reads `unreachable` with the cause "no answer to the state request" and the step `ahub status` in that project, one log line is written, and the earlier failure is not cleared. If the pass itself fails, `--sync` answers `synced: false` with that cause instead of `synced: true`.
6220
+
6221
+ Refusals, each named with the action: an unregistered target, this very project, a hub that is not running, a request over 8 KiB or with an unknown field, a caller that is not a planner, the conductor or the person, a private (PII) turn, a spec issue that already has an open task there (the refusal lists their ids; `--duplicate-ok <reason>` overrides it; an issue number only matches work of the target's own repository, and an imported backlog row counts by its `external` issue; a target from before #1175 refuses `duplicate_ok` as an unknown field and the refusal says to run `ahub upgrade` there or hand off without it), a `[pii]` task named with `--task`, text that matches the target's PII patterns or that the target's screen cannot clear, a target hub in recovery or shutting down (hand it off again after that ends), and an older target hub (it lacks the `task_handoff` feature: run `ahub upgrade` in that project).
6018
6222
 
6019
6223
  Recovery: `task_handoff` is its own recovery label. A project that holds a handoff row, in either direction, refuses a recovery target that lacks it.
6020
6224
 
@@ -6063,3 +6267,76 @@ ahub worktrees tidy --confirm <fingerprint> # the person's own terminal: re-r
6063
6267
  - **Tidy is the person's.** `tidy` previews each settled row that is safe to remove and each refused row with its reasons: dirty, locked, commits not pushed to the upstream or no upstream, branch not merged into the default branch, the main checkout, an instance worktree, a bound task that is not approved or cancelled, or anything git could not tell. The preview ends with a fingerprint over the rows. `--confirm <fingerprint>` is console class (an agent can only preview, and `--live-test` refuses it), re-reads everything, refuses the whole run if the set changed, and runs exactly two argv shapes per row, `git worktree remove <path>` and `git branch -d <branch>`, then reads each result back. `--force` and `-D` never appear: git itself refuses a dirty tree or an unmerged branch. A worktree edited between the confirm's re-read and the removal is not guarded by anything but git's own refusal.
6064
6268
  - **The daemon deletes nothing.** The shelf's git port runs only `worktree list --porcelain -z`, `log -1 --format=%ct HEAD` and `rev-parse --absolute-git-dir`, and refuses any other argv before spawning. Removal exists only in the CLI process (`src/cli/worktree-tidy.ts`), which the daemon never imports; `test/worktree-shelf.test.ts` greps the daemon-reachable modules for a removal.
6065
6269
  - **Limits.** At most 256 worktrees are listed and 1024 overrides read; git reads run 4 at a time under a 3 s total budget (5 s per call), asynchronously, so a slow git never holds the daemon. The quiet threshold is the owner's call after a week of use.
6270
+
6271
+ ## Results: what a run you started left behind (#1054)
6272
+
6273
+ A run you started (a message you sent to a peer, from the console, `ahub say` or acon) settles into one **result** when the adapter's own signal says it ended and nothing else is pending: no queued continuation, open permission or question, owned child, running check or held delivery. Peers whose adapter declares no settle signal (Kimi in its own TUI, agy, person-typed Codex turns) leave none. Outcomes are `done`, `failed`, `limit` and `interrupted` (the hub restarted mid-run); an explicit stop by you and a budget pause leave no result. A result is **unread** until you mark it read; nothing a snapshot, `ahub status` or `ahub results` reads ever marks one.
6274
+
6275
+ - `ahub results [--unread]` lists them, newest first, unread ones marked `*`: id, `<peer>: <outcome word>` (a private result is `a private result`), when it settled and how long it ran. It prints no prompt and no reply.
6276
+ - `ahub results --read <id>` marks one read (it must exist and be unread). `--read-all` marks every unread result of the project. `--unread-last` lights the most recent read one again by deleting its receipt.
6277
+ - An unread result is a `result` need at level `watch` in `ahub status` and `ahub console` (ids, the peer and the time only): it never makes the state read as waiting, blocked or review. The console stream tells you once, with no text.
6278
+ - `result_seen` and `results` are console-role control messages that always reply; an agent's connection and an agent shell (`ahub results` is the person's command) are refused, and nothing a peer writes counts as a receipt.
6279
+ - Retention: the table is bounded. At each start the hub keeps the newest 500 READ results and deletes older read ones with their receipts; an unread result is never deleted, and neither is an open run. A hub restart while a peer is under a budget pause ends its open run with no result (a pause stays a budget need, `ahub budget`), whatever work was still pending; any other open run settles `interrupted`, once.
6280
+ - Recovery: a hub from before this feature ignores the tables, so a target without the `completions` label is refused while a run is open (let it settle) or a result is unread (`ahub results --read-all`, or use a supporting release).
6281
+ - **In the dashboard and the acon console (#1054 T6).** The Agents view of `ahub ui` and of the acon panel has a Results section: the unread results (the newest 50, with a count of any older ones and the command that lists them), each with `<peer>: <outcome word>`, when it settled, how long it ran and a one-line **detail**, and the buttons Mark read, Mark all read and Mark last unread again (the relight; it appears only when a read result exists). Opening or refreshing the panel never marks anything read: a poll is not a person looking.
6282
+ - **The detail line is built when you look, never stored.** A completion row keeps envelope ids only, so the head of the request you started (one line, 120 characters) is read from the envelope the bus still holds, passed through the hub's own secret, PII and private-path screens, shown, and thrown away. A private run shows `a private request` without its body being read; a head the screens refuse shows `request withheld by the privacy screen`; a result whose request the bus no longer holds (a long-running hub whose cache turned over, or a restarted one) shows no detail. A live-test dashboard session gets no results and is refused every receipt.
6283
+ - **In the acon inbox.** With a console that advertises both `inbox.result` and `action.meta`, an unread result is an inbox item in the `result` group: kind `result`, title `<peer>: <outcome word>` (`<peer>: a private result` when private), the detail, a chip by outcome (done, failed, limit, interrupted), one `open` verb that links to the Agents panel, and `seen: { action: "result_seen" }`, which acon runs once per page session when the result has been in view (the seeing rule is agent-console#27, not the hub's). With either feature missing, nothing is published and results stay in the Agents panel, `ahub console`, `ahub status` and `ahub results`. A read item leaves the inbox, so **mark unread is not an inbox verb**: it is the panel's button and `ahub results --unread-last`.
6284
+
6285
+ ## Questions for the person: `hub_ask` (#1053)
6286
+
6287
+ A peer asks with `hub_ask` (the same tool for Claude, Codex, Kimi, Jcode, Pi and the local worker). The call waits for the person, then answers one of:
6288
+
6289
+ | status | meaning |
6290
+ |---|---|
6291
+ | `answered` | the person's options, inside the wait window |
6292
+ | `pending` | the window ended; the row stays open and the answer arrives later as one message of kind `answer` whose first line is `reply_to: <questionSet>` |
6293
+ | `superseded` | the person sent this peer a normal message instead; that message is delivered as usual |
6294
+ | `cancelled` | `stopped` (a stop, kill or roster removal), `turn_aborted`, `session_changed` or `expired`, as `reason` |
6295
+ | `unavailable` | `unattended`: the hub is, or the peer was started, with `--unattended`; nothing was asked and nothing waits |
6296
+
6297
+ - **Wait window.** Per adapter (`questionWaitMs`): 45 s for the MCP peers, 300 s for Pi and the local worker. These are provisional until T1 measures each client's real tool-call timeout. Override per kind with `questions.wait_ms.<kind>` (1000 to 600000 ms). The Claude channel holds its control request open past the client's 30 s cap. Pi's bridge lifts its 60 s idle timeout for this one call and streams the reply (headers at once, a space every 15 s, then the JSON), because Pi installs a global undici dispatcher with a 300 s headers and body timeout (Pi 1.4.0, measured `UND_ERR_HEADERS_TIMEOUT`); a client that drops anyway leaves the question open, so its answer arrives as the late envelope.
6298
+ - **Expiry.** `questions.ttl_s` (60 to 2592000, default 86400) expires an unanswered question. The peer gets a hub notice of kind `status`, never an answer, and a waiting call is told `cancelled` with reason `expired`.
6299
+ - **Restart.** Open questions are stored in `hub.db` and survive a restart. A waiting call ends `pending` at shutdown and at a recovery `prepare`; the row stays open and its answer takes the late path. A row whose peer is back with a different native session is cancelled (`session_changed`); a peer that reports no session is never cancelled for it.
6300
+ - **Bounds.** At most 8 open questions per peer and 64 in all: a loop of questions turns into a refusal, not a flood.
6301
+ - **Answering.** Three surfaces, one control message (`answer`, console only; `questions`, console only, lists the open rows). In `ahub console`, `A` answers the oldest open question key by key: a digit picks an option (single choice moves on, multi toggles and Enter moves on), `o` types free text where the question allows it (a paste goes into that text, printable characters only, so a pasted Enter or Esc cannot send or leave the answer), Esc leaves and the question stays open. The question and every numbered option are shown in a wrapped area (the body in panels mode, the stream in stream mode), never in the one-line footer, which holds only the keys and the text being typed, so no option is ever off screen while a digit can pick it. A question id is the agent's, so the flow keeps its picks in Maps: `constructor` and `toString` are valid ids; `n` (the attention key) opens the same flow for the question it selects. The last answer is sent at once with no confirm step: answering is neither outward nor destructive. `ahub answer` lists the open questions; `ahub answer <set> <question>=<option>[,<option>]... [--other [<question>=]<text>]...` answers one set (person only: an agent shell is refused before it reaches the hub). In the acon console an open question is an inbox item (`group: decision`, `kind: question`, title `<peer> asks: <first prompt>`, a stub for a private question) whose `answer` verb carries a form built from the stored row; it needs a console that advertises `inbox.form` and `action.meta`, otherwise the item is a plain decision with an `open` verb that links to the Agents panel. The plugin accepts an answer only with the console's own verified person `meta` (`via: "console"`), and refuses one without it before it reaches the hub; the verified identity is recorded on the answered row and on its `question` event (`person`, acon answers only). A stale or superseded set answers `{ ok: false, error }` naming its terminal state.
6302
+ - **Attention.** An open question is a `question` need (level `act`) and the status `waiting` while one is open, ranked after approvals and permission defaults. It is level-triggered: viewing or listing it never clears it; only an answer, a supersede, a cancel or an expiry does. The model carries the set id, the asking peer and the time, never a prompt or an option. The console reads the list only from a hub whose status lists the `questions` feature, and prints one stream line (peer and count, no text) when a new question arrives.
6303
+ - **Recovery.** A new daemon feature label, `questions`, marks the protocol. Recovery refuses a target without it while a question is open or an answer is still owed (`target lacks questions`), because an older daemon would silently ignore the table; answer or cancel the questions first. A recovery `prepare` ends every waiting call as `pending` at once, so a waiting `hub_ask` never holds the prepare for its window. The Claude plugin reports a hub without the label as "this hub does not support questions".
6304
+ - **Events.** `question` records `set`, `peer`, `event` (`asked`, `answered`, `pending`, `superseded`, `cancelled`, `expired`, `unavailable`), `latencyMs`, `surface`, `reason`, `questions` (a count) and `private`; never any text.
6305
+
6306
+ ## Anchored review comments (#1056)
6307
+
6308
+ Open a task review in acon and refresh explicitly to read current immutable material. Routine dashboard polls do not query GitHub or Git for review comments. Standalone `ahub ui` is read-only. Unavailable public comments retain their stored text in the person view, but Send refuses those selected IDs until their material is available.
6309
+
6310
+ The hub exact-quote limit is 512 UTF-8 bytes, stricter than acon's character allowance; prefix and suffix each allow 64 bytes. A refusal identifies the field and limit. Shorten the selection, or delete and recreate a stale-head draft from refreshed material. Stored selectors keep exact source text. Delete or dismiss requires confirmation.
6311
+
6312
+ An unconfirmed Send keeps its saved package. Inspect `ahub queue list --peer <owner>` and `ahub queue show <id>` before using the confirmed Retry package action in acon. Recovery reuses the saved envelope identity and text; an already published package starts no new turn. A failed or discarded delivery refuses resend with its state and the queue inspection step instead of reporting queued success. Reviewer requests have a distinct marker and their terminal delivery rows are pruned normally. A previously unpublished package whose comments were dismissed or became private refuses: inspect the saved queue, dismiss the old comments and recreate safe comments when appropriate. An unchanged count-only private stub can recover without revealing text. Legacy or unavailable evidence refuses rather than inventing a fresh message. A queued receipt proves publication, not owner consumption. Package receipts retain terminal delivery payloads alongside their board packages until a future archival transaction can replace both with IDs-only tombstones.
6313
+
6314
+ Comment IDs resolve only after final successful `hub_task_done`, not while a check runs or after it fails. Until resolution or person dismissal, open sent comments and outstanding anchored findings keep scoped responses out of memory and observer views; this does not hide the summary or check tail from its intended recipient.
6315
+
6316
+ For a memory note naming that task, anchored-finding scope conservatively lasts until task close, including a finding later marked fixed. Sent-comment task scope lasts until resolution or person dismissal. Unknown or over-budget task metadata refuses that named-task memory write; it does not impose a peer-wide capture hold based on another task. Capture, progress and session titles follow the current review input instead. Public comment edits from another person appear on the next explicit refresh.
6317
+
6318
+ An owner/reviewer change does not dispose of an earlier review delivery. Its original recipient remains excluded from shared-memory writes naming that task while that delivery is queued or unsettled. Dismissing comments does not dispose of the envelope: inspect `ahub queue list --peer <original-recipient>` and explicitly settle the relevant delivery when appropriate.
6319
+
6320
+ Comment resolution does not end the current review input. A delivered quote-bearing input or a submitted anchored verdict keeps that input's capture and shared-memory writes held through the final answer. Ordinary next-turn input can release it where the transport confirms replacement; restored pull-only sessions follow the native-identity rule below. Presence-only traffic does not release the hold; neither does additional input within the same active turn. Previously saved incoming-label bindings to a review-bearing batch are ignored; a later ordinary input can establish a fresh binding. The local worker latches before the whole anchored-verdict tool batch and does not retain that turn in history, even if the verdict is refused. Historical review scope on another task alone does not block an ordinary new turn. Restarting the daemon does not release a surviving session's recorded review-input hold. The journal stores peer identity, session/input ID hashes and receipt-integrity metadata. In an intact, readable, complete store, a new session with no matching held row is clean immediately. Unreadable or corrupt storage, or unconfirmed coverage for the current session, keeps capture, progress, titles and unscoped shared-memory writes excluded. A validated release receipt confirms its own session while other unconfirmed identities remain held. For an identified pull-only session, a positive held row stays excluded across restart until confirmed native identity changes. Its own incomplete marker clears after verified ordinary replacement or a confirmed new session; legacy coverage alone does not create a sticky hold. A peer with no stable native session id can still receive review input under an in-RAM unknown-held scope, with a peer-level incomplete marker retained until release. For a never-identified peer with no positive or incomplete SID record, a later verified ordinary replacement in that daemon run releases its RAM hold and own marker; in an intact complete store that released absence stays clean after restart. If earlier positive or incomplete SID records exist and the current id cannot be matched, delivery remains available but observer writes stay held. `ahub status` names the peer as "held: session unidentified, earlier review input" and asks for reconnection so its real id is known. A fresh confirmed SID uses its own state; old SID rows remain protected. First confirmation persists any current RAM quote under that SID before clearing the peer-level marker. This records current-input replacement, not erasure of vendor-native transcript history. No launch, attach or socket id is treated as native identity. Presence and additional input within a known active held turn cannot clear the hold. Required provenance persistence happens before a review package is handed over, and a failure refuses that handout. Recovery targets must support `review_observer_input_state` while held or unknown provenance exists. Provenance receipts are bounded at 4096 rows per project. Legacy coverage completes once the journal has no unsettled pre-upgrade review-scoped delivery. At complete coverage, NULL clean receipts are pruned because absence is clean. Positive and uncertain holds remain; a confirmed new session clears only the peer-level incomplete marker, not an older session's held row. Closed fresh-reviewer receipts can still be removed with matching disposal proof. The cap refuses new quote handoffs rather than evicting surviving or uncertain holds. A reviewer still receives the owner's completion summary; recipient output remains distinct from observer exclusion.
6321
+
6322
+ A local tool batch containing an anchored `hub_review` refuses `hub_remember` and `hub_task_propose` before either can write, including siblings listed before the verdict and later steps of that held turn. Save notes or propose new work in a later ordinary turn. Reading a non-empty inbox during an invocation does not replace its answer-routing input; pulled review provenance still protects observers. If provenance admission refuses `hub_inbox`, reconnect the session and retry; if the refusal persists, inspect `ahub status` for delivery journal health. The refused delivery remains queued.
6323
+
6324
+ ### Review-input provenance health (#728)
6325
+
6326
+ Read the separate provenance health in `ahub status`. Legacy coverage can remain incomplete while a captured review delivery is unsettled; inspect it with `ahub queue list` and `ahub queue show <id>`, and give it an explicit disposition rather than replaying it as a repair. Reopen/import conservatively recaptures all handed non-terminal rows present then, so a guarded B can keep this window open after the original legacy A settles. Timeout to needs_review does not itself close the window. Delivery still proceeds, and legacy coverage alone does not create a sticky pull-only hold. Ordinary input and confirmed session changes release only the applicable incomplete marker. A healthy positive hold is expected protection, not a journal failure.
6327
+
6328
+ Corrupt or unreadable provenance stays unknown and held. The status next step directs the person to arrange review-input provenance inspection/repair while preserving task rows, delivery dispositions and known positive holds. No reset command or automatic repair is provided in this slice. Do not replace the whole database with an older copy or edit a live journal to make the warning disappear; that can lose disposition evidence and replay work. Keep affected work held until a maintainer can recover trustworthy provenance.
6329
+
6330
+ ## Memory sync diagnosis (#1173)
6331
+
6332
+ A ready memory profile does not prove a successful sync. Read the separate automatic-sync result and timestamp in `ahub status` or Memory. It resets to not run after daemon restart; it is not a machine-global record of manual CLI attempts. Missing remote configuration is the neutral `not-configured` state; an interrupted/stopped attempt is neutral `stopped`. Both keep their safe next step and produce no failure log under `quietSyncError`.
6333
+
6334
+ For policy-declined rows, a person runs `ahub memory declined --json` to inspect IDs and safe rule categories, never note text. The pinned agent-mem 0.21.0 public `inspectSyncPolicy` API bounds physical outbound inspection for shared-v1 and generation profiles. A complete result supplies the declined IDs and rules; `complete:false` means the scan is unavailable, stale or over its diagnostic bound, not zero declined rows. The command stays read-only and rechecks its profile binding. No migration or policy edit is performed automatically.
6335
+
6336
+ The hub projects verified withheld/exported counts when a trusted SDK sync returns a successful placement result. That projection is covered by an injected-result fixture, not an activated-store sync. Agent-mem 0.21.0 requires a fresh `placementAttestation` even for an already active v3 store; the default hub supplies no such integration and therefore cannot perform that sync yet. No attestation is fabricated. A nonzero withheld count in a successful result is not a transport failure and does not mean those rows were deleted, rewritten or published. Inspect the IDs and rules before deliberately changing any source or approved policy; a forget operation does not remove its original text from physical history.
6337
+
6338
+ A legacy store without active per-row placement still blocks a sync containing declined rows. Status says: `machine sync blocked: N declined rows; per-row placement is not active on this store (agent-mem has no activation command yet, agent-mem#281). Inspect with ahub memory declined.` Here `N` is the known declined count. Agent-mem 0.21.0 has no activation command, so there is no migration or activation step to run in this release. The hub does not replace the core placement protocol or fabricate its activation proof. [Agent-mem#281](https://github.com/STAIxBWLB/agent-mem/issues/281) owns the missing supported activation path.
6339
+
6340
+ The bounded diagnostic addresses #1173 AC4; AC5 has result projection only; supported activation and fresh host-attestation integration remain necessary for default-host v3 sync. #1173 remains open until #281 ships and its supported path is adopted here.
6341
+
6342
+ For a transport or unknown failure, run `ahub memory sync` as a person to see the local diagnostic. Logs keep only the cause class and known declined count, once per unchanged failure episode. Complete declined-ID sets can distinguish a changed failure without placing IDs or bodies in the log; incomplete inspection keeps the set unknown. Existing write-time screening and profile/privacy fences remain. SDK adoption and isolated fixtures do not prove live rollout or a successful sync of the person's memory home.