@staix/agent-hub 0.34.0 → 0.35.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 (161) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.md +35 -6
  3. package/docs/adding-an-ai-tool.md +2 -2
  4. package/docs/addons/claude-mem.md +4 -4
  5. package/docs/addons/open-code-review.md +1 -1
  6. package/docs/agent-notes/README.md +6 -0
  7. package/docs/agent-notes/adapters.md +9 -5
  8. package/docs/agent-notes/budget.md +1 -1
  9. package/docs/agent-notes/bus.md +1 -1
  10. package/docs/agent-notes/daemon.md +4 -3
  11. package/docs/agent-notes/local-worker.md +1 -1
  12. package/docs/agent-notes/models.md +2 -0
  13. package/docs/agent-notes/tasks.md +1 -1
  14. package/docs/agent-notes/tests.md +5 -0
  15. package/docs/design/adapters.md +110 -2
  16. package/docs/design/agent-hub-architecture.md +63 -1
  17. package/docs/design/agent-hub.md +6 -0
  18. package/docs/design/bus.md +18 -0
  19. package/docs/design/execution-reference.md +146 -0
  20. package/docs/design/task-board.md +10 -0
  21. package/docs/dogfood.md +33 -12
  22. package/docs/events.md +38 -20
  23. package/docs/memory.md +21 -7
  24. package/docs/operations.md +539 -24
  25. package/docs/quickstart.md +18 -4
  26. package/docs/research.md +71 -13
  27. package/docs/roles.md +1 -1
  28. package/docs/sdlc.md +9 -7
  29. package/docs/security.md +87 -11
  30. package/docs/smoke.md +14 -0
  31. package/docs/specs/2026-09-19-agent-hub-design.md +8 -3
  32. package/docs/verification/2026-11-07-0.34.1.md +54 -0
  33. package/docs/verification/2026-11-07-compat-python-startup-1248.md +51 -0
  34. package/docs/verification/2026-11-07-compat-receipt-harness-1248.md +105 -0
  35. package/docs/verification/2026-11-07-compat-receipt-producers-1248.md +96 -0
  36. package/docs/verification/2026-11-07-compat-workflow-1248.md +94 -0
  37. package/docs/verification/2026-11-07-daemon-startup-1232.md +169 -0
  38. package/docs/verification/2026-11-07-jcode-receipt-reconstruction-1248.md +118 -0
  39. package/docs/verification/2026-11-07-kimi-delayed-usage.md +34 -0
  40. package/docs/verification/2026-11-07-kimi-review-watch-1284.md +115 -0
  41. package/docs/verification/2026-11-08-agy-1.5.3.md +96 -0
  42. package/docs/verification/2026-11-08-agy-acp-t0.md +56 -0
  43. package/docs/verification/2026-11-08-hub-visual-1058.md +161 -0
  44. package/docs/verification/2026-11-08-jcode-0991-model-setter-1317.md +143 -0
  45. package/docs/verification/2026-11-08-kimi-252-model-setter-1317.md +137 -0
  46. package/docs/verification/2026-11-08-kimi-tui-completion-1282.md +45 -0
  47. package/docs/verification/2026-11-08-kimi-wire-yield-1283.md +31 -0
  48. package/docs/verification/2026-11-08-live-model-override-1317.md +250 -0
  49. package/docs/verification/2026-11-08-qwen-0269-acp.md +138 -0
  50. package/docs/verification/2026-11-08-qwen-0269-model-setter-1317.md +91 -0
  51. package/docs/verification/2026-11-09-0.35.0.md +129 -0
  52. package/docs/verification/2026-11-09-headless-records-1320.md +157 -0
  53. package/docs/verification/claude-permission-transport-native.py +10 -3
  54. package/docs/verification/claude-shell-wrapper-native.py +8 -8
  55. package/docs/verified.json +245 -15
  56. package/package.json +3 -3
  57. package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
  58. package/plugins/agent-hub/kimi-hook.js +20397 -0
  59. package/plugins/agent-hub/kimi-review-policy-worker.js +217 -0
  60. package/plugins/agent-hub/server.js +2701 -664
  61. package/scripts/check-docs.mjs +12 -1
  62. package/src/adapters/acp-model-policy.ts +56 -0
  63. package/src/adapters/acp.ts +175 -5
  64. package/src/adapters/agy/entry.ts +8 -5
  65. package/src/adapters/agy/hooks.ts +65 -15
  66. package/src/adapters/channel-peer.ts +33 -1
  67. package/src/adapters/claude-implementer.ts +16 -3
  68. package/src/adapters/codex-appserver.ts +118 -8
  69. package/src/adapters/codex-review-isolation.ts +3 -1
  70. package/src/adapters/compat.ts +11 -6
  71. package/src/adapters/definitions/builtin-metadata.ts +8 -1
  72. package/src/adapters/definitions/native-cli-entry-kimi.ts +76 -7
  73. package/src/adapters/definitions/native-cli-preview-common.ts +2 -2
  74. package/src/adapters/definitions/native-cli-preview-kimi.ts +9 -2
  75. package/src/adapters/definitions/reviewer-variants.ts +3 -1
  76. package/src/adapters/kimi-review-policy-worker.js +2 -0
  77. package/src/adapters/kimi-review-policy-worker.ts +85 -0
  78. package/src/adapters/kimi-review-policy.ts +68 -0
  79. package/src/adapters/kimi-review.ts +41 -41
  80. package/src/adapters/kimi-tui-facts.ts +153 -0
  81. package/src/adapters/kimi-wire-usage.ts +33 -8
  82. package/src/adapters/local-worker.ts +59 -20
  83. package/src/adapters/model-token.ts +3 -0
  84. package/src/adapters/pi.ts +27 -5
  85. package/src/adapters/registry-types.ts +34 -3
  86. package/src/adapters/registry.ts +1 -0
  87. package/src/adapters/reviewer-policy-watch.ts +86 -0
  88. package/src/adapters/reviewer-print.ts +80 -18
  89. package/src/cli/agents.ts +1 -1
  90. package/src/cli/facts-hook.ts +4 -2
  91. package/src/cli/help.ts +3 -2
  92. package/src/cli/identity.ts +3 -0
  93. package/src/cli/kimi-facts-hook.ts +11 -0
  94. package/src/cli/kimi-plugin.ts +157 -0
  95. package/src/cli/main.ts +48 -9
  96. package/src/cli/model.ts +52 -0
  97. package/src/cli/output.ts +23 -1
  98. package/src/cli/recovery-package.ts +15 -7
  99. package/src/cli/reset.ts +1 -1
  100. package/src/cli/status-lines.ts +8 -4
  101. package/src/hub/budget.ts +1 -1
  102. package/src/hub/bus.ts +40 -2
  103. package/src/hub/code-facts.ts +10 -3
  104. package/src/hub/completions.ts +3 -1
  105. package/src/hub/config-apply.ts +2 -1
  106. package/src/hub/control-client.ts +1 -1
  107. package/src/hub/daemon.ts +1050 -106
  108. package/src/hub/deliverable-files.ts +38 -5
  109. package/src/hub/dogfood-profile.ts +1 -1
  110. package/src/hub/envelope.ts +1 -1
  111. package/src/hub/events.ts +28 -18
  112. package/src/hub/execution-lineage.ts +120 -0
  113. package/src/hub/execution-reference.ts +36 -0
  114. package/src/hub/hub-tools.ts +21 -2
  115. package/src/hub/instances.ts +4 -3
  116. package/src/hub/kimi-events.ts +46 -0
  117. package/src/hub/kimi-usage.ts +132 -29
  118. package/src/hub/lifecycle.ts +31 -15
  119. package/src/hub/limits.ts +13 -4
  120. package/src/hub/manager.ts +15 -2
  121. package/src/hub/memory-policy.ts +2 -1
  122. package/src/hub/memory-sync-attestation.ts +54 -0
  123. package/src/hub/memory-sync-state.ts +20 -5
  124. package/src/hub/model-overrides.ts +115 -0
  125. package/src/hub/native-hook-launch.ts +12 -3
  126. package/src/hub/operational-view.ts +2 -2
  127. package/src/hub/peers.ts +27 -0
  128. package/src/hub/permission-mode.ts +1 -1
  129. package/src/hub/person-figures.ts +88 -0
  130. package/src/hub/report.ts +38 -4
  131. package/src/hub/research-dataset.ts +158 -0
  132. package/src/hub/research-memory.ts +68 -0
  133. package/src/hub/research-quality.ts +118 -0
  134. package/src/hub/research-record.ts +279 -0
  135. package/src/hub/research.ts +126 -27
  136. package/src/hub/restart.ts +14 -0
  137. package/src/hub/settings.ts +3 -0
  138. package/src/hub/startup-event-seeds.ts +51 -0
  139. package/src/hub/startup-readiness.ts +65 -0
  140. package/src/hub/startup-timing.ts +38 -0
  141. package/src/hub/tasks.ts +29 -9
  142. package/src/hub/ui.ts +9 -5
  143. package/src/hub/visual-assets.ts +50 -0
  144. package/src/hub/visual.ts +257 -0
  145. package/src/memory/brief.ts +169 -14
  146. package/src/memory/capture.ts +2 -1
  147. package/src/memory/recall.ts +46 -5
  148. package/src/memory/record-provenance.ts +82 -0
  149. package/src/memory/records.ts +116 -0
  150. package/src/models/route/adec.ts +190 -0
  151. package/src/models/route/config.ts +37 -3
  152. package/src/models/route/decision-evidence.ts +83 -0
  153. package/src/models/route/decision.ts +143 -0
  154. package/src/models/route/runtime.ts +13 -6
  155. package/src/ui/index.html +134 -6
  156. package/src/ui/plugin.ts +65 -4
  157. package/templates/AGENTS.block.md +1 -1
  158. package/templates/config.json +1 -1
  159. package/templates/profiles/general.json +1 -1
  160. package/templates/profiles/research.json +1 -1
  161. package/templates/profiles/writing.json +1 -1
@@ -4,7 +4,7 @@ agent-hub lets the coding agents on one machine work as peers in one project dir
4
4
 
5
5
  ## Install
6
6
 
