@staix/agent-hub 0.34.1 → 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 (159) hide show
  1. package/CHANGELOG.md +47 -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 +12 -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-compat-python-startup-1248.md +51 -0
  33. package/docs/verification/2026-11-07-compat-receipt-harness-1248.md +105 -0
  34. package/docs/verification/2026-11-07-compat-receipt-producers-1248.md +96 -0
  35. package/docs/verification/2026-11-07-compat-workflow-1248.md +94 -0
  36. package/docs/verification/2026-11-07-daemon-startup-1232.md +169 -0
  37. package/docs/verification/2026-11-07-jcode-receipt-reconstruction-1248.md +118 -0
  38. package/docs/verification/2026-11-07-kimi-delayed-usage.md +34 -0
  39. package/docs/verification/2026-11-07-kimi-review-watch-1284.md +115 -0
  40. package/docs/verification/2026-11-08-agy-1.5.3.md +96 -0
  41. package/docs/verification/2026-11-08-agy-acp-t0.md +56 -0
  42. package/docs/verification/2026-11-08-hub-visual-1058.md +161 -0
  43. package/docs/verification/2026-11-08-jcode-0991-model-setter-1317.md +143 -0
  44. package/docs/verification/2026-11-08-kimi-252-model-setter-1317.md +137 -0
  45. package/docs/verification/2026-11-08-kimi-tui-completion-1282.md +45 -0
  46. package/docs/verification/2026-11-08-kimi-wire-yield-1283.md +31 -0
  47. package/docs/verification/2026-11-08-live-model-override-1317.md +250 -0
  48. package/docs/verification/2026-11-08-qwen-0269-acp.md +138 -0
  49. package/docs/verification/2026-11-08-qwen-0269-model-setter-1317.md +91 -0
  50. package/docs/verification/2026-11-09-0.35.0.md +129 -0
  51. package/docs/verification/2026-11-09-headless-records-1320.md +157 -0
  52. package/docs/verification/claude-permission-transport-native.py +10 -3
  53. package/docs/verification/claude-shell-wrapper-native.py +8 -8
  54. package/docs/verified.json +245 -15
  55. package/package.json +3 -3
  56. package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
  57. package/plugins/agent-hub/kimi-hook.js +20397 -0
  58. package/plugins/agent-hub/kimi-review-policy-worker.js +217 -0
  59. package/plugins/agent-hub/server.js +2701 -664
  60. package/scripts/check-docs.mjs +12 -1
  61. package/src/adapters/acp-model-policy.ts +56 -0
  62. package/src/adapters/acp.ts +175 -5
  63. package/src/adapters/agy/entry.ts +8 -5
  64. package/src/adapters/agy/hooks.ts +65 -15
  65. package/src/adapters/channel-peer.ts +33 -1
  66. package/src/adapters/claude-implementer.ts +16 -3
  67. package/src/adapters/codex-appserver.ts +118 -8
  68. package/src/adapters/codex-review-isolation.ts +3 -1
  69. package/src/adapters/compat.ts +11 -6
  70. package/src/adapters/definitions/builtin-metadata.ts +8 -1
  71. package/src/adapters/definitions/native-cli-entry-kimi.ts +76 -7
  72. package/src/adapters/definitions/native-cli-preview-common.ts +2 -2
  73. package/src/adapters/definitions/native-cli-preview-kimi.ts +9 -2
  74. package/src/adapters/definitions/reviewer-variants.ts +3 -1
  75. package/src/adapters/kimi-review-policy-worker.js +2 -0
  76. package/src/adapters/kimi-review-policy-worker.ts +85 -0
  77. package/src/adapters/kimi-review-policy.ts +68 -0
  78. package/src/adapters/kimi-review.ts +41 -41
  79. package/src/adapters/kimi-tui-facts.ts +153 -0
  80. package/src/adapters/kimi-wire-usage.ts +33 -8
  81. package/src/adapters/local-worker.ts +59 -20
  82. package/src/adapters/model-token.ts +3 -0
  83. package/src/adapters/pi.ts +27 -5
  84. package/src/adapters/registry-types.ts +34 -3
  85. package/src/adapters/registry.ts +1 -0
  86. package/src/adapters/reviewer-policy-watch.ts +86 -0
  87. package/src/adapters/reviewer-print.ts +80 -18
  88. package/src/cli/agents.ts +1 -1
  89. package/src/cli/facts-hook.ts +4 -2
  90. package/src/cli/help.ts +3 -2
  91. package/src/cli/identity.ts +3 -0
  92. package/src/cli/kimi-facts-hook.ts +11 -0
  93. package/src/cli/kimi-plugin.ts +157 -0
  94. package/src/cli/main.ts +48 -9
  95. package/src/cli/model.ts +52 -0
  96. package/src/cli/output.ts +23 -1
  97. package/src/cli/reset.ts +1 -1
  98. package/src/cli/status-lines.ts +8 -4
  99. package/src/hub/budget.ts +1 -1
  100. package/src/hub/bus.ts +40 -2
  101. package/src/hub/code-facts.ts +10 -3
  102. package/src/hub/completions.ts +3 -1
  103. package/src/hub/config-apply.ts +2 -1
  104. package/src/hub/control-client.ts +1 -1
  105. package/src/hub/daemon.ts +1050 -106
  106. package/src/hub/deliverable-files.ts +38 -5
  107. package/src/hub/dogfood-profile.ts +1 -1
  108. package/src/hub/envelope.ts +1 -1
  109. package/src/hub/events.ts +28 -18
  110. package/src/hub/execution-lineage.ts +120 -0
  111. package/src/hub/execution-reference.ts +36 -0
  112. package/src/hub/hub-tools.ts +21 -2
  113. package/src/hub/instances.ts +4 -3
  114. package/src/hub/kimi-events.ts +46 -0
  115. package/src/hub/kimi-usage.ts +132 -29
  116. package/src/hub/lifecycle.ts +31 -15
  117. package/src/hub/limits.ts +13 -4
  118. package/src/hub/manager.ts +15 -2
  119. package/src/hub/memory-policy.ts +2 -1
  120. package/src/hub/memory-sync-attestation.ts +54 -0
  121. package/src/hub/memory-sync-state.ts +20 -5
  122. package/src/hub/model-overrides.ts +115 -0
  123. package/src/hub/native-hook-launch.ts +12 -3
  124. package/src/hub/operational-view.ts +2 -2
  125. package/src/hub/peers.ts +27 -0
  126. package/src/hub/permission-mode.ts +1 -1
  127. package/src/hub/person-figures.ts +88 -0
  128. package/src/hub/report.ts +38 -4
  129. package/src/hub/research-dataset.ts +158 -0
  130. package/src/hub/research-memory.ts +68 -0
  131. package/src/hub/research-quality.ts +118 -0
  132. package/src/hub/research-record.ts +279 -0
  133. package/src/hub/research.ts +126 -27
  134. package/src/hub/restart.ts +14 -0
  135. package/src/hub/settings.ts +3 -0
  136. package/src/hub/startup-event-seeds.ts +51 -0
  137. package/src/hub/startup-readiness.ts +65 -0
  138. package/src/hub/startup-timing.ts +38 -0
  139. package/src/hub/tasks.ts +29 -9
  140. package/src/hub/ui.ts +9 -5
  141. package/src/hub/visual-assets.ts +50 -0
  142. package/src/hub/visual.ts +257 -0
  143. package/src/memory/brief.ts +169 -14
  144. package/src/memory/capture.ts +2 -1
  145. package/src/memory/recall.ts +46 -5
  146. package/src/memory/record-provenance.ts +82 -0
  147. package/src/memory/records.ts +116 -0
  148. package/src/models/route/adec.ts +190 -0
  149. package/src/models/route/config.ts +37 -3
  150. package/src/models/route/decision-evidence.ts +83 -0
  151. package/src/models/route/decision.ts +143 -0
  152. package/src/models/route/runtime.ts +13 -6
  153. package/src/ui/index.html +134 -6
  154. package/src/ui/plugin.ts +65 -4
  155. package/templates/AGENTS.block.md +1 -1
  156. package/templates/config.json +1 -1
  157. package/templates/profiles/general.json +1 -1
  158. package/templates/profiles/research.json +1 -1
  159. package/templates/profiles/writing.json +1 -1
@@ -1,6 +1,6 @@
1
1
  # Operations guide
2
2
 
3
- This guide describes ahub 0.34.0 and control protocols 16 through 17. Live verification
3
+ This guide describes ahub 0.35.0 and control protocols 16 through 17. Live verification
4
4
  results and remaining prerequisites are recorded separately in [the smoke ledger](smoke.md).
5
5
 
6
6
  ## Agent-operated live verification
@@ -1040,8 +1040,8 @@ plugin stale when the module changed after setup, so run `ahub setup` again afte
1040
1040
 
1041
1041
  ## Install and start
1042
1042
 
1043
- Use Bun 1.4.2 or newer. Source checks and CI use the version pinned in
1044
- `.bun-version` (currently 1.4.2). Install the released package and install its Claude
1043
+ Use Bun <!-- facts:bun-floor -->1.4.2<!-- /facts:bun-floor --> or newer. Source checks and CI use the version pinned in
1044
+ `.bun-version` (currently <!-- facts:bun-pinned -->1.4.2<!-- /facts:bun-pinned -->). Install the released package and install its Claude
1045
1045
  channel plugin:
1046
1046
 
1047
1047
  ```bash
@@ -1055,6 +1055,20 @@ ahub up
1055
1055
  `ahub init` writes the project configuration and managed instruction blocks.
1056
1056
  Run it after an upgrade when those blocks need refreshing.
1057
1057
 
1058
+ `ahub up` waits for authenticated readiness. After 12 seconds, a verified live
1059
+ child that is still pending produces a `still starting, pid N` message on stderr;
1060
+ the CLI keeps waiting and confirms the retained runtime when it becomes ready.
1061
+ At 120 seconds it reports unconfirmed readiness and exits unsuccessfully, leaving
1062
+ the daemon running. Run `ahub status` and inspect `hub.log` before another start.
1063
+ An owned child observed to exit before readiness produces an error; a cancelled
1064
+ operation cannot confirm startup.
1065
+
1066
+ Each completed daemon startup writes one `startup timings ms:` line to `hub.log`,
1067
+ with fixed phase names and durations. The three event-seed consumers share one
1068
+ lossless streaming pass; abandoned snapshot bodies are not read for startup.
1069
+ The [startup receipt](verification/2026-11-07-daemon-startup-1232.md) separates
1070
+ seed I/O from bounded usage-rollup I/O and records the isolated measurements.
1071
+
1058
1072
  Settings flags (#1100) set the main project settings without a file edit.
1059
1073
  `--agents <a,b,...|none>` (`start.agents`) and `--autonomy inherit|off` (whether a
1060
1074
  new project inherits the machine autonomy preset) go to the untracked
@@ -1655,6 +1669,7 @@ token counts, never message bodies or task titles.
1655
1669
  ahub report --since 7d # turns, busy time and tokens per peer, messages, overlaps, task events
1656
1670
  ahub report --since 7d --json # the same numbers as JSON
1657
1671
  ahub report --by task # tokens, turns and wall time per task and class, with the unattributed share
1672
+ # and, from #1278, what the person was asked per task (permissions, questions, completions, CLI refusals, minutes from recorded latencies only), with the unattributed remainder apart
1658
1673
  ahub export --since 24h # the raw events as JSON lines, for your own analysis
1659
1674
  ahub report --dogfood [--since 7d|<iso>] [--json] # read-only scorecard: flow, quality, cost, delivery, performance, adoption
1660
1675
  ```