7
- Needs [Bun](https://bun.sh) 1.4.2 or newer and macOS (Linux works without the `local` worker's `bash` and `git` tools, which need the macOS sandbox).
7
+ Needs [Bun](https://bun.sh) <!-- facts:bun-floor -->1.4.2<!-- /facts:bun-floor --> or newer and macOS (Linux works without the `local` worker's `bash` and `git` tools, which need the macOS sandbox).
8
8
 
9
9
  Node alone is not supported. Keep `bun` on PATH even if you install with npm.
10
10
  The registry package uses the `staix` organization scope. Install from npm:
@@ -16,10 +16,12 @@ ahub setup # installs the channel plugin and a pr
16
16
 
17
17
  Or use the matching GitHub release:
18
18
 
19
+ <!-- facts:install-tag -->
19
20
  ```bash
20
- bun add -g github:STAIxBWLB/agent-hub#v0.31.0
21
+ bun add -g github:STAIxBWLB/agent-hub#v0.35.0
21
22
  ahub setup
22
23
  ```
24
+ <!-- /facts:install-tag -->
23
25
 
24
26
  From a clone instead: `git clone`, `bun install`, `bun link`.
25
27
 
@@ -46,6 +48,18 @@ terminal in that project: it reviews the plan with you, applies it and follows
46
48
  it; other projects keep their version. It then names the `bun add -g` and
47
49
  `ahub setup` step when your global CLI or plugin is older than the new daemon.
48
50
 
51
+ ## Upgrading to 0.35.0
52
+
53
+ The hub now exact-pins `@staix/agent-mem` 0.23.1 (0.34.x pinned 0.22.1), and managed headless peers (Codex, headless Kimi, Qwen, Jcode, Pi, `local`) start saving compact `amem:` Record lines from their final answers; the hub strips those lines from shared replies (#1320). Upgrade every hub, global `amem` CLI and native amem hook that shares a memory home together: a 0.22.1 hook drain silently discards Record format-3 spool entries, and 0.22.1 refuses the `records` key even when false. To turn Records off, set `records: false` in the amem configuration only after every install is upgraded ([agent-authored records](operations.md#agent-authored-records)). Machine memory sync now supplies amem's placement attestation on every pass (#1173); a refused writer shows as cause `writer` in `ahub status` and the dashboard, with the next step. The budget gate default is 0.99 instead of 0.9 (#1292), but a project whose tracked `.agenthub/config.json` has `"gate": 0.9` from an older `ahub init` keeps 0.9 until you remove that key or run `ahub settings set budget.gate <value>`. On Kimi 2.5.2, `ahub kimi` installs a launch-scoped plugin so turns you start in its TUI leave results (#1282), and `ahub agy` turns off agy's self-update for the run (#1286). Nothing else changes until you use it: `ahub model set|clear|list` (person only) selects a supported peer's next-turn model (#1321), peers can post bounded visuals with `hub_visual` (#1315), and `ahub research export --dataset <dir>` writes per-project research datasets (#1290). N-1 is published 0.34.1 and control remains 16 through 17.
54
+
55
+ `ahub upgrade` does not update the global CLI or the shared Claude plugin: run `bun add -g @staix/agent-hub@0.35.0` and then `ahub setup` yourself, and `ahub doctor` warns while either is older than a running daemon.
56
+
57
+ ## Upgrading to 0.34.1
58
+
59
+ `ahub upgrade` reads npm 12's `npm view --json` output again; with npm 12, 0.34.0 and earlier refused both the default target and `--to`. Update the global CLI to 0.34.1 first, either `bun add -g @staix/agent-hub@0.34.1` or, if you installed with dotfiles, `dot ai family install ahub --version 0.34.1`; then run `ahub upgrade` in each project. If you must run an older CLI's upgrade on npm 12, put npm 11 first on PATH for that run (`npm i --prefix ~/.cache/npm11 npm@11`, then `PATH="$HOME/.cache/npm11/node_modules/.bin:$PATH" ahub upgrade`). Nothing else changes.
60
+
61
+ `ahub upgrade` does not update the global CLI or the shared Claude plugin: run `bun add -g @staix/agent-hub@0.34.1` and then `ahub setup` yourself, and `ahub doctor` warns while either is older than a running daemon.
62
+
49
63
  ## Upgrading to 0.34.0
50
64
 
51
65
  After an Orca restart, `ahub upgrade` no longer stops at panes that are orphaned but still connected: the plan screen offers `[t]` to reattach exactly the panes it verified (each Orca call bounded by 5 s), and the blocker names the refresh key. `ahub setup` now writes the stable install path of a symlinked CLI (for example a dotfiles-managed `~/.local/bin/ahub`) into the marketplace and the Claude profile hook instead of a versioned one, replaces a stale managed hook in place, and a refused marketplace replacement names every blocking row and the next step; `ahub doctor` checks the profile hook of each Claude home it can see. If you installed with dotfiles, follow [Installed with dotfiles](#installed-with-dotfiles) instead of `bun add -g`. Nothing else changes until you use it; N-1 is published 0.33.0 and control remains 16 through 17.
@@ -224,7 +238,7 @@ The restarted Claude planner gets the release-train tools (`hub_release_new`, `h
224
238
 
225
239
  Jcode must be 0.98.0 or later, below 1.0.0; 0.94.0 is no longer accepted, so `ahub doctor` fails its Jcode row for an older install and the peer does not start (#529). Every owned jcode process runs with `JCODE_NO_TELEMETRY=1`.
226
240
 
227
- Since 0.12.37 the hub opens a split memory home through its shards (#534); the current exact `@staix/agent-mem` pin is 0.22.1. Before anyone runs `amem migrate --shards` on a memory home, every hub using that home must run 0.12.37 or later: an older hub does not see the split and keeps writing the old layout. `ahub memory profile-check` is split-aware and compares each hub with its own project's shard (#536). See [memory profiles and rollout](memory.md#selected-memory-profile).
241
+ Since 0.12.37 the hub opens a split memory home through its shards (#534); the current exact `@staix/agent-mem` pin is 0.23.1. Before anyone runs `amem migrate --shards` on a memory home, every hub using that home must run 0.12.37 or later: an older hub does not see the split and keeps writing the old layout. `ahub memory profile-check` is split-aware and compares each hub with its own project's shard (#536). See [memory profiles and rollout](memory.md#selected-memory-profile).
228
242
 
229
243
  From 0.12.30 or earlier, existing Jcode project MCP entries need one refresh before conductor or dashboard starts. Once per project, the person runs:
230
244
 
@@ -302,7 +316,7 @@ More: `ahub help`. How it is built and why: [`docs/specs/2026-09-19-agent-hub-de
302
316
 
303
317
  ## Shared memory
304
318
 
305
- The hub exact-pins the first-party `@staix/agent-mem` 0.22.1 core, including deterministic capture; no claude-mem worker is needed. Existing shared-v1 memory remains the default; upgrading does not migrate it. Managed agents use screened `hub_memory_search`, `hub_memory_show` and `hub_memory_graph` tools, or propose a correction with `hub_memory_correct`; only a person accepts candidates through the supported `amem review` path. Since agent-mem 0.7.0, `amem review` also accepts candidates on a selected generation.
319
+ The hub exact-pins the first-party `@staix/agent-mem` <!-- facts:agent-mem-pin -->0.23.1<!-- /facts:agent-mem-pin --> core, including deterministic capture; no claude-mem worker is needed. Existing shared-v1 memory remains the default; upgrading does not migrate it. Managed agents use screened `hub_memory_search`, `hub_memory_show` and `hub_memory_graph` tools, or propose a correction with `hub_memory_correct`; only a person accepts candidates through the supported `amem review` path. Since agent-mem 0.7.0, `amem review` also accepts candidates on a selected generation.
306
320
 
307
321
  Before any real generation migration, upgrade every hub sharing that memory home to 0.12.30 or later and check its authenticated binding with `ahub memory profile-check` in each custom registry. Ready bindings prove compatibility, not an atomic live cutover: quiesce consumers and close their handles before migrating, then reopen through the supported session-preserving lifecycle. Invalid or changed profiles disable native memory without falling back to legacy writes; the bus and board continue. See [memory operations](operations.md#hub-memory) for the remaining rollout gates.
308
322
 
package/docs/research.md CHANGED
@@ -1,8 +1,9 @@
1
1
  # Research records
2
2
 
3
- Opt-in records of how each task went, kept across projects for management and research (#247). They are built only
4
- from a project's `events.jsonl`, so they hold ids, states, counts, tokens and times, never a title, detail, plan, note,
5
- path, message body or check output.
3
+ Opt-in records of how each task went, kept across projects for management and research (#247). They are built from a
4
+ project's `events.jsonl` and, since schema v2 (#1277), from read-only views of its board history and delivery journal
5
+ that are reduced to closed values on the way in, so they hold ids, states, counts, tokens, times and closed vocabularies,
6
+ never a title, detail, plan, note, path, message body or check output.
6
7
 
7
8
  ## Turning them on
8
9
 
@@ -13,7 +14,7 @@ In a project's `.agenthub/config.json`:
13
14
  ```
14
15
 
15
16
  Off by default; anything but `true` is off. While it is off the hub writes nothing and creates no file. The setting is
16
- read when the hub starts.
17
+ applied live: a config reload takes it without a restart.
17
18
 
18
19
  ## Where they go
19
20
 
@@ -23,11 +24,13 @@ directory, so `ahub research --all` can compare them. `ahub reset --all` does no
23
24
 
24
25
  ## When a record is written
25
26
 
26
- When a task is approved (a review approves it, or its owner reports a task without a reviewer done), the hub waits
27
+ When a task reaches a terminal board state (a review approves it, its owner reports a task without a reviewer done, or it is cancelled), the hub waits
27
28
  until no open turn is attributed to the task (the turn that approved it ends after the approval, and its usage arrives
28
29
  later still), checking every 10 s for at most 30 minutes, then appends one task record built from that project's
29
- events. A hub that stops first writes it once its peers have stopped. Approval is final on the board, so each task has
30
- one record, and the first record written for a task is final. A failing write is one `hub.log` line per hub run and
30
+ events and the board. A hub that stops first writes it once its peers have stopped. A terminal state is final on the
31
+ board, so each task has one record, and the first record written for a task is final with one exception: the guarded merge
32
+ writes its receipt after the approval, so a later pass appends the same task once more when it brings merge figures the
33
+ held record lacked (readers keep the latest row of a task). A v2 row also replaces a held v1 row of the same task. A failing write is one `hub.log` line per hub run and
31
34
  never changes the task flow.
32
35
 
33
36
  `ahub research backfill` builds the same records from a project's existing `events.jsonl` (marked
@@ -37,16 +40,20 @@ waits for its turns freezes that record without them.
37
40
  A task is identified by project, id and proposal time (`createdAt`), because `ahub reset --all` starts the ids again
38
41
  while the store keeps the older records.
39
42
 
40
- ## Task record (`agent-hub.research/v1`, `kind: "task"`)
43
+ ## Task record (`agent-hub.research/v2`, `kind: "task"`)
44
+
45
+ Schema v2 is additive: a v1 file still loads (a v1 row reads as `outcome: "approved"`, and the file is never rewritten), and
46
+ `ahub research` and `ahub research addons` read both. Every statistic about approved tasks filters on `outcome`.
41
47
 
42
48
  | Field | Meaning |
43
49
  | --- | --- |
44
50
  | `project` | the project key above |
45
51
  | `task` | the board's task id |
46
52
  | `class`, `pii` | the task's class; whether it was a PII task (its record holds counts only, like any other) |
47
- | `outcome` | `approved` |
48
- | `createdAt`, `startedAt`, `approvedAt` | proposal, first `in_progress`, approval (ISO) |
49
- | `wallMs` | from `startedAt` to `approvedAt`, null when the start is not in the events |
53
+ | `outcome` | `approved` or `cancelled` (v2): every task in a terminal board state has a record; a v1 row is always `approved` |
54
+ | `createdAt`, `startedAt`, `approvedAt` | proposal, first `in_progress`, approval (ISO); `approvedAt` is null on a cancelled record |
55
+ | `closedAt` | v2: when the task reached its terminal state (the approval or the cancellation) |
56
+ | `wallMs` | from `startedAt` to the terminal state, null when the start is not in the events |
50
57
  | `activeMs`, `turns`, `filesChanged` | sums over the turns attributed to the task (`turn_end`), the ones ending after the approval included |
51
58
  | `sessionRoles` | optional attributed-turn preamble preparations: peer, turn id, role ids/layers/hashes; `roles: []` means prepared without catalog guidance, `roles: null` means unknown; older events leave the field absent. No role bodies or proof of model consumption |
52
59
  | `owners`, `reviewer` | the owners in the order they held the task (an owner who gets it back appears again; reports by owner count the task once per owner); the last reviewer |
@@ -65,6 +72,57 @@ while the store keeps the older records.
65
72
  | `briefs` | #258: task briefs handed over for the task (`delivered`) and how many carried a hub-memory fail note (`withFail`), from `memory_brief` events; absent when none was recorded |
66
73
  | `peers` | with `tooling`: every peer with an event attributed to the task (a turn, tokens, a route), fingerprinted or not, so a peer without a fingerprint makes the task unknown for an add-on rather than off |
67
74
 
75
+ ### v2 blocks (#1277)
76
+
77
+ Every value is a count, a duration or a member of a closed vocabulary. A value that cannot be derived is `null`, never 0,
78
+ and `quality.json` counts it as unknown. Journal-derived fields are null unless the journal is known to hold every delivery from the
79
+ task's proposal on: the journal prunes its oldest-updated terminal rows beyond a cap and keeps unresolved rows, review-package receipts and retry parents, so the
80
+ oldest surviving row proves nothing. The record uses the oldest-updated unprotected terminal row that survived as its bound (every pruned row is no newer than it, and a
81
+ delivery is created no later than it is updated), and treats the journal as unknown when its own review-input coverage is incomplete; board-derived fields are null when `hub.db` could not be read.
82
+
83
+ | Field | Meaning |
84
+ | --- | --- |
85
+ | `cancelCause` | only on a cancelled record: `cancelled`, `declined`, `escalated`, `stale` or `unknown`, taken only from an explicit closed `cause` the board retains on the cancellation. The board keeps only a free-text note today, so it reads `unknown`; a cause is never inferred from the note, from an earlier decline or escalation, or from who cancelled |
86
+ | `declines`, `escalations` | intermediate declines and escalations (history events, not terminal), counted next to `reassignments` |
87
+ | `review` | `rounds` (verdicts that are not evidence-only), `firstPass`, `findings` by pass (`bugs`, `security`, `compliance`) each with `important`, `nit`, `open`, `fixed`, `falsePositive`, `accepted` (distinct findings at their last disposition; null when no verdict carried a structured report or the task is `[pii]`), `reviewerFamily`, `ownerFamily`, `scope` (`code`, `full`, `delta`, `none`, `unknown`), `evidenceOnly`, `context` (`fresh`, `continuing`, `unknown`), `staleHeadReReviews` |
88
+ | `delivery` | `count` (envelopes that named the task), `firstDeliveryMs` (`p50`, `p95` of the journal's completed deliveries), `needsReview`, `failed`, `discarded` (journal states), `dropped` (`fyi`, `hop`) |
89
+ | `merge` | `ciRuns`, `ciReruns`, `githubReviews` and `approvedToMergedMs` from the receipt the guarded merge wrote, `doneToApprovedMs` from the events' own times |
90
+ | `specs` | `covering`, `unchanged`, `acked`, `errors` from the latest done's spec report; an incomplete report is unknown |
91
+ | `memory` | `briefs`, `briefNotes`, `fails` from `memory_brief` events (a known 0 when briefs were being reported in the log and the task got none, null when the log shows none at all); `rendered`, `delivered`, `cited`, `failsExposed`, `failsCited`, `failRetrySignal` (#1279) are counts of distinct entries from agent-mem's own machine-local counters for the task, never an id: `rendered` entries put into a delivery attributed to the task, `delivered` of those whose delivery the journal acknowledged, `cited` of those that a hub-screened done summary, review note, `spec_ack` reason, message or `hub_remember` text named by `mem:<id>` (exact match, no judgment), `failsExposed` and `failsCited` the same over fail notes, `failRetrySignal` the exposed fail notes the plan's paths or symbols touch that were not cited by approval: a signal for a reviewer to read, never an outcome, since it does not say the approach was retried. All six are null with no counter row for the task (sidecar off, a private task, rows aged out of the sidecar's 90 days), `delivered` is null while no delivery is known to have completed, and the fail figures are null when a rendered entry can no longer be read |
92
+ | `person` | #1278: what the person was asked and did for the task, from the four events above. `permissions` (`asked`, `allowed`, `denied`, `expired`), `questions` (`asked`, `answered`, `expired`), `completions`, `cliRefusals`, `latencyMs` (`sum`, `n`, `unknown`) and `consoleConfirmations`. An interaction counts for a task only when its event names one; `unattributed` is counted apart (in `ahub report --by task`) and never folded into a task. `latencyMs.sum` adds only recorded latencies of PERSON-answered permissions and questions (an answer with a `surface`); a person answer without a latency reading raises `n` and `unknown` and adds nothing to `sum`, and an automatic answer, an expiry and a cancellation are not the person's minutes. Every part is null for a task that began before the first `hub_start` with `personAttribution` (the log cannot say), and a known 0 after it when no interaction happened, in the record and in `ahub report --by task` alike (one function decides, `src/hub/person-figures.ts`). `consoleConfirmations` stays null: the hub records no event for a console confirmation, whose approval arrives as the permission's `answered`. Not observed: an interaction inside a vendor TUI the hub cannot see |
93
+ | `stalls` | persisted idle-ladder entries on the task's history (the stall table itself is cleared when a task closes); null without the board |
94
+ | `handoffs` | `in`, `out` from the handoff tables |
95
+ | `cohort` | `silent`, `advisory` or `none` |
96
+
97
+ The record reads the board through a read-only handle (`readEvidence`): titles, details, plans, refs and notes are never
98
+ decoded, and the journal's envelope bodies are never read.
99
+
100
+ ## Dataset export (`ahub research export --dataset <dir> [--since] [--all]`)
101
+
102
+ Writes `<dir>/<project key>/tasks.jsonl`, `manifest.json` and `quality.json` for this project, or for every registered
103
+ project that has research records with `--all` (read-only, on this machine, with the same project-key hashing as the
104
+ record file names). It is built to leave the machine, so it is an allowlist:
105
+
106
+ - a peer is only a family name (`claude`, `codex`, `kimi`, ...), resolved through the project's own config for an instance
107
+ id, and `unknown` when none is known (never guessed from the id's spelling); `profile`, `role`, `roleIdentity` and
108
+ `sessionRoles`, which carry project-chosen names, are dropped;
109
+ - the target must be inside your home directory, not the home itself, and outside every registered project and its state
110
+ directory (compared as real paths, so a symlink out of the home or into a project does not pass); a project directory
111
+ that is a symbolic link is refused;
112
+ - files are written whole (a temporary file, then a rename), owner-only, and the manifest last, so a directory with a
113
+ manifest is complete.
114
+
115
+ `manifest.json`: `schema`, `hubVersion`, `projectKeyHash`, `window` (`since`, `until`), `peers` (families), `counts`
116
+ (`tasks`, `approved`, `cancelled`, `labels`), `recordedAt`. `--format` and `--dataset` exclude each other.
117
+
118
+ `quality.json` (#44 AC0, `agent-hub.research-quality/v1`): `fields` (per v2 field: `n` records and how many are
119
+ `unknown`, null or the vocabulary's own `unknown`), `quota` per family (`readings`, `gaps` over one hour, their total
120
+ `gapMs`, `stalenessP95Ms` between readings, and `clock`: how many readings used the event's time or their own
121
+ `measuredAt`), `budget` (pauses and resumes per family, and `handoffs`: the owner moves a budget pause drove, counted from the `reassigned` task events whose board reason is `budget`, once per move; null when the window holds no task event at all, never 0), `outcomes` per family of the last owner and class, `events`
122
+ (the types present with counts, and the ones the report expects that did not occur), and `tokenAttribution` (`total` and the
123
+ share by `delivery`, `single_open`, `unattributed`; null shares when nothing was counted). The 2026-10-21 field analysis found
124
+ 40.2% of token increments unattributed: a dataset now says so itself.
125
+
68
126
  ## Label record (`kind: "label"`)
69
127
 
70
128
  `ahub task label <id> ok|regressed|reverted|incomplete|wrong|abandoned [--created <time>]` appends
@@ -92,7 +150,7 @@ names the agent (empty for the person).
92
150
  and no brief recorded (shown once any record has a brief).
93
151
 
94
152
  They are computed each time from the records; nothing derived is stored. `ahub research export [--format jsonl|csv]
95
- [--since] [--all]` writes the records for outside analysis; the CSV columns are, in order: project, task, class, pii,
153
+ [--since] [--all]` writes the records for outside analysis (`--dataset <dir>` writes the per-project dataset below); the CSV columns are, in order: project, task, class, pii,
96
154
  outcome, createdAt, startedAt, approvedAt, wallMs, activeMs, owners, reviewer, reviewRounds, changesRequested, checkPassed,
97
155
  checkFailed, dones, reassignments, firstPass, stuck, overlaps, conflicts, testsPass, testsFail, tokens, turns,
98
156
  filesChanged, models, label, labelBy, writerVersion, writerSource. The CSV is a flat subset; JSONL carries every field.
@@ -122,4 +180,4 @@ either side a difference is inconclusive. This is field data: the sides differ i
122
180
  association, not cause. Controlled arms are `ahub bench run --addon <name>=on|off` (docs/bench.md), and the decisions
123
181
  taken from both are in `docs/addons/`.
124
182
 
125
- Agents may read the measures and export (ids and counts only); `backfill` is the person's or the configured conductor's (#933), and an agent labels only its own task.
183
+ Agents may read the measures and export (ids and counts only), `--dataset` included; `backfill` is the person's or the configured conductor's (#933), and an agent labels only its own task.
package/docs/roles.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  # Definitions and layers
8
8
 
9
- - Shipped definitions live at `templates/roles/dev|research/<id>.md`. The first catalog has six dev and five research roles. Other shipped groups wait for the #251 measurement.
9
+ - Shipped definitions live at `templates/roles/<group>/<id>.md`: six dev and five research roles, plus four writing and two ops roles that the domain packs use ([profiles](profiles.md)).
10
10
  - Project definitions at `.agenthub/roles/<id>.md` replace the same shipped id. Machine-level definitions are deferred. Files are bounded regular files, at most 8 KiB, with no symlink/hardlink targets; project directories cannot be symlinks. Scanning is flat and capped at 64 files.
11
11
  - A flat header declares id, name, group, summary, base, classes and source. Id equals the file stem. Base is one or more existing flow roles excluding conductor; classes name today's seven base task classes. At least one declared base must already be held by a peer receiving that session guidance.
12
12
  - Exactly six nonempty sections, in order: Scope, Not in scope, Deliverables, Hub use, Review duties, Evidence. Rendered body is at most 2000 characters. No extra heading/personality section, code block, command-shaped line, emoji, control or non-ASCII punctuation. This syntactic lint is not an instruction-safety classifier; project text is labelled untrusted guidance and cannot change daemon authority checks.
package/docs/sdlc.md CHANGED
@@ -36,7 +36,7 @@ Stored `implement` and `verify` stage ids read as `build` and `test`. A write th
36
36
  | S5 | Continuous evals for agent configuration | Gate mechanism only: `config_paths` mark a configuration PR and the configured `eval_check` is then required, with its pass rate and suite version recorded. No suite is chosen, created or defaulted: suite choice pending the person (#251/#266) |
37
37
  | S6 | Production approval gate | Person-only commit/version/docs receipt and opt-in Git tag pre-push guard; report stays read-only |
38
38
  | S7 | Metrics, deterministic control bands and follow-up intent | `ahub sdlc metrics`, optional `[bands]` with log, diagnose and propose tiers, one card per breach episode; merge-time review and CI figures recorded from the merged PR; release metrics (interval, lead time, hotfix rate) and a `hotfix_rate` band; an opt-in scheduled security-scan baseline (`[security_scan]`, `security_open_alerts` band). Only the per-release gates-run record remains unbuilt (design spec amendment) |
39
- | S8 | Compliance acceptance-criteria checklist | Head-bound checklist retained; legacy workflow and phase compatibility deprecated; task import removed |
39
+ | S8 | Compliance acceptance-criteria checklist | Head-bound checklist retained; legacy workflow and phase receipts refused since 0.13.0; task import removed |
40
40
  | S9 | Planner grooming and plan review | Read-only digest, current plan-review receipt at build admission, all-mode WIP and accepted-untouched return |
41
41
  | S10 | Planner cost governance | Typed review/CI scope, premature-review admission, identical-tree CI reuse and read-only cost report |
42
42
 
@@ -72,7 +72,7 @@ A per-project `.agenthub/sdlc.toml` may set `mode = "advise"` and `required_chec
72
72
 
73
73
  A person records ids and a claimed local receipt with `ahub sdlc record 12 '{"repo":"owner/repo","issue":109,"pr":325,"localHead":"<full SHA>","localResult":"passed","stage":"build"}'`. Owners/reviewers have the bounded `hub_sdlc_record` tool; it accepts no remote bodies or external-check results. Only the assigned reviewer or person records a reviewed head. `ahub review 12 approved --head <full SHA>` rechecks current evidence before approval. Changes requested does not require green CI.
74
74
 
75
- `ahub sdlc status` lists bounded local projections without polling every remote PR. `ahub sdlc explain 12` or `status 12` performs a fresh structured readback, with a console-only boundary and no remote body retention. Shared console/dashboard projections explicitly show unknown external evidence; they do not certify freshness from cached data. Default task rows and contracts remain unchanged when off.
75
+ `ahub sdlc status` lists bounded local projections without polling every remote PR. `ahub sdlc explain 12` or `status 12` performs a fresh structured readback, open to any agent as a public view (private tasks stay stubs), with no remote body retention. Shared console/dashboard projections explicitly show unknown external evidence; they do not certify freshness from cached data. Default task rows and contracts remain unchanged when off.
76
76
 
77
77
  # Spec acceptance (#481 S1)
78
78
 
@@ -164,6 +164,8 @@ The package-owned scripts/check-docs.mjs accepts --json with the same normal/--r
164
164
 
165
165
  The release reader additionally requests --committed: manifest, README and package metadata come from the captured HEAD, and dirty documentation/evidence files block freshness. A working-tree restamp cannot certify a commit that still contains stale stamps. The ordinary checker continues to support working-tree review before those stamps are committed.
166
166
 
167
+ The manifest must list `README.md`, every agent note and every page directly under `docs/` and `docs/addons/` except the dated records `docs/smoke.md` and `docs/gsd-collaboration-analysis.md`; a missing entry is an error. CI's `decision` job reports stale stamps as warnings on every PR, even when the gate is reused; only the release PR restamps (#1268).
168
+
167
169
  # Guarded merge (#786)
168
170
 
169
171
  `.agenthub/sdlc.toml` takes `auto_merge = "off" | "dry-run" | "on"` (default `off`) and optional `[repos."owner/name"]` tables with `required_checks` and `auto_merge`. A table's mode overrides the top-level one for that repository; its `required_checks` come first, then the top-level list, so a repository without branch protection is gated by what this file names (an empty set refuses: `required checks unknown`). Unknown keys are refused with a field path. The file is read once at start.
@@ -217,7 +219,7 @@ The predecessor is the newest non-draft entry from GitHub's descending release l
217
219
 
218
220
  # Delivery readback commands and surfaces (#257)
219
221
 
220
- `ahub sdlc queue <owner/repository>` and `ahub sdlc release <owner/repository> [live-issue...]` issue console-only readbacks. They also run through the existing console command path. The dashboard's Delivery evidence section uses labelled inputs and read-only buttons, textContent output and existing project/instance/generation fences; switching projects discards old reports and input. The report never changes task state or grants a permission. Private document names are removed at the Tasks public projection. Ledger issue state remains metadata, never native-success proof or release permission.
222
+ `ahub sdlc queue <owner/repository>` and `ahub sdlc release <owner/repository> [live-issue...]` are read-only readbacks, open to any agent as a public view. They also run through the existing console command path. The dashboard's Delivery evidence section uses labelled inputs and read-only buttons, textContent output and existing project/instance/generation fences; switching projects discards old reports and input. The report never changes task state or grants a permission. Private document names are removed at the Tasks public projection. Ledger issue state remains metadata, never native-success proof or release permission.
221
223
 
222
224
  The all-project manager forwards both delivery reads through the same scoped console connection with a 30-second cap, beyond their shared 20-second read budget. Stale/unknown project or instance identities and unknown operations remain refused.
223
225
 
@@ -298,7 +300,7 @@ A review request is premature if its task head does not match the PR head, confi
298
300
 
299
301
  **Evidence in the review request (#824).** A fresh reviewer cannot run tests or read `gh`, so the request carries the hub's own reading of the admission it already made, for every reviewer: `Hub-verified at <head>: required checks <names> passed at this exact head; no bot review thread is waiting for a reply` (plus `via identical-tree run <n>` when the hub reused one), or in advise mode `Hub-verified at <head>: not ready: <blockers>`. No admission (lifecycle off) and a PII task get no block, and no new `gh` call is made for it: it renders what `reviewRequestAdmission` read. `ReviewReadiness.checks` names the required checks that were judged. When the admission is ready and the hub ran no check of its own, the checklist's check line says CI passed at this exact head and that test execution or CI evidence is not an unmet item (name a specific missing test or behaviour instead); otherwise the old wording stays. The text comes only from the hub's reads and recorded `check passed` events, never from the builder's summary or a `hub_sdlc_record` claim, and a review-only reviewer still gets no builder summary (#223 R2).
300
302
 
301
- CI controllers are serialized per pull request (`check-pr-<number>`) and for main pushes and ordinary dispatches (`check-main`), for the entire workflow and without canceling an admitted controller (#609); macOS timing dispatches use their own macos-timing group. Within a PR `queue: single` lets a newer pending run replace the older pending one; main keeps `queue: max`, which preserves up to 100 waiting controllers ([GitHub concurrency documentation](https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/control-workflow-concurrency)). Different PRs run side by side: a repository-wide lock serialized every PR (17-18 min alone, 40-54 min with 4-5 PRs in flight on 2026-10-20). Two groups admitting the same tree at the same instant can run it twice; that duplicate costs one run and never a false green. Required job names and read-only token permissions are unchanged. Before PR or push gates, `scripts/ci-reuse.mjs` reuses successful full proof for the identical Git tree, including its workflow. On reuse the two OS matrix jobs still expand and report their exact required names, while the seeded aggregate reports its exact name. They run only a `Reuse verified identical-tree full gate` marker step naming the original proof; full setup and gates are skipped. Skipped jobs and successful reuse-reporting jobs never count as fresh full proof. A running identical-tree gate refuses duplicate admission; unknown active coverage refuses safely. Only a 404, a timeout on the individual read limit, or malformed successful proof is dropped: a failed successful-run list drops that proof phase, while a failed commit or jobs read drops only that candidate and continues the bounded search. Other failures (5xx, 403, 429, response size bounds, authentication and unclassified errors) remain unknown. Exhausting the overall deadline or request budget stays unknown even when a request reports a timeout. Without verified proof, a complete empty active scan requires a fresh gate. An unreadable, truncated or unidentifiable active inventory, or exhaustion of the shared request budget or deadline, stays unknown and refuses admission. The release reader (`--require`) still refuses without verified identical-tree full proof. Metadata reads are bounded to 100 recent successes/active runs and 240 calls within 60 seconds. Pending serialized controllers are not admitted gates. Run focused tests and `scripts/check.sh --changed` locally; CI carries the full-suite gate; a known flake warrants only one rerun of its failed job with recorded evidence.
303
+ CI controllers are serialized per pull request (`check-pr-<number>`) and for main pushes and ordinary dispatches (`check-main`), for the entire workflow and without canceling an admitted controller (#609); macOS timing dispatches use their own macos-timing group. Within a PR `queue: single` lets a newer pending run replace the older pending one; main keeps `queue: max`, which preserves up to 100 waiting controllers ([GitHub concurrency documentation](https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/control-workflow-concurrency)). Different PRs run side by side: a repository-wide lock serialized every PR (17-18 min alone, 40-54 min with 4-5 PRs in flight on 2026-10-20). Two groups admitting the same tree at the same instant can run it twice; that duplicate costs one run and never a false green. Required job names and read-only token permissions are unchanged. Before PR or push gates, `scripts/ci-reuse.mjs` reuses successful full proof for the identical Git tree, including its workflow. On reuse the shard jobs are skipped, and the aggregate jobs `check (ubuntu-latest)`, `check (macos-latest)` and `seeded guards` report their exact required names. They run only a `Reuse verified identical-tree full gate` marker step naming the original proof; full setup and gates are skipped. Skipped jobs and successful reuse-reporting jobs never count as fresh full proof. A running identical-tree gate refuses duplicate admission; unknown active coverage refuses safely. Only a 404, a timeout on the individual read limit, or malformed successful proof is dropped: a failed successful-run list drops that proof phase, while a failed commit or jobs read drops only that candidate and continues the bounded search. Other failures (5xx, 403, 429, response size bounds, authentication and unclassified errors) remain unknown. Exhausting the overall deadline or request budget stays unknown even when a request reports a timeout. Without verified proof, a complete empty active scan requires a fresh gate. An unreadable, truncated or unidentifiable active inventory, or exhaustion of the shared request budget or deadline, stays unknown and refuses admission. The release reader (`--require`) still refuses without verified identical-tree full proof. Metadata reads are bounded to 100 recent successes/active runs and 240 calls within 60 seconds. Pending serialized controllers are not admitted gates. Run focused tests and `scripts/check.sh --changed` locally; CI carries the full-suite gate; a known flake warrants only one rerun of its failed job with recorded evidence.
302
304
 
303
305
  `hub_sdlc_costs` and the person's `ahub sdlc costs [owner/repo]` report linked merged PRs without changing the board. A review round is a board done-to-verdict handoff, not each reviewer in a multi-reviewer round. An observed handoff still counts when its declared scope was none. The report counts distinct check-workflow run ids, extra attempts (`run_attempt - 1`), full re-reviews, delta rounds and summed review intervals, with GitHub review counts alongside them. Unspecified scope and unavailable evidence stay unknown/incomplete. Only current public links are projected; a private counterpart withholds the whole PR. Up to 16 linked PR reads share 20 seconds; each PR covers at most 100 commits, reviews and recent check-workflow runs, and the pure aggregator bounds 500 tasks, 200 PRs and 10,000 history entries. Truncation is explicit. S10 exposes pure band-breach proposal data; S7 owns rolling baselines, band configuration, automatic scheduling and board-card creation.
304
306
 
@@ -405,11 +407,11 @@ Tracked npm cleanliness compares the cached index to HEAD and raw tracked blob b
405
407
 
406
408
  Release preparation is an agent task; the release decision remains the person's. Consent may be recorded by the person or by the configured conductor on the explicit version-naming instruction above; preparation alone grants no consent. A hub captured before #1120 has no `scripts/check-docs.mjs` in its runtime copy, so `ahub sdlc authorize-release` there refuses with "documentation checker ... is missing from the running hub" (not "stale documentation"): record consent from a hub that carries the checker, either `ahub upgrade` to a release that includes #1120 or a hub started from the candidate checkout with `bun src/cli/main.js`, and never restamp documents to get past it.
407
409
 
408
- `bun scripts/release-prepare.ts [--version X.Y.Z] [--dry-run]` is the mechanical half of AGENTS.md "Releasing" steps 1 and 2. On a `release/<version>` branch (it refuses `main`, a detached HEAD, a dirty tree and any other branch name) it reads the commits since the last `v*` tag, applies the rule (any `feat` subject bumps the minor, otherwise the patch; a version below the rule or not newer than the last tag is refused, so is a missing or empty `## Unreleased`), and writes `package.json`, the README `Status:` line, the CHANGELOG section (the Unreleased entries move under the version, Unreleased stays empty), the bundle (`bun run build`) and a verification report skeleton `docs/verification/<date>-<version>.md`. The skeleton lists the pull requests the commits name and the native acceptance receipts, which it reads from `docs/verification/` files added since the tag, never from what an agent writes. It leaves `TODO(agent)` markers for what only a review can state: each PR's review and CI, the document review, the checks and the known gaps.
410
+ `bun scripts/release-prepare.ts [--version X.Y.Z] [--dry-run]` is the mechanical half of AGENTS.md "Releasing" steps 1 and 2. On a `release/<version>` branch (it refuses `main`, a detached HEAD, a dirty tree and any other branch name) it reads the commits since the last `v*` tag, applies the rule (any `feat` subject bumps the minor, otherwise the patch; a version below the rule or not newer than the last tag is refused, so is a missing or empty `## Unreleased`), and writes `package.json`, the README `Status:` line, the generated doc facts (the quickstart install tag and the version pins between `<!-- facts:<name> -->` markers, rendered by `scripts/lib/readme-facts.ts`, so the release PR passes `scripts/readme-facts.ts`; `--dry-run` lists the blocks it would change, #1273), the CHANGELOG section (the Unreleased entries move under the version, Unreleased stays empty), the `Upgrading to <version>` note in `docs/quickstart.md` with the global CLI and plugin step, the bundle (`bun run build`) and a verification report skeleton `docs/verification/<date>-<version>.md`. The skeleton lists the pull requests the commits name and the native acceptance receipts, which it reads from `docs/verification/` files added since the tag, never from what an agent writes. It leaves `TODO(agent)` markers for what only a review can state: each PR's review and CI, the document review, the checks and the known gaps.
409
411
 
410
412
  It restamps nothing by itself. A stamp in `docs/verified.json` records that someone read what changed under a document, so `bun scripts/release-prepare.ts restamp <document>... --reviewed` sets `verifiedAgainst` to HEAD for the named documents only, refuses without `--reviewed`, refuses an unnamed or unknown document and refuses with staged changes. The agent runs it per document after reading the source commits, commits `docs/verified.json`, opens the release PR and merges it with a merge commit (never squash).
411
413
 
412
- `bun scripts/release-tag.ts <version> [--execute]` is the one guarded tag run. Every run repeats the whole preflight and reports every refusal at once: a valid release version; `package.json`, the README Status line and a CHANGELOG section at HEAD all naming it; HEAD on `main`, clean, and the merge commit of a `release/<version>` PR (a squash is refused); HEAD an ancestor of `origin/main` (fetched, not assumed); no `v<version>` tag locally or on the remote; `scripts/check-docs.mjs --require-fresh --committed` clean; and exactly one `docs/verification/<date>-<version>.md` with no `TODO(agent)` left. Without `--execute` it only reports, and whether the person's consent matches (exit 2 when it does not). With `--execute` it reads the consent through `ahub sdlc release-guard -- git push <remote> <commit>:refs/tags/v<version>` (read only; the ledger `ahub sdlc authorize-release` fills from the person or the explicitly instructed configured conductor, revoked by a hub restart), repeats the preflight, checks the commit did not move, reads the consent once more, and then makes the single push of that exact commit. A rejected push is reported and never retried, and the script passes neither `--force` nor `--no-verify`; the pre-push hook from `ahub init --release-guard` still runs on that push. The push creates a lightweight tag (`<commit>:refs/tags/v<version>`), the exact form `ahub sdlc release-guard` parses and the consent receipt binds; the repository's tags are mixed (v0.19.0 and v0.18.0 are annotated, v0.17.0 is not) and `.github/workflows/release.yml` accepts either (`v*`, then `--verify-tag`), so this is a convention difference, not a gap. If the project wants annotated release tags, create the tag object first and push the tag ref, which the guard also accepts (its source object is peeled to the commit); that is a later change to this script. `AHUB_RELEASE_ROOT` points both scripts at a checkout other than the one they live in (the tests use it so nothing outside a scratch repository is touched). Neither script calls `authorize-release`: the report only names the command; the person or the explicitly instructed configured conductor records consent separately. Publication (npm, the GitHub release) stays with the tag-triggered workflow, which has its own gate (`.github/workflows/release.yml`).
414
+ `bun scripts/release-tag.ts <version> [--execute]` is the one guarded tag run. Every run repeats the whole preflight and reports every refusal at once: a valid release version; `package.json`, the README Status line and a CHANGELOG section at HEAD all naming it; HEAD on `main`, clean, and the merge commit of a `release/<version>` PR (a squash is refused); HEAD an ancestor of `origin/main` (fetched, not assumed); no `v<version>` tag locally or on the remote; `scripts/check-docs.mjs --require-fresh --committed` clean; a `docs/quickstart.md` "Upgrading to <version>" section with the global CLI and plugin step and no `TODO(agent)`; and exactly one `docs/verification/<date>-<version>.md` with no `TODO(agent)` left. Without `--execute` it only reports, and whether the person's consent matches (exit 2 when it does not). With `--execute` it reads the consent through `ahub sdlc release-guard -- git push <remote> <commit>:refs/tags/v<version>` (read only; the ledger `ahub sdlc authorize-release` fills from the person or the explicitly instructed configured conductor, revoked by a hub restart), repeats the preflight, checks the commit did not move, reads the consent once more, and then makes the single push of that exact commit. A rejected push is reported and never retried, and the script passes neither `--force` nor `--no-verify`; the pre-push hook from `ahub init --release-guard` still runs on that push. The push creates a lightweight tag (`<commit>:refs/tags/v<version>`), the exact form `ahub sdlc release-guard` parses and the consent receipt binds; the repository's tags are mixed (v0.19.0 and v0.18.0 are annotated, v0.17.0 is not) and `.github/workflows/release.yml` accepts either (`v*`, then `--verify-tag`), so this is a convention difference, not a gap. If the project wants annotated release tags, create the tag object first and push the tag ref, which the guard also accepts (its source object is peeled to the commit); that is a later change to this script. `AHUB_RELEASE_ROOT` points both scripts at a checkout other than the one they live in (the tests use it so nothing outside a scratch repository is touched). Neither script calls `authorize-release`: the report only names the command; the person or the explicitly instructed configured conductor records consent separately. Publication (npm, the GitHub release) stays with the tag-triggered workflow, which has its own gate (`.github/workflows/release.yml`).
413
415
 
414
416
  The scripts and their pure helpers live in `scripts/release-prepare.ts`, `scripts/release-tag.ts` and `scripts/lib/release-plan.ts`; they are maintainer tooling and are not part of the published package. The deterministic recovery barriers of #787 AC3 are not needed by either leg and are not part of this change.
415
417
 
@@ -427,4 +429,4 @@ A report, translation or slide deck has no commit, so the artifact identity is t
427
429
  - **Private records.** For a PII task, or a file under `pii.paths`, the record is private: every peer view, review request, status and log shows the id, hash and size only, `(path withheld)` in the request. The board keeps the path so the file can be found again, as it keeps the rest of a PII task's text. A path that matches a PII pattern is withheld the same way.
428
430
  - `hub_sdlc_record` with `deliverables` records them on a `lifecycle` entry, through the same hashing and the same owner, reviewer or console authority; a receipt without them is the old call.
429
431
  - Limit: a daemon from before this slice ignores `deliverables` on `hub_task_done` (the plugin is installed apart from the daemon), so restart the hub after an upgrade before relying on it; a done that names deliverables and shows no `Deliverables` block in its review request was recorded without them.
430
- - Not in this slice: `ahub deliverables [--task <id>]` and the dashboard column (#509 AC3, the next slice), and a hash of a file the hub cannot read.
432
+ - `ahub deliverables [--task <id>] [--approved] [--check]` lists them (#509 AC3). Not in this slice: a hash of a file the hub cannot read.
package/docs/security.md CHANGED
@@ -10,7 +10,7 @@ agent-hub connects agents that can each run commands. This page says what the hu
10
10
  - **Control-write diagnostics carry metadata only (#1126).** The daemon's write spans and event-loop probe record a closed set of operation kinds, phases, task numbers and durations, never task text, paths, ids of transports, actors or sessions, credentials, error text or external output, and nothing for a PII task or a private turn. A frame from a client that has not authenticated is never inspected for them. Retention is capped (128 requests, 256 spans, 512 recent intervals, five-minute expiry) and a record in `hub.log` is at most 2,048 bytes; client timing on stderr is off unless `AHUB_CONTROL_WRITE_TIMING=1`.
11
11
  - **Permission prompts stay on unless an operator selects a permission mode.** `ask` leaves native decisions unchanged. `ask-when-needed` grants scoped Claude file tools or Pi/local writes and edits, and a headless Claude implementer's proven shell commands through the host wrapper (#845, below); Kimi and Codex use their own native policies. `never-ask` requires explicit runtime confirmation, a person's console `y` for a config default at hub start, or retained autonomy consent bound to that peer's exact never-ask value in the untracked `config.local.json` (#999, below). Without one of these the peer runs in `ask`. Kimi's and `local`'s permission requests are relayed to the console and cancelled after `approvals.timeout_s` (default 120 s) of silence; the macOS notification for a waiting request carries the peer and the tool name only. The one exception is the hub's own tools (`hub_send` and the task tools, matched by exact name): Kimi's requests for them are approved once without a prompt and logged by name, the same trust Codex gets through `approval_mode` in the hub's config. They invoke hub-owned operations rather than arbitrary file or shell tools, and every call passes the hub's own checks. A call the always-ask policy classes (below) is never approved this way, whatever its title. Identity comes from the exact permission title, or from the earlier tool-call title bound to the same call id and resolved against the configured MCP servers when the permission title contains argument JSON (Qwen). Payload text and unrelated display titles never establish identity. Configured ACP kinds (#451) never get this exception: a request whose title spells a hub tool exactly still goes to the person approval path, and only a mapped native mode changes what the agent itself asks. For ACP agents, a payload longer than the console shows is marked as cut and never offers a session-wide grant. Pi's write, edit, shell and git write requests are relayed the same way and can be answered `Always allow <tool> until Pi restarts`: later calls of that tool by the same Pi start skip the prompt but stay inside the path guard and sandbox. The grant covers every later call of that tool whatever its payload, is kept in memory only, cannot be given from the dashboard, and each call it allows is logged by tool name. A Pi or `local` request whose call was cancelled or whose turn ended is withdrawn at once: a later answer is refused and grants nothing, an `Always allow` included. One shell command runs without a hub approval: the `!command` a person types in the managed Pi TUI. It stays inside the same sandbox and denylist, and the hub takes it only from the verified owner process of a Pi in TUI mode, while Pi is idle, on a reservation that is used once; a model's own bash call never takes that path (issue #253). `--unattended` turns prompts off, says so loudly, and is never the default; a classed call from a hub-owned ACP peer, the local worker or Pi still waits for a person under it.
12
12
  - **A committed config cannot choose launch commands, credential files, data endpoints, permission modes or a wider sandbox.** The machine-local fields (`kimi_cmd`, `permission_modes`, `codex_bin`, `pi.cmd`, `checks`, `mlx.bin`, `mlx.runtimeDir`, `mlx.modelPath`, `omniroute.urls`, `omniroute.access_hosts`, the `omniroute` key files, `memory.worker_url` (still accepted, ignored since 0.16.0), `local.read_allow`, `local.bash_network`, `local.network_allow`, `local.doc_read`, `local.web_fetch`, `terminal.open`, `tasks.auto_pull`, `tasks.queue_backpressure`, `context.max_assignment_used`, `schedules` (the `every`, `cron` and `tz` forms alike), `folder_triggers`, `corpus.paths`, `invocation`, `start.agents`, `instances`, `action_policy`, `rooms`) apply only from a config file git confirms nobody committed (`instances`, `action_policy` and `rooms` are read only from an untracked `.agenthub/config.local.json`; in `config.json` they are ignored even when untracked): `.agenthub/config.json` or `.agenthub/config.local.json`, matched by file identity so no other spelling the file system accepts slips past, and `.agenthub` itself not a committed symlink or submodule. Without a repository, or when git fails, they keep their defaults; an empty value always means the default. "Untracked" is answered by the repository that contains the project: a checkout copied or extracted into an unrelated repository, or into an ignored directory of one, is trusted like your own files. So a cloned repository cannot choose a launch command, a completion check, a gateway to send a key file to, a memory endpoint, a permission mode, the command that opens a terminal, which agents `ahub up` opens terminals for, a folder the hub reads, work proposed on a timer, a host a model may fetch from, which of your agents' skills other peers may call, or a wider sandbox. A command in `checks` runs as you, outside the local worker's sandbox, like a git hook. So do the `setup` and `verify` commands of a benchmark suite (issue #251): `ahub bench run <suite.json>` is a person's command, refused from an agent session, and it resets the work tree for each attempt, so it runs only with `bench.enabled`, at the root of a clean repository that tracks nothing under `.agenthub/`. Nothing in `routing.toml` or in task text is ever run. `routing.toml` and the other shared fields still come from the checkout, and they matter: `routing.toml` picks the models the local worker and the hub's inference use at your gateway and can turn the PII constraint off, and roles and budget shape who does what. Review them in a repository you do not trust. The hub's own git calls outside the sandbox (snapshots, facts, config trust, project discovery, memory recall, the bench runner) all use the single hardened implementation in the exact-pinned first-party `@staix/agent-mem/platform` package, re-exported by `src/hub/git.ts`, which disables the programs a repository's configuration can name — the file-system monitor, hooks, external diff, the pager, and the credential and askpass helpers, with prompts failing closed — so a config that ever does get written cannot run code with the hub's rights. Of those, the monitor, hooks and external diff are reachable by commands the hub issues (the planted-program test exercises all three; a `diff --no-index` reads the repo's config, so that call also passes `--no-ext-diff` and `--no-textconv`); the pager is not (every call pipes stdout). Credential and askpass helpers are disabled for project repositories; memory sync alone may use credentials for its own memory repository, without prompts. The cost: `core.fsmonitor=false` also turns off a person's enabled built-in monitor for the hub's own calls, so the per-turn snapshot's `add -A` scans the whole tree (the snapshot Git calls of one burst share a 250 ms budget, each call at most 10 s; a boundary that runs out is recorded as unknown, never as a stale baseline) (#576). git has no global switch for filter drivers, and of the hub's commands only the snapshot's `add`/`restore` would run one (its driver command lives in the protected `.git/config`).
13
- - **Telemetry holds no bodies.** `.agenthub/state/events.jsonl` (issue #40) records envelope ids, routing and sizes, task ids and states, overlapping paths and token counts, and the task id (with a PII flag) that each usage and token record is attributed to. It never records a message body, a task title or detail, and marks private (PII) envelopes and tasks as such. Budget pauses, resumes and quota resets carry the peer, window, times and the hub's own reason text, and a person's release authorization its commit, version and docs digest. The usage roll-up (#683) folds the usage, token and limit records into `hub.db` and joins issue, PR and release from the board when read; `ahub usage` and the console's Usage panel answer only the console and a configured planner, and a planner not during a private turn. Kimi's token records (#285) are counted from its own session wire log under its home: only the four token integers, the model label and the row time are copied, and the record id is an opaque hash, never a path, session id or text. The log stays on the machine; `ahub export` only prints it. Two opt-in stores copy numbers from it into your home directory, shared by every project on the machine (directories mode 700, files 600): research records (issue #247, `research.enabled`) in `~/.agenthub/research/` hold per approved task the ids, states, counts, tokens, times and PII flag the events hold, a person's label from a closed list, and a hash in place of the project id; benchmark runs (issue #251) in `~/.agenthub/bench/` hold suite name and hash, arm label, the attached peers' ids, models and permission modes, task ids, outcomes, exit codes, counts, tokens and times, never suite text or a command's output.
13
+ - **Telemetry holds no bodies.** `.agenthub/state/events.jsonl` (issue #40) records envelope ids, routing and sizes, task ids and states, overlapping paths and token counts, and the task id (with a PII flag) that each usage and token record is attributed to. It never records a message body, a task title or detail, and marks private (PII) envelopes and tasks as such. Budget pauses, resumes and quota resets carry the peer, window, times and the hub's own reason text, and a person's release authorization its commit, version and docs digest. The usage roll-up (#683) folds the usage, token and limit records into `hub.db` and joins issue, PR and release from the board when read; `ahub usage` and the console's Usage panel answer only the console and a configured planner, and a planner not during a private turn. Kimi's token records (#285) are counted from its own session wire log under its home: only the four token integers, the model label and the row time are copied, and the record id is an opaque hash, never a path, session id or text. agy's (#1280) are read from its session transcript through the hook-gated readback the same way: the token integers and an opaque hash of session and step. A person's model override (`model_change`, #1317) records the peer, the previous and new model ids and the next-turn boundary; a visual (#1058) its id, kind, byte and image counts and optional task id. The log stays on the machine; `ahub export` only prints it. Two opt-in stores copy numbers from it into your home directory, shared by every project on the machine (directories mode 700, files 600): research records (issue #247, `research.enabled`) in `~/.agenthub/research/` hold per approved or cancelled task (record v2) the ids, states, counts, tokens, times and PII flag the events hold, counts and closed words only, a person's label from a closed list, and a hash in place of the project id, and `ahub research export --dataset <dir>` writes an allowlisted copy meant to leave the machine (peers reduced to family names, project-chosen role and profile names dropped, owner-only files in a directory inside the home and outside every registered project, compared as real paths); benchmark runs (issue #251) in `~/.agenthub/bench/` hold suite name and hash, arm label, the attached peers' ids, models and permission modes, task ids, outcomes, exit codes, counts, tokens and times, never suite text or a command's output.
14
14
  - **Snapshots stay in your repository.** Per-turn snapshots (issue #33) are git objects in the project's own object store, written through a temporary index; nothing is referenced, pushed or copied elsewhere, and `git gc` prunes them. They hold what the work tree held, including untracked files that are not ignored, so keep secrets in ignored files. They carry the repository's own permissions, and nothing caps their disk use but `git gc`. A turn of a peer holding an open PII task is not snapshotted; a PII file left in the project is snapshotted by later turns like any other file. `ahub undo` restores only files whose current content is exactly what the turn left.
15
15
  - **The edit hook reads, never decides.** `ahub check-path --hook` (issue #32) reads hub.db and returns context for Claude and a line for you; it sets no permission decision, so your permission rules stay in charge. It names other owners' task ids, titles and states, which then reach Claude's model; PII tasks are left out.
16
16
  - **The session record holds identities only.** `.agenthub/state/sessions.json` (issue #37, mode 600) keeps each attached peer's recovery metadata: launch options, session and thread ids, Pi's session file path. No message or task text; loss notices name deliveries by id, sender and public task title. The Pi continuity bookmark that recovery keeps across stop and archive (`.agenthub/state/recovery-continuity.json`, mode 600, at most 16 KiB) holds identities only too: operation and project ids, the project root, and Pi's session id, session file, backend and model name; a bookmark that is not a private regular file, is larger than that or names another project or operation reads as invalid, which is kept distinct from no bookmark.
@@ -26,7 +26,7 @@ agent-hub connects agents that can each run commands. This page says what the hu
26
26
 
27
27
  ## Native memory authority and profile selection
28
28
 
29
- The portable core is the exact-pinned first-party `@staix/agent-mem` 0.22.1 package; it does not replace the hub's caller identity, PII policy or task-write authority. Deny, hardened git, real paths and process identity are single-sourced in its Node-safe platform entry. Current project privacy is applied by the hub before promotion and again before model-facing reads. Standalone person reads are unscreened, and the standalone `--policy-free` acknowledgement grants no hub or peer authority.
29
+ The portable core is the exact-pinned first-party `@staix/agent-mem` 0.23.1 package; it does not replace the hub's caller identity, PII policy or task-write authority. Deny, hardened git, real paths and process identity are single-sourced in its Node-safe platform entry. Current project privacy is applied by the hub before promotion and again before model-facing reads. Standalone person reads are unscreened, and the standalone `--policy-free` acknowledgement grants no hub or peer authority.
30
30
 
31
31
  `hub_memory_search`, `hub_memory_show` and `hub_memory_graph` take the project from the daemon, require `memory_read`, refuse private turns and validate UTF-8 input before logs or storage work. Complete records and graph metadata are screened before counts and truncation. Search is at most 20 lines/4 KiB (default 8); show accepts at most 8 ids and returns at most 8 KiB; graph is at most 120 nodes/240 edges/6 KiB (default 60 nodes), with no edge lacking an endpoint. Unknown, ambiguous and withheld ids share one neutral refusal. Arguments and results are excluded from hub-managed capture; this does not govern a vendor's independent transcript or memory hooks.
32
32
 
@@ -696,7 +696,10 @@ console-role only, and `ahub peer takeover` is refused to agent shells and
696
696
  `--live-test`. A standby connection runs no peer operation until it owns the peer,
697
697
  and the selected standby is rechecked for membership, launch and isolation,
698
698
  lifecycle and claim generation after its preface. Standby ids and reported PIDs are
699
- display metadata, never native-session or permission evidence. Takeover detaches
699
+ display metadata, never native-session or permission evidence. The current owner's
700
+ attachment facts in the console's detailed status (#1310: client pid, attach time
701
+ and, once the host verified it, a short native session id) are display metadata
702
+ too, dropped with the socket and withheld from `status.json`, events, logs and memory. Takeover detaches
700
703
  the old hub connection, not its native process; that session's later hub calls are
701
704
  refused, and takeover never marks a delivery completed.
702
705
 
@@ -704,6 +707,13 @@ Kimi's pull-only TUI owns its tool approvals and session. The person's launcher
704
707
  merges/removes only the hub MCP entry; the daemon never edits that file and
705
708
  refuses headless starts while a stale entry exists. An attached Kimi TUI blocks
706
709
  controlled recovery instead of being treated as a hub-owned headless process.
710
+ Its completion facts (#1282) are admitted only on the receipt-verified `kimi-tui`
711
+ compat entry (2.5.2) through the launcher's `native_hook_launch` authority and a
712
+ launch-scoped plugin under the native home; another version launches the plain
713
+ TUI without completion receipts. The launcher replaces another launch's plugin only
714
+ after its recorded owner is proven gone, under one fail-closed lock per native home
715
+ that is never removed automatically. Re-reads carry no prompt, response, error,
716
+ task command or title text, and native-person results are private stubs.
707
717
  Scoped Pi and local-worker write/edit grants check both the original project-relative
708
718
  path and its resolved target for protected agent configuration names. A protected
709
719
  directory symlinked to an ordinary project directory still asks a person; an alias
@@ -720,7 +730,7 @@ the other names; look-alikes such as `.agent-backup/` or `src/gemini.ts` stay gr
720
730
  The latter is conservatively protected by name; its default documented location
721
731
  is in the home directory, not a project loader. The source inventory and limits
722
732
  are in [the configuration inventory](verification/2026-10-11-agent-config-inventory.md).
723
- Instruction files `AGENTS.md`, `CLAUDE.md` and `QWEN.md` remain grantable by default.
733
+ Instruction files `AGENTS.md`, `CLAUDE.md`, `QWEN.md` and `GEMINI.md` (#1280) remain grantable by default.
724
734
  A person can set `scoped_grants.protect_instructions` to true to require approval
725
735
  for scoped writes and edits through original or resolved spellings. It applies
726
736
  live after a settings write, undo or reload (turning it off from an edited file needs the confirmed terminal reload), never changes reads or never-ask, and cannot
@@ -729,7 +739,7 @@ withholds the scoped mode on these writes so an older hook grants nothing too.
729
739
 
730
740
  Agent CLI versions (#691). One policy gates the Codex and Kimi fresh reviewers, the headless Claude implementer, Jcode, agy, Qwen (ACP and the dual-output TUI) and Pi (#843): a receipt-verified version passes, Jcode and agy also start an untested version inside their compatible line with a note, and any other version is refused unless the person trusts that exact version on this machine with `ahub agents trust <id> <version>`. The record is `agent-compat.json` in the hub home (`~/.agenthub`), written atomically with mode 600 and at most 64 entries; an unreadable, linked, oversized or invalid record trusts nothing. Agent shells and `--live-test` may read `ahub agents` but cannot trust or revoke. Trust lifts only the version pin: the fresh reviewers' runtime proofs still run before every child, and an untested version's native contract stays unverified.
731
741
 
732
- Qwen Code's optional production ACP peer requires the qwen-code native identity, a compat-admitted version (receipt-verified 0.25.0, 0.26.0, 0.26.1, 0.26.4, 0.26.5 and 0.26.8)
742
+ Qwen Code's optional production ACP peer requires the qwen-code native identity, a compat-admitted version (receipt-verified 0.25.0, 0.26.0, 0.26.1, 0.26.4, 0.26.5, 0.26.8 and 0.26.9)
733
743
  and advertised mode ids before setting a policy or starting work. Its own tools
734
744
  have no hub sandbox; auto-edit approves file edits, yolo every tool. Configured
735
745
  never-ask still requires the person's confirmation. qwen_cmd is machine-local and
@@ -738,7 +748,7 @@ this production table. Startup diagnostics withhold native host/payload details.
738
748
 
739
749
  Jcode is optional, source/version-fenced and runs native tools without a hub sandbox or hub permission-mode mapping. .jcode joins the scoped-grant exclusion, including lexical/resolved aliases. Only a person writes its unique project MCP entry; foreign/override/symlink entries refuse. The hub owns a private daemon/socket and ACP group, never the global daemon, and copies no credentials. It accepts a plain jcode release from 0.98.0 up to, not including, 1.0.0 (#529; only 0.98.1, 0.99.0 and 0.99.1 are verified, and another version in that line starts with a note), or an exact version the person trusts (#691), and runs every owned jcode process (probe, serve, ACP and TUI) with `JCODE_NO_TELEMETRY=1`, because jcode's telemetry can report the provider account's email. Native diagnostics withhold payloads/hosts. Hub tools are bound to an owned launch (task 368): the shared project entry names no peer, each owned Jcode process carries its own peer id and launch id, the daemon admits a hub-tools connection for a Jcode peer only with the id of that peer's current owned launch, and a tools-mode server started without a peer id refuses every tool. Extra Jcode instances (#685, at most 8) are declared only through `instances` in an untracked `.agenthub/config.local.json`, start headless, and each needs its own `jcode_home`, which may not be the base home or another instance's, nor nested in or around one, however it is spelled. Unproved conversation loads refuse; no fresh replacement is called a restore. A finished task's advisory about the project root checkout names the branch only after the hub's whole public-text screen (`Tasks.publicMetadataText`: a PII pattern, a secret shape and a project-private path) passes it and it is a plain ref name; otherwise a generic sentence carries no part of it (#572). Native TUI launch exposes its private socket/session tuple only to the authenticated console, bound to the same live owned socket and session after read-only native persistence proof. Tools/status do not receive it. The TUI has no single-writer guard: person input may interleave with ACP. Native policy remains unmanaged and closing the frontend does not stop ACP. An `important` message reaches a busy Jcode as a soft interrupt written to the hub's own private server socket for the owned session (#572); the hub never cancels a turn to deliver, and a steer that was sent but not acknowledged, or whose turn ended first, is held as `needs_review`, never replayed. Jcode has no switch to choose its MCP servers, so the owned serve and ACP processes load every server named in `<JCODE_HOME>/mcp.json` and the project's `.jcode/mcp.json`, `.mcp.json` and `.claude/mcp.json`, besides the hub's own; `ahub doctor` lists their names only. Point `jcode_home` at a separate home, or remove a server from those files, to keep it out.
740
750
 
741
- Antigravity (`agy`, #485) is optional, cloud and off roster by default. The launcher accepts the Antigravity CLI 1.4.x line; only 1.4.2, 1.4.7 and 1.4.12 have a native receipt ([1.4.7](verification/2026-10-26-agy-1.4.7.md), [1.4.12](verification/2026-11-03-agy-1.4.12-launcher-nonce.md)), so another 1.4.x patch launches with a warning that it is unverified, and every other version is refused before a hub connection or plugin write (#589) unless the person trusts that exact version (#691). It runs native tools under its own unmanaged permissions, without a hub sandbox or hub permission-mode mapping; headless, resume, unattended and native permission flags are refused. Its roles are capped at implementer and verifier, so it never plans, reviews or receives PII work. Only the person's `ahub agy` launcher writes its three plugin files in `.agents/plugins/agent-hub/` (`plugin.json`, `mcp_config.json`, `hooks.json`): linked or foreign entries refuse before any write, cleanup removes only that launch's matching files, and it never writes project-level `.agents/hooks.json` or the person's `~/.gemini` settings. Conductor starts are refused, and a declared agy extra instance is refused at config load (#625). Hook authority (`native_hook_launch`) comes only from that launcher, belongs to the console socket that began it and ends when that socket closes; hook input over 1 MiB is refused, and observations are bound to the daemon, launch, native session and project.
751
+ Antigravity (`agy`, #485) is optional, cloud and off roster by default. The launcher accepts the Antigravity CLI 1.4.x line and exact 1.5.3; only 1.4.2, 1.4.7, 1.4.12 and 1.5.3 have a native receipt ([1.4.7](verification/2026-10-26-agy-1.4.7.md), [1.4.12](verification/2026-11-03-agy-1.4.12-launcher-nonce.md), [1.5.3](verification/2026-11-08-agy-1.5.3.md)), so another 1.4.x patch launches with a warning that it is unverified, and every other version, other 1.5.x included, is refused before a hub connection or plugin write (#589) unless the person trusts that exact version (#691). It runs native tools under its own unmanaged permissions, without a hub sandbox or hub permission-mode mapping; headless, resume, unattended and native permission flags are refused. Its roles are capped at implementer and verifier, so it never plans, reviews or receives PII work. Only the person's `ahub agy` launcher writes its three plugin files in `.agents/plugins/agent-hub/` (`plugin.json`, `mcp_config.json`, `hooks.json`): linked or foreign entries refuse before any write, cleanup removes only that launch's matching files, and it never writes project-level `.agents/hooks.json` or the person's `~/.gemini` settings. Conductor starts are refused, and a declared agy extra instance is refused at config load (#625). Hook authority (`native_hook_launch`) comes only from that launcher, belongs to the console socket that began it and ends when that socket closes; hook input over 1 MiB is refused, and observations are bound to the daemon, launch, native session and project.
742
752
 
743
753
  Configured ACP adapters (#451 A3) are declared only by the person, in a Git-confirmed untracked `.agenthub/config.local.json`. The whole declaration (command, argv, identity, roles and mode maps) is machine-local authority: a shared or tracked declaration is ignored with a notice, settings never edit it, and changes need a hub restart. The hub runs only the declared argv, never a module or callback. Argv that selects a leader, attach, connect or remote mode is refused, and a Grok declaration needs `--no-leader`. Each start requires the pinned native identity, an exact `session/load` echo on resume, and positive ownership of the spawned process and its original process group; unknown ownership refuses, and stop signals only that verified group. An agent that hands its work to a process outside that group is outside this boundary. Without a mode map native policy is unmanaged, and there is no hub sandbox either way. Configured kinds are cloud peers (never PII), headless only and off roster by default, and the hub makes no quota, context or controlled-recovery claims for them. A read-only instance needs the native read-only mode acknowledged before a prompt and is limited to read and review hub tools, both in the published MCP list and at the host; its native non-read tool requests are rejected, and no setting or runtime change can raise it. These agents' stderr and error text are withheld from logs, and previews redact the executable, argv and session id.
744
754
 
@@ -823,13 +833,29 @@ result. Failure logs retain only the closed cause and known declined count.
823
833
  `ahub memory declined` is person-only (refused to agent shells and `--live-test`)
824
834
  and uses bounded physical-row inspection to show IDs and rule categories, never
825
835
  note text or literal patterns; incomplete inspection is not a zero count.
826
- Default-host v3 sync remains unsupported because the hub supplies no fresh
827
- placement attestation. No activation proof is fabricated. The native amem CLI
828
- provides person-only activation from 0.22.0; the hub does not invoke it, and
829
- default-host v3 sync still requires legitimate fresh host-attestation integration
830
- (#1173). Suggested graph edges are off by default
836
+ Since agent-mem 0.23.0 the daemon passes a fresh `placementAttestation` on every
837
+ sync pass, built by the package's own `createPlacementAttestation` for the memory
838
+ home (`src/hub/memory-sync-attestation.ts`, #1337); the hub supplies no writer
839
+ facts of its own and never fabricates the proof. A refusal is the closed cause
840
+ `writer` with a category, cause class and next step; the package's message, paths
841
+ and versions never enter status or logs. The native amem CLI provides person-only
842
+ activation from 0.22.0; the hub does not invoke it, and whether an activated store
843
+ syncs on a person's machine is not established by the isolated fixtures (#1173). Suggested graph edges are off by default
831
844
  and are not evidence until a person confirms them. Operational details and limits: [hub memory](memory.md).
832
845
 
846
+ Agent-authored Records (#1320). Managed headless Codex, Kimi, Qwen, Jcode, Pi and
847
+ local turns get the agent-mem Record instruction; reviewers and person-owned TUI
848
+ prompts do not. amem's effective `records` policy (`recordsEnabled`) gates
849
+ instruction, capture and Record preference, and an unavailable policy disables
850
+ them. Only a completed final answer of a public, local, task-bound turn is
851
+ captured: private, PII, review-scoped, remote, unbound and incomplete turns are
852
+ excluded, and complete text and anchors clear the secret, PII, private-path and
853
+ deny screens before storage as `Record` capture rows, never curated notes or
854
+ evidence. `amem:` lines are stripped before invocation routing, shared-answer
855
+ publication, digests, the journal and the console event tap; the native console
856
+ can still show the original answer. Diagnostics are counts and byte sizes only,
857
+ and no model or network call is added.
858
+
833
859
  ## Floating pet retirement (#1012)
834
860
 
835
861
  The standalone JXA host, WKWebView, window bridge and its packaged assets are
@@ -1293,6 +1319,56 @@ source note identifies incoming-message text and never attributes it as the
1293
1319
  recipient peer's own speech. `status.json`, events, logs and memory never
1294
1320
  receive a derived copy. Removed-instance and Local bindings are not retained.
1295
1321
 
1322
+ ## Person-owned live model overrides (#1317)
1323
+
1324
+ `ahub model set|clear|list` is person-only: the daemon refuses a non-console
1325
+ caller, so agent shells, `--live-test` and every hub-tool caller are refused,
1326
+ list included, and a conductor role grants nothing. The dashboard path needs a
1327
+ person settings session with preview and confirm. An override is desired
1328
+ runtime state in `hub.db` (cleared by `ahub reset`), applies from the next
1329
+ eligible turn and never changes a running turn; a stale callback cannot mark a
1330
+ newer value or another adapter owner applied. Model ids are one bounded token
1331
+ (letters, digits and `._:/@+-`, at most 128 bytes, no leading dash), so an id
1332
+ cannot inject a flag. The override grants no model, provider or account
1333
+ authority, leaves the machine-local `instances.*.model` and `*_cmd` tier 3
1334
+ values as they are, and an unsupported surface is refused without a restart.
1335
+
1336
+ ## Optional adec decision route (#554)
1337
+
1338
+ A `decision` route with `decision_backend = "adec"` is off unless configured and
1339
+ supports `shadow` only (`enforce` is refused at config load); the proposed pair
1340
+ never replaces the heuristic model, effort or conversation. The endpoint must be
1341
+ numeric HTTP loopback with path `/v1/route`, no userinfo, query or fragment; no
1342
+ redirects, ambient proxies, credentials or browser headers are used, and the hub
1343
+ never starts the service or reads its credentials. Evidence is a bounded 8 KiB
1344
+ projection (a capped latest-user fragment, recent assistant and tool tails,
1345
+ screened tool names and numeric dimensions) that passes the public-view pattern,
1346
+ private-path and secret checks; tool arguments, system or developer instructions
1347
+ and older history are never sent. Review-observer-excluded and remote turns skip
1348
+ it. A PII or private turn needs positive `onCampus()` before any evidence is
1349
+ built and then sends dimensions only with its privacy flags. Privacy, observer
1350
+ and authority facts are rechecked across every await, a changed authority
1351
+ refuses the stale turn, and route events keep closed metadata only, never
1352
+ evidence, credentials or service error prose.
1353
+
1354
+ ## Bounded visual attachments (#1058)
1355
+
1356
+ `hub_visual` stores a validated acon `VisualPayload` in `hub.db` under hub
1357
+ admission ceilings (1 MiB serialized and per-field UTF-8 limits; oversized data is
1358
+ refused, never truncated), screening every text field and record key before and
1359
+ after sanitization. Peers and the bus get only a stub
1360
+ (`[visual <id>] <kind>: <title or caption>` and `refs.visual`); an explicit
1361
+ remote recipient refuses the whole call and nothing crosses the room broker. Publication is refused in a
1362
+ private turn or on a PII task, including when privacy changes during an awaited
1363
+ image read, and current privacy gates `hub_visual_show`, snapshots and asset
1364
+ reads later. Images go through the guarded deliverable reader: a public PNG, JPEG
1365
+ or WebP of at most 8 MiB whose signature matches its extension, refusing SVG,
1366
+ secret or PII paths, hub state, outside paths, links swapped during inspection
1367
+ and two names for one file; paths are replaced with opaque asset ids, and each
1368
+ asset read repeats the guard and hash check, with the acon provider binding the
1369
+ project and instance. Raw tool input and results never reach observer capture,
1370
+ even on refusal.
1371
+
1296
1372
  ## Questions a peer asks the person (#1053)
1297
1373
 
1298
1374
  `hub_ask` lets any peer put a structured question (one to four questions, two to eight options each) to the person. A question is not a permission: it is its own record in `hub.db` (`questions`), has its own control messages, and never expires as a denial.
package/docs/smoke.md CHANGED
@@ -98,6 +98,20 @@ operationId, console hello and initiating-peer audit (225 related tests passed).
98
98
  This is fixture/CLI proof, not an actual native Pi restoration; that live leg
99
99
  remains owed in #273.
100
100
 
101
+ ## Kimi TUI completion facts (#1282)
102
+
103
+ Run this only on Kimi 2.5.2 in a disposable Git project and an isolated `KIMI_CODE_HOME`; `compat.ts` `kimi-tui` refuses every other version. The launcher must print its launch-scoped plugin notice before the TUI starts, and `plugins/installed.json` must contain only that launch's `agentHubLaunch` plus its owner pid and process signature. Another project's active owner must not be replaced; only a proven-gone owner is stale. Do not use the person's project or global `config.toml`.
104
+
105
+ 1. `ahub up --no-console --no-agents` in the disposable project, then `ahub kimi --mode tui`. `--print-command` must describe the same conditional plugin without writing it. Refuse `native_hook_launch` once: the TUI still starts and reports that completion receipts are off. An unverified version also starts without them.
106
+ 2. Send an ordinary prompt. `ahub results --json` shows one private `done` only after `turn.ended`, with the same native turn id in `native_turn_end`.
107
+ 3. Start a real background `sleep 15` through Kimi's Bash tool. The results stay empty while `agents/main/tasks/<id>.json` says `running`; after the task and notification turn finish, exactly one `done` appears even if TaskStarted/Notification hooks never arrive.
108
+ 4. Queue a hub message with `ahub say @kimi native-origin-probe`, then call `mcp__agent-hub__hub_inbox` once. The pulled message creates no second person-originated run; the person's prompt that called the tool settles once.
109
+ 5. Block the first Stop with exit 2. `agent.turn.ended` may appear, but no `turn.ended` and no completion appears until the final Stop is allowed. Check delayed final output and a replayed old Stop.
110
+ 6. Point the isolated provider at a closed loopback port. StopFailure alone creates no result until the matching `turn.ended(reason: "failed")`; then the outcome is `failed`. Interrupt yields `cancelled` and no unread result. Closing the launcher or restarting the daemon yields at most one `interrupted` for that run.
111
+ 7. Confirm forged, stale, wrong-launch, wrong-session and wrong-cwd observations create no run or receipt, and prompt, response, error, task command and task title are absent from completion rows, `events.jsonl`, `hub.log` and memory. Exit the TUI normally: the launch-scoped plugin record and files are removed and other plugins remain.
112
+
113
+ The exact-version receipt is `docs/verification/2026-11-08-kimi-tui-completion-1282.md`. A version lacking any of these readbacks keeps `settle` disabled.
114
+
101
115
  ## Kimi usage counting (#285 AC11)
102
116
 
103
117
  The [2026-10-25 Kimi 2.3.1 receipt](verification/2026-10-25-kimi-usage-ac11.md)
@@ -227,7 +227,7 @@ interface Envelope {
227
227
  kind: "chat" | "task" | "review" | "status" | "budget" | "presence";
228
228
  priority: "important" | "status" | "fyi";
229
229
  body: string; // agent conclusions only, never tool noise
230
- refs?: { repo?: string; branch?: string; commit?: string; paths?: string[]; task?: string };
230
+ refs?: { repo?: string; branch?: string; commit?: string; paths?: string[]; task?: string; visual?: string };
231
231
  ts: number;
232
232
  }
233
233
  ```
@@ -439,10 +439,15 @@ inside a peer.
439
439
  hub's own count of Kimi's session wire log (`src/adapters/kimi-wire-usage.ts`, wire protocol 1.5, read on Kimi 2.3.0; the issue
440
440
  read 2.1.1), not an ACP total: each `usage.record` row of the owned session, sub-agents included, is a `usage` event
441
441
  (`source: kimi_wire`) and a `tokens` event, and the rolling 5 h sum of `inputOther + inputCacheCreation + output` (cache reads are
442
- recorded, never counted) is the `tokens` window, source `hub count, kimi wire log, of kimi_tokens_5h N`. Any read the reader
442
+ recorded, never counted) is the `tokens` window, source `hub count, kimi wire log, of kimi_tokens_5h N`. #1281 adds one coalesced
443
+ delayed drain per peer after each cue (250 ms, 1,000 ms and 2,000 ms, at most three follow-ups), cancelled on stop, offline and
444
+ session replacement; offline keeps the final synchronous `collect -> drop`. A native row time selects a unique interval among the
445
+ 32 most recent captured turns, with no unique match recorded as `unattributed`. Late rows update usage, token increments, the
446
+ roll-up and soft window without rewriting an earlier `turn_end` or incrementing a later turn. A reader slice commits offsets only
447
+ after its rows are recorded, so cancellation cannot advance past an unrecorded row. Any read the reader
443
448
  cannot trust gives unknown (one notice, the reading dropped), never a short count. `ahub budget set`
444
449
  feeds a reading by hand. `local` has no quota and is never paused.
445
- - Gate at `budget.gate` (default 0.9) on any fresh window; readings older than
450
+ - Gate at `budget.gate` (default 0.99, a registered setting) on any fresh window; readings older than
446
451
  `budget.stale_min` are ignored. Checkpoint first, pause second: the peer gets one
447
452
  important envelope asking it to write `.agenthub/checkpoint.md` and call `hub_checkpoint`,
448
453
  the hub waits up to `budget.checkpoint_timeout_s`, then pauses it; a hard-limited peer is