@@ -1716,13 +1731,15 @@ redistributed (rules: [events](events.md#per-task-usage-reports)).
1716
1731
  ## Research records
1717
1732
 
1718
1733
  With `"research": { "enabled": true }` in `.agenthub/config.json` (off by default), the hub keeps one record per
1719
- approved task in `~/.agenthub/research/`, shared by every project that opts in: outcome, review rounds, rework, failed
1720
- checks, tokens and time per peer and model, never task or message text ([schema](research.md)).
1734
+ task that reaches a terminal state (approved or cancelled) in `~/.agenthub/research/`, shared by every project that opts in:
1735
+ outcome, review rounds and findings, delivery and merge figures, rework, failed checks, tokens and time per peer and model,
1736
+ never task or message text ([schema](research.md)). Unknown values are null and counted, never zero.
1721
1737
 
1722
1738
  ```bash
1723
1739
  ahub research --since 30d # success, first-pass, rework and check-failure rates; tokens and wall time per task
1724
1740
  ahub research --all --json # every opted-in project on this machine
1725
1741
  ahub research export --format csv # the records, for your own analysis
1742
+ ahub research export --dataset ~/research-out [--all] # per-project tasks.jsonl, manifest.json, quality.json: family names only, to a directory in your home outside every project
1726
1743
  ahub research backfill # build this project's records once from events.jsonl
1727
1744
  ahub task label 12 reverted # a later verdict (ok, regressed, reverted, incomplete, wrong, abandoned)
1728
1745
  ```
@@ -1824,7 +1841,7 @@ ahub memory forget 1a2b3c4d # a person's; also promote, demote, sup
1824
1841
 
1825
1842
  `"memory": { "native": false }` in `.agenthub/config.json` turns it off for a project.
1826
1843
 
1827
- The exact-pinned `@staix/agent-mem` 0.22.1 resolves `AMEM_HOME`, then `$AGENTHUB_HOME/memory`, then `~/.agenthub/memory`. An absent `M/profile.json` selects shared v1; an explicit generation selects `M/generations/G` with the package's readonly legacy bridge. All daemon memory consumers and the hub memory/goal CLI use that selected profile. Invalid metadata or a missing selected generation makes native memory unavailable while the bus and board continue; there is no fallback to legacy writes. Inspect the daemon's memory binding/availability before interpreting missing memory as an empty store.
1844
+ The exact-pinned `@staix/agent-mem` 0.23.1 resolves `AMEM_HOME`, then `$AGENTHUB_HOME/memory`, then `~/.agenthub/memory`. An absent `M/profile.json` selects shared v1; an explicit generation selects `M/generations/G` with the package's readonly legacy bridge. All daemon memory consumers and the hub memory/goal CLI use that selected profile. Invalid metadata or a missing selected generation makes native memory unavailable while the bus and board continue; there is no fallback to legacy writes. Inspect the daemon's memory binding/availability before interpreting missing memory as an empty store.
1828
1845
 
1829
1846
  The daemon checks its selector at hub operation entry and before candidate/counter writes. It does not hot-switch handles or provide an atomic fence against selection changes during an in-flight core call. Quiesce consumers and close their handles before migration or selection changes; reopen through the supported session-preserving lifecycle afterward. Candidate/counter storage remains `M/candidates.db`, independent of `G`; since agent-mem 0.7.0 `amem review` also lists and accepts candidates on a selected generation. Every running hub using this home needs an authenticated receipt: a matching ready binding, or a generation-capable unbound receipt (`disabled` when native memory is off, `unavailable` when opening failed) proving it holds no memory handle. An unbound daemon and zero running hubs are quiesced for memory; a stale bound handle, unreadable registry or unknown runtime is not ready. Existing JSON memory/goal results remain unchanged; the additive binding receipt belongs to daemon status, profile-check and the decay object. This source integration does not authorize installing a new runtime, migrating real memory, restarting sessions or claiming real-machine sync/capture receipts. Migration/deactivation and their limits belong to [agent-mem](https://github.com/STAIxBWLB/agent-mem); see [the hub profile contract](memory.md#selected-memory-profile).
1830
1847
 
@@ -2442,6 +2459,73 @@ roster setting do not change running participation: they take effect at the next
2442
2459
  hub start. The tracked config can provide defaults and config.local.json can
2443
2460
  override them.
2444
2461
 
2462
+ ## Live peer model overrides (#1317)
2463
+
2464
+ From a plain person terminal, select the next model of a supported peer without
2465
+ restarting the peer or reloading project configuration:
2466
+
2467
+ ```bash
2468
+ ahub model set claude-impl claude-sonnet-5-5
2469
+ ahub model set codex gpt-5.1-codex
2470
+ ahub model clear claude-impl
2471
+ ahub model list
2472
+ ahub model list --json
2473
+ ```
2474
+
2475
+ Use a model id available through the peer's existing provider.
2476
+ Model ids are one bounded token: letters, digits and `._:/@+-`, at most 128 bytes,
2477
+ no spaces and no leading dash. Agent shells and every hub-tool caller are
2478
+ refused, including list; a conductor role does not grant person authority.
2479
+ The dashboard Peers action uses a person settings session and preview/confirm,
2480
+ not an agent command or an ordinary dashboard permission escalation.
2481
+
2482
+ An override is desired runtime state in `hub.db`. It survives daemon restart;
2483
+ `ahub reset` clears it with runtime state. Precedence is override, instance
2484
+ model, launch model, then native default where that surface supports them.
2485
+ Clear restores the existing lower-priority selection. For Codex app-server,
2486
+ an explicit client model always wins over hub injection. A `turn/start` model
2487
+ becomes the native thread default, so clear may retain that current default;
2488
+ it does not recover an earlier model. Raw/pull-only native TUI attachments remain
2489
+ unsupported. A retained former override is labelled `native thread default (was override)`;
2490
+ a rejected start leaves the next start free to sample a corrected selection.
2491
+ The model and its source appear in `ahub status` and JSON output. An unknown native default remains
2492
+ unknown. `pending next turn` means the desired value has not been acknowledged
2493
+ at an eligible boundary; it does not claim the current turn changed models.
2494
+
2495
+ Changing a desired value during a turn leaves that turn unchanged. Steering and
2496
+ local tool rounds keep the captured selection. If the person changes it twice
2497
+ before the next turn, only the last desired value is selected there. A stale
2498
+ callback cannot mark a newer desired value or another adapter owner applied.
2499
+
2500
+ T1 supports headless Claude implementer turns, supported Codex app-server turns,
2501
+ fresh codex-review spawns, the local worker and Pi. Native account/provider
2502
+ availability is still the backend's responsibility. Headless Kimi Code CLI 2.5.2
2503
+ also supports next-turn selection through its receipt-backed ACP model setter.
2504
+ It requires the existing native identity admission, an exact-version receipt,
2505
+ an owned current session and advertised model choices. Clear restores the
2506
+ validated session baseline. Kimi reviewer isolation and version qualification
2507
+ are separate and unchanged.
2508
+
2509
+ Jcode, Qwen, other Kimi versions and native TUI forms remain `unsupported live`.
2510
+ The [Jcode headless fixture](verification/2026-11-08-jcode-0991-model-setter-1317.md)
2511
+ passes, but does not fence a shared TUI turn across native failover. The
2512
+ [Qwen 0.26.9 setter fixture](verification/2026-11-08-qwen-0269-model-setter-1317.md)
2513
+ fails restoration of the original model. Neither enables a production setter.
2514
+ A refused set writes no new override and never restarts a process. Follow the
2515
+ reported cause and `ahub restart <peer>` guidance for unsupported forms.
2516
+
2517
+ This does not make `instances.*.model` or `*_cmd` live or editable by agents.
2518
+ Their existing machine-local tier 3 confirmation and restart behavior remains.
2519
+ The plural `ahub models` still manages the existing local model service; it is
2520
+ separate from the singular per-peer `ahub model` command. `model_change` records
2521
+ ids and the next-turn boundary, never prompts or credentials.
2522
+
2523
+ Model display is bounded to 512 local identities. If legacy sessions exceed the
2524
+ view budget, list refuses explicitly and status/dashboard mark model data
2525
+ unavailable; they never call a truncated list complete. Close unused native
2526
+ sessions, clear obsolete overrides and follow the restart guidance. Single-peer
2527
+ set/clear remains usable and no process is restarted automatically.
2528
+
2445
2529
  ## Settings from the dashboard and the terminal
2446
2530
 
2447
2531
  In `ahub console`, press `6` for Settings. Existing operational-tab arrow cycling
@@ -2990,6 +3074,14 @@ Read quota and approve tools
2990
3074
  in Kimi's own TUI; a person can pause the hub queue by hand. End the native terminal
2991
3075
  session there, not with `ahub stop kimi`.
2992
3076
 
3077
+ On Kimi 2.5.2 (compat entry `kimi-tui`), the launcher also installs a launch-scoped
3078
+ plugin under `plugins/managed/agent-hub` in the Kimi home (`KIMI_CODE_HOME`, default
3079
+ `~/.kimi-code`) and removes it when the TUI exits (#1282). Its hooks let a turn you
3080
+ started there settle into a result (`ahub results`), always private, once the native
3081
+ transcript shows the turn ended and no native background task is running or unknown. This adds no push,
3082
+ steer, watchdog or quota handoff. Another Kimi version, or a plugin setup that
3083
+ fails, prints the cause and launches the plain TUI without completion receipts.
3084
+
2993
3085
  Automatic routing never picks a pull-only owner or reviewer, even as the only
2994
3086
  candidate. A proposer can name the owner or claim its own task; a person or the
2995
3087
  conductor can name an owner or reviewer with
@@ -3099,6 +3191,8 @@ Unread quota resume notices are coalesced without losing their public task moves
3099
3191
 
3100
3192
  A refused prompt with no acceptance or activity is requeued without a review hold. At the estimated reset it is tried again; another quota refusal pauses it again without consuming the poison-envelope retry count. Work that already started remains held for review even while its open tasks move to other peers. After the quota reset its queue stays held: inspect `ahub queue show <delivery-id>` and the associated task, then choose `ahub queue resolve <delivery-id> --action completed|retry|discard --reason "<text>"`. Do not retry work that another owner already took over without inspecting it. If the hub cannot confirm the pause, the refusal is also held instead of spending retries. Other errors keep their ordinary review behavior. Only the observed Kimi 5-hour text is recognised; weekly/monthly limits and other ACP vendors, including Qwen, remain unverified. The recognised text and its provenance live in `src/adapters/acp-quota.ts`.
3101
3193
 
3194
+ The gate is `budget.gate`, 0.99 by default (#1292; 0.9 before). A peer whose fresh quota window reaches the gate is asked to checkpoint, then paused and its work handed over. It resumes when every fresh window is under the gate minus 0.1, or at the reset. A release cut is also held while any window has no more headroom than 1 minus the gate. At 0.99 a soft pause leaves about 1% of the window for the checkpoint turn; a provider's hard refusal still pauses at once. `budget.gate` is a registered setting (a number from 0 to 1): set it with `ahub settings set budget.gate <value>` in a terminal or in an `ahub ui --settings` session. An ordinary dashboard session and the conductor cannot set it. The value goes to `config.local.json`; the running hub uses it from the next quota reading, with no restart. A project created before #1292 has `"gate": 0.9` in its tracked `.agenthub/config.json` (the init template wrote it), and that value overrides the default: remove the key there to follow the default, or set the machine-local value.
3195
+
3102
3196
  Quota also shapes routing and handoffs:
3103
3197
 
3104
3198
  - Among peers a task could go to, those with quota readings are ordered by
@@ -3549,7 +3643,119 @@ The data-only [manifest](../scripts/compat-receipts/manifest.json) and [per-leg
3549
3643
 
3550
3644
  Optional model/TUI behavior explicitly left unverified by a model-free receipt is not a new admission leg. `codex-review` requires its separately authorized model-backed tool-call proof. `agy` has no public install source. `qwen-dual` requires real-model and TUI work and its current receipt does not name the OS. The manifest records acquisition metadata separately from historical native-binary evidence; no CLI installation or COMPAT change is made by this audit.
3551
3645
 
3552
- For CI-opened PRs, the person still chooses between explicit `check.yml` dispatch with GITHUB_TOKEN and a GitHub App installation token. Dispatch avoids an additional App secret but needs Actions write permission in the publisher and exact-head evidence; it does not run the separate PR-only closing-refs/routing-receipt workflows. An App token lets normal PR events start checks, with person-owned scoped credentials confined to the publisher. Neither route is enabled here. Current [GitHub documentation](https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/trigger-a-workflow) also describes approval-required PR runs for GITHUB_TOKEN automation; that is not unattended CI. See the audit for permissions, queue and gate trade-offs.
3646
+ For CI-opened PRs, the person selected `GITHUB_TOKEN` with explicit `check.yml` dispatch on `compat/<id>-<version>` (2026-11-07). The publisher needs `actions: write` for dispatch in addition to its branch, PR and issue permissions. No GitHub App credentials are used. Dispatch supplies exact-head check evidence; it does not trigger the separate PR-only closing-refs/routing-receipt workflows. The T0 audit retains the alternative App-token trade-offs as history. Current [GitHub documentation](https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/trigger-a-workflow) also describes approval-required PR runs for GITHUB_TOKEN automation; that is not unattended CI. See the audit for permissions, queue and gate trade-offs.
3647
+
3648
+ ### Automated receipt workflow (#1248 T2)
3649
+
3650
+ After merge, `agent-compat.yml` runs daily and can be dispatched with `dry: true`
3651
+ (the manual default). The watcher emits validated ahead/version and verified-ID
3652
+ JSON directly; matrix construction never parses report prose. At most eight IDs
3653
+ are considered, with two hosted jobs at once. An unspecified manifest OS stays
3654
+ not-run rather than receiving an invented native-platform proof.
3655
+
3656
+ The candidate matrix has only `contents: read`, no supplied secrets, checkout
3657
+ credentials disabled and a 20-minute job bound. It runs the disposable harness and
3658
+ uploads JSON/Markdown even for failed or not-run legs. A separate trusted read-only
3659
+ job validates the summaries and builds the embedded COMPAT bundle from repository
3660
+ source. It never imports candidate JS or Markdown. The publisher downloads that
3661
+ job's immutable artifact ID and validates descriptors, byte hashes, identities,
3662
+ complete leg sets and trusted manifest policy again.
3663
+
3664
+ The candidate CLI and receipt writer share a job and OS user. A malicious release
3665
+ could overwrite its own `summary.json` before upload with a well-formed eligible
3666
+ forgery; preparation cannot detect that forgery from the data alone. An eligible
3667
+ receipt is a compatibility signal, not a security attestation of the release.
3668
+ The person's PR and release review is the real admission gate; the automation
3669
+ never merges its verification PR.
3670
+
3671
+ Only the publisher has contents/PR/issues write and Actions dispatch permission.
3672
+ It executes first-party Node code and `gh` REST calls, with no package install,
3673
+ build or candidate execution. All required legs must pass and trusted `autoAdmit`
3674
+ must allow the ID; artifact `eligible` alone is not evidence. It changes only the
3675
+ exact COMPAT row, its receipt and prepared plugin assets on `compat/<id>-<version>`.
3676
+ Existing bot branches are checked for manual drift; unknown collisions refuse,
3677
+ updates append without force, and verification PRs are never auto-merged. A newer
3678
+ same-ID PR supersedes an older one only after the replacement and its check
3679
+ dispatch succeed.
3680
+
3681
+ Other results update closed-cause receipt blocks in the exact-title drift issue.
3682
+ Confirmed verified IDs remove their stale blocks, while still-drifting or unknown
3683
+ IDs retain their evidence. Report text never supplies those identities. Dry mode
3684
+ still performs isolated proof and read-only build/artifact work, then prints the
3685
+ planned GitHub changes without creating/updating branches, PRs, labels or issues.
3686
+ It is not native compatibility qualification on the person's Mac.
3687
+
3688
+ See the [T2 verification report](verification/2026-11-07-compat-workflow-1248.md)
3689
+ for the data/credential boundary, fixtures and final committed-head evidence.
3690
+
3691
+ ### Isolated receipt harness (#1248 T1)
3692
+
3693
+ Run `bun scripts/compat-receipt.ts <id> <exact-version> --out <new-owned-directory> --disposable-runner`
3694
+ from the checkout on a GitHub-hosted runner or disposable Linux container. The
3695
+ explicit flag is required; a local personal Mac is refused even with a temporary
3696
+ HOME. The runner VM/container must be destroyed after the job. The command writes `summary.json` and `receipt.md` without
3697
+ changing COMPAT, trust on this machine, or any workflow. Its exit codes are 0 for
3698
+ a completed pass, 1 for failure, 3 for required work left not-run, and 2 for invalid
3699
+ input/output. `eligible` also requires the manifest's admission policy; a pass
3700
+ alone never overrides the Codex/agy policy or a required model/TUI leg.
3701
+
3702
+ The installer binds exact npm metadata and SRI before installing the verified
3703
+ local tarball into its private prefix, with empty user/global npm configs and a
3704
+ private cache. Release assets must match their tagged SHA-256 digest and checksum
3705
+ before bounded extraction; absolute/traversal/link/nonregular archive entries and
3706
+ size/count excesses refuse. No global installer or caller credential environment
3707
+ is used. Normal npm lifecycle behavior is not silently disabled; this does not
3708
+ certify an unreviewed lifecycle proof. Isolation uses fresh HOME/XDG/tool homes and
3709
+ an allowlisted environment, not a claim of kernel confinement for every installer.
3710
+
3711
+ Proof APIs load in a separate worker after that environment is established. The
3712
+ Codex backend's real production preparation is reused, including empty and
3713
+ ambient fixtures; its returned model `exec` command is never started. Kimi private
3714
+ configuration, exact host inventory and broker refusals are separate from native
3715
+ tool dispatch. Committed Claude transport/wrapper producers are imported through
3716
+ `runpy`, with optional exact-version/runtime-path inputs; their historical defaults
3717
+ and behavior assertions remain intact. No prepare evidence-description string is
3718
+ used as an execution-time proof.
3719
+
3720
+ | Proof family | T1 can run | Explicit not-run work |
3721
+ | --- | --- | --- |
3722
+ | Codex reviewer | Committed production prepare, actual metadata/native-canary checks and launch configuration assertions | Model-backed hub call, separate control artifact and human source review |
3723
+ | Kimi reviewer | Committed prepare/inventory/broker plus promoted native two-tool loop, Write positive/negative controls, preload guard and producer group cleanup | Human source review; unsupported native platform |
3724
+ | Claude implementer | Committed Write/Bash/rewrite and host-wrapper producer groups | Human upstream-impact review; unsupported launcher forms |
3725
+ | Pi | Artifact acquisition only in this core | Historical top-level producer needs import-safe/candidate-version extraction |
3726
+ | Qwen ACP | Promoted ACP identity/session/mapped-mode and actual published permission-handler producers, preload controls and group cleanup | Human lifecycle/source review; Linux does not replace required macOS arm64 |
3727
+ | Jcode | Reconstructed isolated serve/ACP/MCP, fixed-stream steering, telemetry and owned-group checks | Human source comparison; native macOS qualification remains unverified. The 0.99.1 Linux control is not qualified: telemetry identity and strict persisted-context binding fail; see the [reconstruction receipt](verification/2026-11-07-jcode-receipt-reconstruction-1248.md) |
3728
+ | agy and qwen-dual | Policy and unavailable/manual-leg reporting | No public agy installer; account/model/TUI or unspecified-platform legs |
3729
+
3730
+ Every manifest leg appears as pass, fail or not-run with a cause and next step.
3731
+ Missing producers are not inferred from configuration or other host tests. Partial
3732
+ receipts cannot qualify a candidate while required work is missing. Source/lifecycle
3733
+ semantic review is not a fetched-diff pass. Synthetic loopback fixture sentinels are
3734
+ not real provider API keys; parent account/model variables never reach a worker.
3735
+
3736
+ Default execution deadlines are 15 minutes overall, 3 minutes for installation
3737
+ and 2 minutes per proof-worker/group. Owned-process teardown has a separately
3738
+ bounded 10-second allowance, included in the JSON wall-time bounds. Output verification/publication has a
3739
+ separate 16-second allowance included in the total; elapsed time is sampled before
3740
+ publication. The summary
3741
+ separates `ownedStopConfirmed` from `processCleanup: proven|unproven` and gives its
3742
+ reason. An empty leader process group cannot prove lifetime cleanup: a helper can
3743
+ detach into a new group with ignored pipes and survive its launcher. Detected
3744
+ survivors and an unconfirmed owned stop fail the run. Producer cleanup legs assert
3745
+ that the native group they started stopped, using their original receipt evidence;
3746
+ they retain that pass or fail independently of harness lifetime cleanup. Native
3747
+ lifetime cleanup remains unproven in T1 and is reported without gating eligibility.
3748
+ A passing receipt with eligible policy and confirmed owned stop can be eligible
3749
+ while lifetime cleanup is unproven, provided no survivor was detected. The private
3750
+ workspace is kept until runner destruction; its location is reported only on stderr. A portable
3751
+ process supervisor belongs to a separate follow-up. Disposable runner lifetime is
3752
+ the containment boundary, not fresh HOME or process-group emptiness.
3753
+
3754
+ The output directory is held open before installation. Exclusive, no-follow
3755
+ writes are relative to that handle, so replacing an ancestor cannot redirect the
3756
+ receipt. The receipt excludes raw native stdout/stderr and private prefix paths.
3757
+ No PR is created by T1. The [T1b producer report](verification/2026-11-07-compat-receipt-producers-1248.md) maps the promoted Kimi and Qwen modules to their original receipts. Publisher integration is T2. The [Jcode reconstruction](verification/2026-11-07-jcode-receipt-reconstruction-1248.md) retains its failed native control legs and unverified Mac qualification.
3758
+
3553
3759
 
3554
3760
  ## Upgrade and crash recovery
3555
3761
 
@@ -3625,7 +3831,7 @@ or the plugin is older than the daemon it installed (#1179). It prints nothing w
3625
3831
  | Runtime | Control protocol window | Legacy status field |
3626
3832
  | --- | --- | --- |
3627
3833
  | 0.12.20 through 0.25.0 | 16 only | `protocol: 16` |
3628
- | 0.26.0 through 0.34.0 | 16 through 17 | `protocol: 16`, `protocolMin: 16`, `protocolMax: 17` |
3834
+ | 0.26.0 through 0.35.0 | 16 through 17 | `protocol: 16`, `protocolMin: 16`, `protocolMax: 17` |
3629
3835
 
3630
3836
  New clients choose the highest common protocol. The protocol window remains 16
3631
3837
  through 17, so the published protocol-16 client and channel plugin remain usable; unrelated feature
@@ -3633,7 +3839,10 @@ capabilities still require their own labels. A disjoint window refuses with 4426
3633
3839
  The compatibility-floor `protocol` field remains readable by old clients.
3634
3840
 
3635
3841
  Release N-1 is counted in minor versions, separately from this protocol window
3636
- (#1164, family contract entelecheia/ai-workspace#70). For 0.34.x, N-1 is 0.33.0,
3842
+ (#1164, family contract entelecheia/ai-workspace#70). For 0.35.x, N-1 is 0.34.1,
3843
+ the latest published patch of 0.34.x (exact `@staix/agent-mem` 0.22.1, control
3844
+ protocol 17 with descriptor floor 16). Every patch on 0.35.x keeps that release
3845
+ baseline. For 0.34.x, N-1 is 0.33.0,
3637
3846
  the latest published patch of 0.33.x (exact `@staix/agent-mem` 0.22.1, control
3638
3847
  protocol 17 with descriptor floor 16). Every patch on 0.34.x keeps that release
3639
3848
  baseline. For 0.33.x, N-1 is 0.32.0,
@@ -4395,6 +4604,97 @@ and doctor. Every route without the `hub/` prefix, including named legacy routes
4395
4604
  and `sy/` selections, uses the fixed alias with one warning. Loading or reloading
4396
4605
  never rewrites the operator's routing files.
4397
4606
 
4607
+ #### Optional adec decision route (#554)
4608
+
4609
+ The optional `decision` route calls a separately managed, loopback-only
4610
+ `adec serve` using the [agent-decide route contract](https://github.com/STAIxBWLB/agent-decide/issues/4).
4611
+ The hub never starts or configures that service, reads its credentials, or calls
4612
+ its model directly. This backend is selected explicitly:
4613
+
4614
+ ```toml
4615
+ [hub_routes."hub/decision"]
4616
+ type = "decision"
4617
+ decision_backend = "adec"
4618
+ decision_mode = "shadow"
4619
+ policy_id = "ahub.chat"
4620
+ adec_endpoint = "http://127.0.0.1:8740/v1/route"
4621
+ decision_timeout_ms = 8000
4622
+ efficient = "fast"
4623
+ capable = "coding"
4624
+ candidates = [
4625
+ { alias = "fast", efforts = ["low", "medium"] },
4626
+ { alias = "coding", efforts = ["high"] },
4627
+ ]
4628
+ ```
4629
+
4630
+ Select it by editing the existing `[local] route` or class `route` to
4631
+ `"hub/decision"`. Do not append a duplicate `[local]` table. Ordinary routes,
4632
+ class `fixed_model` selections and a person's explicit model override retain
4633
+ their existing behavior; an explicit model bypasses this route.
4634
+
4635
+ Omitting `decision_mode` means `off`: no decision request or evidence is built.
4636
+ This delivery supports `shadow` only. It records the proposed alias/effort,
4637
+ margin, latency and decider while keeping the exact existing stage heuristic
4638
+ model, effort and conversation. `enforce` is explicitly refused at config load:
4639
+ `decision_mode enforce is not available yet; use shadow; tracked in #554`.
4640
+ There is no dormant application path or gateway effort plumbing. No route is
4641
+ enabled by default or deployed by this feature.
4642
+
4643
+ The configured alias/effort list has at most 26 pairs. Aliases narrow the adec
4644
+ policy; efforts are an additional hub-side response allowlist and are not sent
4645
+ as a policy override. Align the lists with the service policy: an unconfigured
4646
+ effort is refused as `outside_options`, keeping the heuristic. The request
4647
+ cannot add a policy candidate. The service assembles its question and
4648
+ owns its hard rules, margin threshold, in-memory pins and fallback. A valid
4649
+ `fallbackApplied` pair is recorded as the service's proposed fallback even when
4650
+ `abstain` is true. Every proposal, failure and `no_candidate` keeps the hub
4651
+ heuristic unchanged. An HTTP 400 is a caller/configuration
4652
+ error; inspect the policy id and allowed pairs. Transport errors, timeout,
4653
+ malformed responses and an out-of-list pair also keep the hub heuristic, with a
4654
+ closed reason in the route event. A failed service is not asked again in the
4655
+ same user turn. Operational diagnostics contain a closed cause and repair step,
4656
+ and are emitted once per state/configuration change; a successful reply resets
4657
+ the diagnostic so a later recurrence is visible. Per-call route records remain
4658
+ available for outcome correlation. The default total timeout is 8 seconds (configurable from 1 to
4659
+ 30000 ms), covering headers and the full bounded response. Redirects, ambient
4660
+ proxies, credentials and browser headers are not used. Endpoint URLs accept only
4661
+ HTTP numeric loopback with `/v1/route`, without userinfo, query or fragment.
4662
+
4663
+ Each eligible tool round carries the same opaque, route/session/user-turn-bound
4664
+ `turnId`. adec therefore reuses its pin while rechecking current hard rules.
4665
+ The hub does not send a proposal as `current`: no pair is applied in shadow,
4666
+ and the native/default effort is unknown. Explicit model selections bypass the
4667
+ service.
4668
+ Changing a service, provider, model, version or policy threshold requires a new
4669
+ shadow period. The hub does not recalibrate a remote policy. The optional decider `version` is recorded when configured on the service;
4670
+ otherwise it stays unknown and is not inferred from the model id. Moving
4671
+ `latest` version aliases are refused.
4672
+
4673
+ Evidence is a bounded data projection, never the forwarded conversation: a
4674
+ capped latest-user task fragment, the last assistant/tool tails, screened tool
4675
+ names and numeric stage dimensions. Whole selected sources pass the current
4676
+ public-view pattern/private-path/secret checks before UTF-8/JSON byte caps.
4677
+ Tool arguments, system/developer instructions and older history are not sent.
4678
+ The total evidence cap is 8 KiB; candidate and response payloads have finite
4679
+ bounds too. Evidence is labelled untrusted data, not instructions; injection
4680
+ resistance is not claimed. Review-observer-excluded and remote turns skip adec.
4681
+ A PII/private turn needs positive `onCampus()` before any evidence build or
4682
+ service call. Even on campus, its evidence is dimensions only, its context size
4683
+ is redacted to 0, and its separate `pii`/`private` flags reach adec so the service
4684
+ can enforce on-prem candidates/decider rules. The ordinary gateway campus and
4685
+ execution-budget checks still apply. Current board/turn privacy and observer
4686
+ facts are checked again across admission, campus and service awaits. A changed
4687
+ or unavailable authority refuses the stale turn instead of turning into a
4688
+ service-error fallback; inspect current task policy and retry. A returned proposal is checked again before telemetry. The original gateway
4689
+ execution path and its privacy/budget checks are unchanged. Source text, tool arguments, credentials
4690
+ and server error prose never enter route events.
4691
+
4692
+ This delivery is the adec consumer slice of #554. The direct System One backend,
4693
+ enforcement application, optional stage classifier and manual offline triage
4694
+ evaluation remain separate unimplemented legs. Offline fake-service tests establish protocol and shadow
4695
+ behavior; no routing gain, native service/model run or production rollout is
4696
+ claimed.
4697
+
4398
4698
  Migrate the retired settings as follows:
4399
4699
 
4400
4700
  | Retired setting or route | In-process replacement |
@@ -4574,18 +4874,18 @@ durable-delivery retries remain the journal's responsibility.
4574
4874
 
4575
4875
  ## Documentation source verification
4576
4876
 
4577
- `docs/verified.json` maps README, the current security, operations and quickstart
4578
- pages, and every agent note to a full source commit and explicit covered paths.
4877
+ `docs/verified.json` maps README, every page directly under `docs/` and
4878
+ `docs/addons/`, and every agent note to a full source commit and explicit covered paths.
4579
4879
  A stamp records a human check of the cited paths, symbols, numeric limits and
4580
4880
  operational commands. It does not certify live deployment or prove prose
4581
4881
  correctness automatically. Specs, changelogs and the smoke ledger retain their
4582
- own dated evidence and are outside this manifest.
4882
+ own dated evidence and are outside this manifest; so does the retired GSD analysis.
4583
4883
 
4584
4884
  `node scripts/check-docs.mjs` also runs in `scripts/check.sh`. Missing manifest
4585
4885
  coverage, unresolved or nonancestor commits, removed source paths and a README
4586
4886
  status version different from `package.json` fail the gate. Source commits since
4587
4887
  a stamp and uncommitted covered changes produce sorted stale notices without
4588
- failing it. Counts are commits touching any covered path, rather than file or
4888
+ failing it; CI's `decision` job repeats them as warnings on every pull request. Counts are commits touching any covered path, rather than file or
4589
4889
  line counts; an unrelated commit leaves the document fresh. Git history must
4590
4890
  include the stamped ancestors (CI checks out full history).
4591
4891
 
@@ -4653,10 +4953,11 @@ makes `scripts/check.sh` run slice `i` of the full suite through `bun test --sha
4653
4953
  every test file in exactly one shard and balances them by the recorded per-file times; shard 1 also runs the docs check, typecheck, role check, bundle freshness and
4654
4954
  package contents, so each of those runs once per gate, and the others install and test. Each shard runs under the same 1500 s backstop as Linux inside a 25-minute job
4655
4955
  limit, and its last line is `check: OK (shard i/n)`. The required check `check (macos-latest)` is an aggregator job that passes only when every shard passed (or an
4656
- identical-tree full proof was reused), so branch protection and the reuse proof read the same names as before. `AHUB_CHECK_SHARD` is the full suite only: it cannot be
4956
+ identical-tree full proof was reused), so branch protection and the reuse proof read the same names as before. A stacked pull request (base not `main`) runs no macOS shard, and its `check (macos-latest)` fails with "macOS shards deferred", naming the base (#1307): GitHub allows 5 macOS jobs at once on this account, and such a PR is rebased and rerun once it targets `main`. A PR retargeted to `main` without a new commit gets its macOS run by a push, or by `gh pr close <n> && gh pr reopen <n>`; `gh run rerun` replays the old base and defers again. `AHUB_CHECK_SHARD` is the full suite only: it cannot be
4657
4957
  combined with `--changed`. A shard execs the system git once before its tests (`AHUB_CHECK_WARM_GIT`, default `/usr/bin/git`; a failure of it is ignored), because the first execs of `/usr/bin/git` on a fresh hosted macOS runner outlast the 1000 ms some tests give it. `scripts/test-timings.json` is advice for balance, never a list of tests: a file missing from it still runs (Bun gives it the median time).
4658
4958
  Regenerate it from a green macOS leg when the shards drift apart: `gh api --allow-escape-sequences repos/<owner>/<repo>/actions/jobs/<macOS shard job id>/logs`
4659
- for each shard, or from one unsharded leg's log, then `bun scripts/make-test-timings.ts <log>`.
4959
+ for each shard, or from one unsharded leg's log, then `bun scripts/make-test-timings.ts <log>...`, or `bun scripts/refresh-test-timings.ts` for the newest green run (#1257).
4960
+ A red CI job gets one rerun of its failed jobs on the same head, then an investigation; the weekly flake ledger issue and the rerun policy are in `docs/agent-notes/tests.md` (#1257).
4660
4961
  The `closing-refs` workflow fails a PR whose title, body or commit messages would make
4661
4962
  GitHub close an issue the text only mentions (`Refs #N`, a negated or qualified keyword);
4662
4963
  an issue linked only in the sidebar passes (#760).
@@ -5001,11 +5302,11 @@ Native policy is unmanaged and has NO hub sandbox; no permission_modes.jcode or
5001
5302
 
5002
5303
  ## Optional Antigravity (agy) peer
5003
5304
 
5004
- Antigravity is optional and off roster by default. Use `ahub roster add agy`, then run `ahub agy` in a terminal: it runs the Antigravity CLI as a fresh pull-only TUI in that terminal. It accepts the 1.4.x line: 1.4.2, 1.4.7 and 1.4.12 are the verified versions ([1.4.7 receipt](verification/2026-10-26-agy-1.4.7.md), [1.4.12 launcher nonce receipt](verification/2026-11-03-agy-1.4.12-launcher-nonce.md)), any other 1.4.x launches with a stderr warning (agy updates itself; set `AGY_CLI_DISABLE_AUTO_UPDATE=1` for a live run, #1140), and other versions are refused (#589) unless the person trusts that exact version with `ahub agents trust agy <version>` (#691). The running hub must advertise `native_hook_launch`; an older hub refuses before any write with an upgrade-or-restart remedy. Only the person's launcher starts it: conductor starts are refused, there is no controlled restore or resume, and a declared agy extra instance is refused when the config loads (#625). Headless/print, resume (`--conversation`, `--continue`, `--new`), `--mode`, `--dangerously-skip-permissions` and `--unattended` are refused until their native mapping is verified; `--print-command` previews without writing.
5305
+ Antigravity is optional and off roster by default. Use `ahub roster add agy`, then run `ahub agy` in a terminal: it runs the Antigravity CLI as a fresh pull-only TUI in that terminal. It accepts the 1.4.x line plus exact 1.5.3: 1.4.2, 1.4.7, 1.4.12 and 1.5.3 are the verified versions ([1.4.7 receipt](verification/2026-10-26-agy-1.4.7.md), [1.4.12 launcher nonce receipt](verification/2026-11-03-agy-1.4.12-launcher-nonce.md), [1.5.3 account-free receipt](verification/2026-11-08-agy-1.5.3.md)), any other 1.4.x launches with a stderr warning (agy updates itself, so the launcher sets `AGY_CLI_DISABLE_AUTO_UPDATE=1` for its version probe and the TUI it starts, #1140, #1286), and other versions are refused (#589) unless the person trusts that exact version with `ahub agents trust agy <version>` (#691). The running hub must advertise `native_hook_launch`; an older hub refuses before any write with an upgrade-or-restart remedy. Only the person's launcher starts it: conductor starts are refused, there is no controlled restore or resume, and a declared agy extra instance is refused when the config loads (#625). Headless/print, resume (`--conversation`, `--continue`, `--new`), `--mode`, `--dangerously-skip-permissions` and `--unattended` are refused until their native mapping is verified; `--print-command` previews without writing.
5005
5306
 
5006
5307
  The launcher writes exactly `plugin.json`, `mcp_config.json` and `hooks.json` in `.agents/plugins/agent-hub/`, refusing linked or foreign entries before any write, and removes only its own matching files when the TUI ends. It never writes project-level `.agents/hooks.json` or the person's `~/.gemini` settings. Run `ahub init` afterwards to ignore the untracked plugin directory; a tracked plugin is written with a warning. The native model sees the hub tools under the server name `agent-hub_agent-hub`.
5007
5308
 
5008
- Like Kimi's TUI, agy is pull-only: it reads `hub_inbox` and shares results with `hub_send`, messages do not interrupt it, and automatic routing never picks it. Its roles are capped at implementer and verifier; a plan or review assignment and PII work are refused. Native permissions are unmanaged, with no hub sandbox, hub permission mode, quota source or session resume. Plugin hooks bound to that launch carry the hub's turn facts and report each completed native turn. A native read is recorded as a peer `Read` fact: `view_file` (`AbsolutePath`) in 1.4.2 and `read_file` (`Path`) in 1.4.3; a `StartLine`/`EndLine` range counts as a partial read, never a whole-file view (#555). The live TUI/account leg is unverified ([T0 receipt](verification/2026-10-19-agy-p1-t0.md)). The 1.4.3 T0 re-run keeps 1.4.3 unverified: its `call_mcp_tool` schema and native tool names changed, and the TUI attaches the hub tools only from the second model turn ([1.4.3 receipt](verification/2026-10-21-agy-1.4.3-t0.md), #607).
5309
+ Like Kimi's TUI, agy is pull-only: it reads `hub_inbox` and shares results with `hub_send`, messages do not interrupt it, and automatic routing never picks it. Its roles are capped at implementer and verifier; a plan or review assignment and PII work are refused. Native permissions are unmanaged, with no hub sandbox, hub permission mode, quota source or session resume. Plugin hooks bound to that launch carry the hub's turn facts and report each completed native turn. When messages wait at a model boundary (`SessionStart`, `PreInvocation`), the hook adds one count-only line asking it to call `hub_inbox`, never a body or sender (#1280). A native read is recorded as a peer `Read` fact: `view_file` (`AbsolutePath`) in 1.4.2 and `read_file` (`Path`) in 1.4.3; a `StartLine`/`EndLine` range counts as a partial read, never a whole-file view (#555). The live TUI/account leg is unverified ([T0 receipt](verification/2026-10-19-agy-p1-t0.md)). The 1.4.3 T0 re-run keeps 1.4.3 unverified: its `call_mcp_tool` schema and native tool names changed, and the TUI attaches the hub tools only from the second model turn ([1.4.3 receipt](verification/2026-10-21-agy-1.4.3-t0.md), #607).
5009
5310
 
5010
5311
  ## Configured ACP adapters (#451 A3)
5011
5312
 
@@ -5616,7 +5917,7 @@ A headless Claude implementer (#525-4) is declared separately:
5616
5917
  "claude-impl": { "kind": "claude", "roles": ["implementer"], "start_mode": "headless", "worktree": ".worktrees/claude-impl", "permission_mode": "ask" }
5617
5918
  ```
5618
5919
 
5619
- A headless Claude implementer may name its model (#844): `"model": "claude-sonnet-5-5"` on its declaration passes `--model <value>` on every native turn, resumed ones included. It is one bounded word (letters, digits and `._:/@+-`, up to 128 bytes, no spaces and no leading dash) and is accepted only on a headless implementer, only from the untracked `config.local.json`. A tracked-file declaration is ignored with a reason, and the key is tier 3: the dashboard and a settings session cannot read or change it, only the console or your editor. Unset, the argv is unchanged and Claude uses its own default. `ahub status` shows the model on the instance row as `model source`. Declarations are captured once when the hub daemon starts, so a changed or newly added `model` takes effect after a hub daemon restart (`ahub restart`); restarting only the instance keeps the old model.
5920
+ A headless Claude implementer may name its model (#844): `"model": "claude-sonnet-5-5"` on its declaration passes `--model <value>` on every native turn, resumed ones included. It is one bounded word (letters, digits and `._:/@+-`, up to 128 bytes, no spaces and no leading dash) and is accepted only on a headless implementer, only from the untracked `config.local.json`. A tracked-file declaration is ignored with a reason, and the key is tier 3: the dashboard and a settings session cannot read or change it, only the console or your editor. Unset, the argv is unchanged and Claude uses its own default. `ahub status` shows the model on the instance row as `model source`. Declarations are captured once when the hub daemon starts, so a changed or newly added `model` takes effect after a hub daemon restart (`ahub restart`); restarting only the instance keeps the old model. For a person-owned next-turn override without changing that configuration path, use [live peer model overrides](#live-peer-model-overrides-1317).
5620
5921
 
5621
5922
  Create and register the attached-branch worktree first; the hub never creates it implicitly. `ahub claude --as claude-impl` asks the hub to start the instance and returns. Idle holds no native child. Task deliveries use a persisted UUID per task: first `--session-id`, later `--resume`. Unscoped chat reuses a session only when exactly one task is in progress for the instance; otherwise it is ephemeral. Positive missing-transcript evidence starts fresh with a notice; unknown read evidence retains the id. Reset includes the per-peer session map. A child inbox read refuses a queued batch attributed to another task and leaves it queued for that task's own session.
5622
5923
 
@@ -5770,7 +6071,7 @@ Native launcher connections retry transport timeouts/unclean closes with bounded
5770
6071
 
5771
6072
  ## amem runtime (agent-mem#13 P4)
5772
6073
 
5773
- The hub exact-pins @staix/agent-mem 0.22.1 for recall, curated evidence and local deterministic capture. No runtime call reaches a claude-mem worker; `memory.worker_url` is ignored. The person's `ahub memory import claude-mem` remains explicit and off by default. Existing profile, alias, PII and private-turn fences still apply. `ask --remember` and context checkpoints propose machine-local candidates, never automatic curated evidence. See [memory integration](memory.md#in-process-recall-and-capture-agent-mem13-p4) for changed behavior and bounded-view ceilings.
6074
+ The hub exact-pins @staix/agent-mem <!-- facts:agent-mem-pin -->0.23.1<!-- /facts:agent-mem-pin --> for recall, curated evidence and local deterministic capture. No runtime call reaches a claude-mem worker; `memory.worker_url` is ignored. The person's `ahub memory import claude-mem` remains explicit and off by default. Existing profile, alias, PII and private-turn fences still apply. `ask --remember` and context checkpoints propose machine-local candidates, never automatic curated evidence. See [memory integration](memory.md#in-process-recall-and-capture-agent-mem13-p4) for changed behavior and bounded-view ceilings.
5774
6075
 
5775
6076
  ## Person-run backlog legs (#1176)
5776
6077
 
@@ -6344,7 +6645,7 @@ ahub worktrees tidy --confirm <fingerprint> # the person's own terminal: re-r
6344
6645
 
6345
6646
  ## Results: what a run you started left behind (#1054)
6346
6647
 
6347
- A run you started (a message you sent to a peer, from the console, `ahub say` or acon) settles into one **result** when the adapter's own signal says it ended and nothing else is pending: no queued continuation, open permission or question, owned child, running check or held delivery. Peers whose adapter declares no settle signal (Kimi in its own TUI, agy, person-typed Codex turns) leave none. Outcomes are `done`, `failed`, `limit` and `interrupted` (the hub restarted mid-run); an explicit stop by you and a budget pause leave no result. A result is **unread** until you mark it read; nothing a snapshot, `ahub status` or `ahub results` reads ever marks one.
6648
+ A run you started (a message you sent to a peer, from the console, `ahub say` or acon) settles into one **result** when the adapter's own signal says it ended and nothing else is pending: no queued continuation, open permission or question, owned child, running check or held delivery. Peers whose adapter declares no settle signal (agy, person-typed Codex turns) leave none; Kimi in its own TUI leaves one only when started with `ahub kimi` on the verified Kimi 2.5.2 (see [Kimi in its own TUI](#kimi-in-its-own-tui)). Outcomes are `done`, `failed`, `limit` and `interrupted` (the hub restarted mid-run); an explicit stop by you and a budget pause leave no result. A result is **unread** until you mark it read; nothing a snapshot, `ahub status` or `ahub results` reads ever marks one.
6348
6649
 
6349
6650
  - `ahub results [--unread]` lists them, newest first, unread ones marked `*`: id, `<peer>: <outcome word>` (a private result is `a private result`), when it settled and how long it ran. It prints no prompt and no reply.
6350
6651
  - `ahub results --read <id>` marks one read (it must exist and be unread). `--read-all` marks every unread result of the project. `--unread-last` lights the most recent read one again by deleting its receipt.
@@ -6405,13 +6706,13 @@ Corrupt or unreadable provenance stays unknown and held. The status next step di
6405
6706
 
6406
6707
  A ready memory profile does not prove a successful sync. Read the separate automatic-sync result and timestamp in `ahub status` or Memory. It resets to not run after daemon restart; it is not a machine-global record of manual CLI attempts. Missing remote configuration is the neutral `not-configured` state; an interrupted/stopped attempt is neutral `stopped`. Both keep their safe next step and produce no failure log under `quietSyncError`.
6407
6708
 
6408
- For policy-declined rows, a person runs `ahub memory declined --json` to inspect IDs and safe rule categories, never note text. The pinned agent-mem 0.22.1 public `inspectSyncPolicy` API bounds physical outbound inspection for shared-v1 and generation profiles. A complete result supplies the declined IDs and rules; `complete:false` means the scan is unavailable, stale or over its diagnostic bound, not zero declined rows. The command stays read-only and rechecks its profile binding. No migration or policy edit is performed automatically.
6709
+ For policy-declined rows, a person runs `ahub memory declined --json` to inspect IDs and safe rule categories, never note text. The pinned agent-mem 0.23.1 public `inspectSyncPolicy` API bounds physical outbound inspection for shared-v1 and generation profiles. A complete result supplies the declined IDs and rules; `complete:false` means the scan is unavailable, stale or over its diagnostic bound, not zero declined rows. The command stays read-only and rechecks its profile binding. No migration or policy edit is performed automatically.
6409
6710
 
6410
- The hub projects verified withheld/exported counts when a trusted SDK sync returns a successful placement result. That projection is covered by an injected-result fixture, not an activated-store sync. Agent-mem 0.22.1 requires a fresh `placementAttestation` even for an already active v3 store; the default hub supplies no such integration and therefore cannot perform that sync yet. Activation alone does not enable the hub's automatic sync, and no attestation is fabricated. A nonzero withheld count in a successful result is not a transport failure and does not mean those rows were deleted, rewritten or published. Inspect the IDs and rules before deliberately changing any source or approved policy; a forget operation does not remove its original text from physical history.
6711
+ The hub projects verified withheld/exported counts when a trusted SDK sync returns a successful placement result. That projection is covered by an injected-result fixture, not an activated-store sync. Agent-mem requires a fresh `placementAttestation` even for an already active v3 store; since 0.35.0 the daemon builds one with the package's own `createPlacementAttestation` on every sync pass and supplies no writer facts of its own, so no attestation is fabricated. A refused attestation is the closed cause `writer` in status, the dashboard and the log: its category, its cause class (`missing`, `changed`, `unavailable`) and the next step (repair the amem install and its host pin, then a person runs `amem migrate --sync-placement --dry-run`). Activation remains a person-run action. A nonzero withheld count in a successful result is not a transport failure and does not mean those rows were deleted, rewritten or published. Inspect the IDs and rules before deliberately changing any source or approved policy; a forget operation does not remove its original text from physical history.
6411
6712
 
6412
- A legacy store without active per-row placement still blocks a sync containing declined rows. Status names inactive placement and the person-only `amem migrate --sync-placement --dry-run` preview, which requires every local writer installation through repeatable `--writer-root` flags. It also names the missing host `placementAttestation` and keeps `ahub memory declined` as the safe inspection step. The native CLI supports activation from 0.22.0; the pinned core is 0.22.1. See the [activation preview and prerequisites](memory.md#automatic-sync-health-1173) before any separately authorized apply. Preview writes nothing, and activation does not migrate layouts, change policy or automatically raise a format floor. The hub does not invoke it or fabricate its proof.
6713
+ A legacy store without active per-row placement still blocks a sync containing declined rows. Status names inactive placement and the person-only `amem migrate --sync-placement --dry-run` preview, which requires every local writer installation through repeatable `--writer-root` flags. It keeps `ahub memory declined` as the safe inspection step. The native CLI supports activation from 0.22.0; the pinned core is 0.23.1. See the [activation preview and prerequisites](memory.md#automatic-sync-health-1173) before any separately authorized apply. Preview writes nothing, and activation does not migrate layouts, change policy or automatically raise a format floor. The hub does not invoke it or fabricate its proof.
6413
6714
 
6414
- The bounded diagnostic addresses #1173 AC4; AC5 has result projection only. The core activation command is available, while legitimate fresh host-attestation integration remains necessary for default-host v3 sync. #1173 remains open for that integration.
6715
+ The bounded diagnostic addresses #1173 AC4; AC5 has result projection and the host attestation. Whether an activated store syncs on a person's machine is not established by the isolated fixtures.
6415
6716
 
6416
6717
  For a transport or unknown failure, run `ahub memory sync` as a person to see the local diagnostic. Logs keep only the cause class and known declined count, once per unchanged failure episode. Complete declined-ID sets can distinguish a changed failure without placing IDs or bodies in the log; incomplete inspection keeps the set unknown. Existing write-time screening and profile/privacy fences remain. SDK adoption and isolated fixtures do not prove live rollout or a successful sync of the person's memory home.
6417
6718
 
@@ -6444,3 +6745,217 @@ Upgrade the broker, then every room member to a codec-capable release, then reco
6444
6745
  `ahub status` distinguishes local room storage opening from a failed broker
6445
6746
  connection; direct `ahub room status` failures expose only closed cause/action
6446
6747
  metadata. A healthy online link needs no broker repair advice.
6748
+
6749
+ ## Optional Orca companion contract (#1198 O0)
6750
+
6751
+ The [versioned O0 contract](../integrations/orca/host-contract-v1.md) and its
6752
+ [capability matrix](../integrations/orca/host-contract-v1.json) describe the
6753
+ installed Orca 1.4.252 declarations, pinned primary references and reusable
6754
+ fake-host/CLI fixtures. O0 installs no plugin and changes no ahub runtime.
6755
+ Worker and frame APIs differ: workspace inventory/navigation are worker-only;
6756
+ focused context lacks the exact workspace id, execution host and real root
6757
+ needed to select a project. Names and branches are never selection authority.
6758
+
6759
+ Terminal focus remains unsupported until a plugin terminal id can be bound to a
6760
+ current same-host CLI handle. Workspace navigation and manual `ahub console` /
6761
+ `ahub ui` commands remain available independently of a companion. No remote
6762
+ loopback URL is opened locally. Native local, remote, Windows and mobile results
6763
+ are recorded separately; declarations and fake results do not qualify them.
6764
+ The three baseline journeys count procedure actions only, with observed native
6765
+ counts still unknown. O1 must record actual open/disable/standalone UI proof.
6766
+
6767
+ ### Optional Orca companion: chooser and entry points (#1198 O1)
6768
+
6769
+ [`integrations/orca/`](../integrations/orca/README.md) holds the optional companion: a manifest, a
6770
+ trusted worker and a sidebar panel. It is not in the npm package, no ahub source refers to it, and ahub and
6771
+ Orca keep all their own functionality without it. It lists Orca's workspaces, you select one by its exact
6772
+ id (a name or branch never selects), and it opens an existing screen: `ahub ui` run as a short-lived child
6773
+ with the folder as its working directory only, or a terminal created in the workspace running `ahub console`
6774
+ or `acon ui`. A selection binds the id, host id, kind and the folder's real path on this Mac and is
6775
+ re-checked against a complete inventory before every effect; a duplicate, missing, truncated or changed
6776
+ identity, or a host not declared to be this Mac, refuses visibly and runs nothing. It never passes `--json`
6777
+ to `ahub ui`, never approves, assigns, completes, retries, stops or upgrades anything, and keeps no CLI
6778
+ output, one-time link or task text in plugin state or logs.
6779
+
6780
+ Install is explicit and in an isolated Orca profile first (`orca plugin validate`, `orca plugin dev load`
6781
+ with the fingerprint Orca prints); remove it with `orca plugin disable` and `orca plugin dev unload`. The
6782
+ fake-host checks are `test/orca-companion-entry.test.ts` and `test/orca-companion-independence.test.ts`.
6783
+ A **Show status** button (O2) reads the selected project's public status on a click only: version, tasks by
6784
+ state, and peer state with queue length, copied from `ahub status --json` through a closed allowlist so no
6785
+ free-text field (task title, a peer's current line, a notice, a path) is read. Offline, unavailable version,
6786
+ partial, stale and unknown stay distinct states; there is no poller, no transcript read and no terminal read.
6787
+ There is **no terminal focus**: O0 found no proof that a plugin terminal id names an `orca terminal switch`
6788
+ handle, so the companion never calls it and never replaces a terminal.
6789
+
6790
+ **Platforms (O3).** [`support-matrix.json`](../integrations/orca/support-matrix.json) records one verdict per
6791
+ combination and a test enforces that `supported` needs a receipt that no longer says NOT RUN. Today none is
6792
+ supported: macOS local is fixture-only, and remote, Windows, browser or mobile and Linux are unsupported, each
6793
+ with a person-run receipt template under `integrations/orca/receipts/`. A remote workspace is refused before
6794
+ anything runs and shows the existing remote console command to run on that host; a remote loopback link is never
6795
+ opened locally. On Windows every workspace is refused (`unsupported_platform`). Where a client has no external
6796
+ navigation no link is held. Mobile availability is not inferred from SSH reachability or from Orca's mobile
6797
+ pairing option.
6798
+
6799
+ **The native open and disable receipt is a person-run leg that has not been run**
6800
+ ([`native-receipt.md`](../integrations/orca/native-receipt.md)): the action counts in the companion's README
6801
+ are procedure counts, remote, Windows and mobile stay unqualified (O3), and there is no focus-existing-console
6802
+ action.
6803
+
6804
+ ## Herdr companion host contract (#1199 H0)
6805
+
6806
+ [`integrations/herdr/host-contract-v1.md`](../integrations/herdr/host-contract-v1.md) freezes what an optional Herdr mod may
6807
+ rely on, taken from the installed app's own declarations (Herdr 0.1.13, herdr-core 0.1.18, hashes recorded), and records
6808
+ what was not observed. It changes no ahub runtime and installs nothing. `exec` is bounded (30 s, 8 MiB) and used for
6809
+ reads only. The pane's machine and cwd are a navigation hint: the project root comes from `ahub projects --json` read on
6810
+ that same machine, and a remote cwd is never resolved on this Mac. Herdr has no install or disable command, so a mod is
6811
+ a folder the person adds and removes, and what removal does to a shown copy is for the H1 native receipt. Existing
6812
+ terminal focus is unbuilt until a Herdr terminal can be mapped to an ahub console. The three baseline journeys are
6813
+ procedure counts that have not been run in a native window.
6814
+
6815
+ ### Who owns a pull-only peer (#1310)
6816
+
6817
+ `ahub status` (and `--json`) names the current owner of an attached channel peer, beside its standby rows: `<peer> owner: pid <n>; attached <age>; native session <id>`. The pid is what the owner's plugin reported at hello (`pid unknown` when it reported none or an unusable one). `attached` is when the attach finished, not when the hello arrived. The native session reads `unverified` until the hub has verified one through the native-facts path (today agy), and an id that is not a short plain token is never shown. The owner line is absent while a newer session is still arriving, while the peer is offline, and after a detach, so a claimant is never shown as attached, and a takeover replaces the whole line at once. Use it to see who holds a queued peer before running `ahub peer takeover`. It is display only: it never decides delivery, takeover or permission, and `status.json` on disk does not carry it.
6818
+
6819
+ ### Herdr project and machine entry (#1199 H1)
6820
+
6821
+ [`integrations/herdr/`](../integrations/herdr/README.md) is an optional mod: three files the person copies to
6822
+ `mods/ahub` in the Herdr app home, which Herdr reads live and which declares `place: none`, so no layout changes.
6823
+ It lists the focused space's machine and folder and that machine's registered projects, and opens nothing until a
6824
+ row is selected. Open console starts one new tab in the selected space running `ahub --project <id> console`, with the project root
6825
+ as the tab's folder. Before every effect it re-reads the space, the machine and the registrations and runs
6826
+ `ahub status --json` from the project folder, and refuses a changed, duplicate, offline or unready target. The local
6827
+ dashboard runs the existing `ahub ui` and keeps no link or ticket. A remote space gets the console tab or a copyable
6828
+ command, never a dashboard link. Remove it by deleting the folder. It starts, stops and upgrades nothing and is not an
6829
+ ahub dependency. `herdr-app check` accepts it in a scratch home; the native open and disable receipt is a person-run
6830
+ leg that has not been run.
6831
+
6832
+ ### Herdr status card and existing-console focus (#1199 H2)
6833
+
6834
+ The Herdr entry also offers three read-only actions. **Status** reads `ahub --project <id> status --json` from the project folder on the
6835
+ selected machine, on a click only, and shows task counts, peer states and queue lengths and the hub version from a closed
6836
+ allowlist, so no task or note text reaches it. Missing CLI, timeout, an exit error, no hub running, oversized or
6837
+ unrecognized output, an old hub, a partial reading and a stale one are distinct states. **Focus console** and **Peek
6838
+ console** (this computer only) accept a terminal only after `hc get` shows it live and running `ahub console` for the
6839
+ selected project, exactly one match, rechecked right before a single `pane.focus` or `terminal.peek`, and a focus is
6840
+ believed only after Herdr's `focused()` shows it. They never start, replace, type into or close a terminal, and
6841
+ Herdr's own working, blocked, done and idle words are shown, never sent to ahub. A console started through a wrapper is
6842
+ not recognized and the person uses Open console. The native focus receipt is a person-run leg that has not been run.
6843
+
6844
+ ### Herdr platform support matrix (#1199 H3)
6845
+
6846
+ [`integrations/herdr/support-matrix.json`](../integrations/herdr/support-matrix.json) records one verdict per combination and a
6847
+ test enforces that `supported` needs a receipt that no longer says NOT RUN. Today none is supported: macOS local is
6848
+ fixture-only, and remote, Windows, Linux and browser or mobile are unsupported, each with a person-run receipt template under
6849
+ `integrations/herdr/receipts/`. The remote console workflow is designed (read on that machine, new tab in that machine's space,
6850
+ no dashboard link, no remote focus) and exercised against the fixture only, because no remote machine is configured on the
6851
+ machine it was built on. On Windows every action refuses. Mobile is not inferred from SSH reachability or a machine being ready.
6852
+ [`baseline-comparison.md`](../integrations/herdr/baseline-comparison.md) lays the entry's journeys beside the H0 baseline as
6853
+ procedure counts: the entry costs one more action than the baseline when the pane is already in the right project, and its value
6854
+ is the verified machine and root, not a shorter path.
6855
+
6856
+ ## Bounded visual attachments (#1058)
6857
+
6858
+ `hub_visual` lets an ordinary local peer or the local worker annotate public work
6859
+ with a Mermaid diagram, comparison, table or before/after view. It is a separate
6860
+ tool, not extra fields in `hub_send`. Its input is `{payload, task?, to?}` and its
6861
+ receipt is `visual <id>`. `hub_visual_show {id}` explicitly reads a retained public
6862
+ record in this project. Read-only roles may read; publication still requires the
6863
+ ordinary shared-note capability and no-writes admission. Isolated fresh-reviewer
6864
+ MCP remains limited to `hub_review` and `hub_task_show`.
6865
+
6866
+ The stored payload is the exact acon `VisualPayload` shape. acon owns `ui.visual`,
6867
+ its sandboxed Mermaid renderer, table kit and asset route; the hub imports types
6868
+ only. The standalone dashboard shows a readable title and text/source. Stream
6869
+ stubs and task details load a visual on demand. A missing/older acon visual kit
6870
+ keeps the fallback rather than loading another renderer into the hub.
6871
+
6872
+ ```json
6873
+ {"payload":{"kind":"mermaid","title":"Review flow","source":"flowchart TD\nPlan --> Implement --> Review"}}
6874
+ ```
6875
+
6876
+ The four kinds are `mermaid` (`source`, optional `title`), `comparison` (2-6
6877
+ `options`, optional `title`), `table` (`columns`, `rows`, optional `caption`) and
6878
+ `beforeAfter` (`before`, `after`, optional `caption`). Each comparison option has a
6879
+ title, optional summary, up to 10 pros and 10 cons, and optional `recommended`.
6880
+ Tables allow at most 500 rows and 12 columns. Column fields are `key`, `label`,
6881
+ optional `align: start|end` and `format: text|number|percent`; percent values use
6882
+ percent units as acon does. Cells are strings or finite numbers.
6883
+
6884
+ Mermaid is self-contained source without a Markdown fence, at most 20 KiB UTF-8.
6885
+ The first non-comment line starts with a supported diagram keyword. This is only
6886
+ a header check, not parsing proof: acon shows source with a notice when its parser
6887
+ fails or the renderer times out. The hub adds no parser or Mermaid dependency.
6888
+
6889
+ Hub admission also bounds the entire serialized payload to 1 MiB and each text
6890
+ field: title 300 bytes, caption 2000, alt 1000, text side 20 KiB, option summary
6891
+ 4000, each pro/con 2000, table cell 4000, column key 128 and file path 4096. These
6892
+ are hub storage/admission ceilings, distinct from acon's payload type. Every text
6893
+ field and record key is screened before sanitization and again afterwards; an
6894
+ unsafe key is refused rather than renamed. Oversized or malformed data is refused
6895
+ with the field and repair step, never silently truncated or partially stored.
6896
+
6897
+ A before/after side is either `{text}` or a project path with alt text:
6898
+
6899
+ ```json
6900
+ {"payload":{"kind":"beforeAfter","caption":"Layout change","before":{"path":"artifacts/before.png","alt":"Previous layout"},"after":{"path":"artifacts/after.png","alt":"Updated layout"}}}
6901
+ ```
6902
+
6903
+ Paths are relative to the canonical hub project, not an inferred peer worktree.
6904
+ WebP asset display requires acon 0.1.7 or later (agent-console#81). Older 0.1.3/0.1.4 hosts refuse that MIME type; use PNG or JPEG there. The hub adds no transcoder.
6905
+
6906
+ Only a guarded public PNG, JPEG or WebP of at most 8 MiB is accepted; extension
6907
+ and byte signature must agree. SVG, secret/PII paths, hub state, outside paths,
6908
+ links swapped during inspection and two names for the same physical file refuse.
6909
+ The hub replaces paths in the payload with opaque asset ids and records its own
6910
+ hash. A later asset read repeats the current guard and hash checks; a changed or
6911
+ newly private image is refused as changed since posted. Post it again using a
6912
+ current public file. A copied visual id in another project's database does not
6913
+ select that project's asset: the acon provider binds the project and instance,
6914
+ then rechecks metadata/privacy after the guarded read before returning bytes.
6915
+
6916
+ The default audience is the console, with an FYI chat stub. Explicit `to` uses
6917
+ local addressed delivery, including `to: "user"` for the console; an empty list
6918
+ broadcasts to local peers. An explicit
6919
+ remote recipient refuses the whole call, rather than silently dropping that
6920
+ target. The stub carries only `[visual <id>] <kind>: <title or caption>` and
6921
+ `refs.visual`. Peers receive no source, table/comparison body or inline image.
6922
+ This feature adds no visual transport over the cross-network broker.
6923
+
6924
+ Publication is refused in a private turn or on a PII task, including when policy
6925
+ or task privacy changes during an awaited image read. Current privacy also gates
6926
+ show, snapshots and assets after posting. Visual file access combines accepted
6927
+ runtime restrictions with the normally merged on-disk candidate policy. A pending
6928
+ stricter path rule protects new visual access immediately; a pending weaker file
6929
+ cannot remove the accepted restriction. Native sessions retain their existing
6930
+ configuration-adoption boundary. The raw visual tool input/result is
6931
+ excluded from observer capture even when the call is refused; amem/delivery
6932
+ capture retains only the sanitized stub. `events.jsonl` records only id, kind,
6933
+ payload byte count, image count and optional task id, never title/source/path or
6934
+ image bytes. A visual is explanatory material, not approval or completion proof,
6935
+ and a visual-only turn does not clear the task's no-write advisory.
6936
+
6937
+ Periodic snapshots and task lists read bounded metadata only. Full payload
6938
+ screening happens on an explicit show; image reads still check current privacy,
6939
+ existence and hash at the asset route. A moved or deleted image leaves the public
6940
+ caption/text/alt record readable but cannot serve its old bytes.
6941
+
6942
+ A recovery refusal asks you to retry after `ahub status` shows the hold released.
6943
+ Role or membership refusals name the missing authority, and a task/config change
6944
+ asks for a retry. If the privacy screen is unavailable, restore the configured screen and retry;
6945
+ if the visual exceeds its input limit, shorten the text first. Private context, paths and pattern hits
6946
+ retain a uniform privacy refusal. Rate limits are checked before expensive
6947
+ screening or file reads and checked again at publication.
6948
+
6949
+ Task-bound visuals live with their task. Unbound records expire after 30 days;
6950
+ `visuals.retention_days` is an integer setting from 1 to 3650 days, applied live,
6951
+ with the normal authority guard on raising retention. Expired/removed records
6952
+ are unavailable even before a maintenance prune runs. No documentation stamp or
6953
+ real-home policy change is performed by posting a visual.
6954
+
6955
+ ## Agent-authored records
6956
+
6957
+ Headless Record handling follows amem's effective machine and repository policy. Set `records: false` in the amem configuration to disable instruction, capture and Record preference; repository policy can only narrow machine policy. There is no hub-only override. Private or unverified contexts fail closed for capture without preventing ordinary answer routing.
6958
+
6959
+ Upgrade every amem consumer sharing a memory home together, including native CLI hooks and embedded hub/plugin copies. A 0.22.1 hook drain silently discards Record format-3 spool entries without a counted loss, and its config parser refuses the `records` key even when false, so set `records: false` only after every consumer is upgraded. The hub exact-pins 0.23.1 and uses its public `recordsEnabled` accessor for the active home and execution repository; it does not upgrade the other consumers automatically. No real home is migrated by the feature's fixtures.
6960
+
6961
+ Count-only `records capture`, `records forwarded`, `records brief` and `records recall` diagnostics report accepted/dropped rows or before/after UTF-8 bytes. Record text, anchors, native session IDs and credentials are absent from these diagnostics. An absent registry proof or an unavailable Record source preserves old note content; a privacy or policy change during a read withholds the stale block.