@staix/agent-hub 0.12.45 → 0.14.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 (108) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +85 -116
  3. package/docs/agent-notes/README.md +6 -0
  4. package/docs/agent-notes/adapters.md +10 -7
  5. package/docs/agent-notes/budget.md +7 -1
  6. package/docs/agent-notes/bus.md +6 -4
  7. package/docs/agent-notes/daemon.md +14 -2
  8. package/docs/agent-notes/local-worker.md +2 -1
  9. package/docs/agent-notes/tasks.md +16 -3
  10. package/docs/agent-notes/tests.md +5 -3
  11. package/docs/dogfood.md +95 -0
  12. package/docs/events.md +14 -1
  13. package/docs/operations.md +290 -27
  14. package/docs/profiles.md +2 -2
  15. package/docs/quickstart.md +13 -3
  16. package/docs/sdlc.md +59 -8
  17. package/docs/security.md +26 -13
  18. package/docs/smoke.md +9 -4
  19. package/docs/specs/2026-09-19-agent-hub-design.md +797 -8
  20. package/docs/verification/2026-10-19-reviewer-isolation.md +2 -0
  21. package/docs/verification/2026-10-22-code-facts.md +78 -0
  22. package/docs/verification/2026-10-22-codex-reviewer-isolation.md +61 -0
  23. package/docs/verification/2026-10-22-kimi-qwen-compat.md +79 -0
  24. package/docs/verification/2026-10-23-0.13.0.md +89 -0
  25. package/docs/verification/2026-10-23-0.14.0.md +69 -0
  26. package/docs/verification/dogfood/2026-10-22.md +3109 -0
  27. package/docs/verified.json +22 -17
  28. package/package.json +1 -1
  29. package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
  30. package/plugins/agent-hub/server.js +702 -347
  31. package/src/adapters/agy/entry.ts +5 -4
  32. package/src/adapters/claude-channel.ts +20 -9
  33. package/src/adapters/codex-appserver.ts +18 -2
  34. package/src/adapters/codex-review-isolation.ts +29 -11
  35. package/src/adapters/compat.ts +140 -0
  36. package/src/adapters/definitions/builtin-metadata.ts +3 -2
  37. package/src/adapters/definitions/native-cli-doctor.ts +12 -5
  38. package/src/adapters/definitions/native-cli-entry-codex.ts +13 -0
  39. package/src/adapters/definitions/native-cli-entry-jcode.ts +9 -6
  40. package/src/adapters/definitions/native-cli-jcode-mcp.ts +9 -2
  41. package/src/adapters/definitions/native-cli-preview-common.ts +2 -0
  42. package/src/adapters/definitions/native-cli-preview-jcode.ts +8 -2
  43. package/src/adapters/definitions/native-cli-preview-qwen.ts +2 -1
  44. package/src/adapters/definitions/qwen-native.ts +7 -3
  45. package/src/adapters/definitions/qwen.ts +2 -1
  46. package/src/adapters/jcode.ts +72 -31
  47. package/src/adapters/kimi-review.ts +6 -3
  48. package/src/adapters/registry-types.ts +3 -1
  49. package/src/adapters/registry.ts +1 -0
  50. package/src/adapters/reviewer-print.ts +34 -7
  51. package/src/cli/agents.ts +40 -0
  52. package/src/cli/console-review.ts +54 -0
  53. package/src/cli/console-state.ts +124 -29
  54. package/src/cli/console-symbols.ts +74 -0
  55. package/src/cli/console-usage.ts +89 -0
  56. package/src/cli/console.ts +26 -1
  57. package/src/cli/help.ts +17 -6
  58. package/src/cli/identity.ts +29 -6
  59. package/src/cli/init.ts +109 -11
  60. package/src/cli/main.ts +127 -11
  61. package/src/cli/output.ts +91 -3
  62. package/src/cli/preview.ts +1 -1
  63. package/src/cli/sdlc-release-guard.ts +82 -0
  64. package/src/cli/status-lines.ts +1 -1
  65. package/src/hub/action-class.ts +362 -0
  66. package/src/hub/action-policy.ts +106 -0
  67. package/src/hub/board.ts +65 -9
  68. package/src/hub/budget.ts +71 -2
  69. package/src/hub/bus.ts +43 -3
  70. package/src/hub/checks.ts +80 -31
  71. package/src/hub/code-facts.ts +223 -0
  72. package/src/hub/config-apply.ts +10 -1
  73. package/src/hub/config-trust.ts +7 -4
  74. package/src/hub/daemon.ts +319 -34
  75. package/src/hub/dogfood-read.ts +199 -0
  76. package/src/hub/dogfood.ts +163 -0
  77. package/src/hub/envelope.ts +1 -1
  78. package/src/hub/events.ts +11 -2
  79. package/src/hub/hub-tools.ts +26 -3
  80. package/src/hub/inbox-polling.ts +25 -0
  81. package/src/hub/instances.ts +87 -5
  82. package/src/hub/manager.ts +2 -2
  83. package/src/hub/profiles.ts +7 -2
  84. package/src/hub/release-worktree.ts +56 -0
  85. package/src/hub/review-brief.ts +36 -0
  86. package/src/hub/sdlc-metrics.ts +260 -0
  87. package/src/hub/sdlc-queue.ts +31 -9
  88. package/src/hub/sdlc-release-authorization.ts +173 -0
  89. package/src/hub/sdlc.ts +54 -14
  90. package/src/hub/settings.ts +5 -2
  91. package/src/hub/snapshots.ts +93 -8
  92. package/src/hub/spec-read.ts +31 -0
  93. package/src/hub/symbol-normalizer.ts +26 -0
  94. package/src/hub/task-code-group.ts +52 -0
  95. package/src/hub/task-code-symbols.ts +90 -0
  96. package/src/hub/task-sweep.ts +3 -2
  97. package/src/hub/tasks.ts +455 -67
  98. package/src/hub/test-integrity.ts +162 -0
  99. package/src/hub/ui.ts +3 -0
  100. package/src/hub/usage-rollup.ts +419 -0
  101. package/src/local/sandbox.ts +5 -3
  102. package/src/ui/index.html +274 -2
  103. package/templates/AGENTS.block.md +1 -1
  104. package/templates/profiles/dev.json +6 -1
  105. package/templates/profiles/general.json +6 -1
  106. package/templates/profiles/research.json +6 -1
  107. package/templates/profiles/writing.json +6 -1
  108. package/templates/release-guard.block.md +6 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,37 @@
2
2
 
3
3
  Issue and pull request numbers in the entries for 0.7.7 and earlier refer to the previous repository, archived on 2026-09-30 when this repository's history was rewritten; the one exception is the open smoke-check issue, formerly #12, which moved here as #1. Numbers in newer entries refer to this repository.
4
4
 
5
+ ## Unreleased
6
+
7
+ ## 0.14.0
8
+
9
+ - Fix #724: `ahub board` at a terminal width prints one line per task: TITLE cut with `...`, an AFTER column for dependencies, a compact STAGE (meter, stage and a next-step code such as `intent: issue?`) and a legend line. `--full` and piped output keep the wrapped layout with the whole SDLC advice; `--json` is unchanged.
10
+ - The dashboard task sheet gets a "Code" group (#684 phase 2c): the task's changed symbols as buttons marked `matching` or `unplanned` against its plan, the plan names nothing changed under as `untouched`, and `unknown (no code facts)` without facts; a private task shows none. The hub classifies them (`codeGroup` in the UI snapshot) and a symbol opens the Memory tab with its canonical `symbol:<name>@<path>` node as the graph start.
11
+ - SDLC metrics and control bands (#481 S7): `ahub sdlc metrics [--since]` and the planner tool `hub_sdlc_metrics` report time from acceptance to plan, first review and merge, first-pass approval and rework rounds, the first-pass completion check, same-head CI reruns, stale-head re-reviews (a verdict bound to a different head than the review requested) and stalls from board history, with `unknown` for anything the board cannot derive. An optional `[bands.<metric>]` table in `sdlc.toml` sets a rolling baseline (mean and standard deviation, a per-metric deviation floor, a minimum step and a drift rule); a breach episode is a hub notice (`log`, remembered across restarts), one read-only triage backlog task (`diagnose`) or one backlog card naming the metric and its evidence (`propose`), once per episode and never while the metric has an open band card. The evaluator runs every five minutes when the board changed, and not while the hub is stopping, recovering or has a storage error. No model runs in detection and no GitHub issue is created.
12
+ - Fix #723: the hub links a task's SDLC issue identity when the task is proposed or accepted (advise and enforce), from its title and refs with no GitHub read, so `issue identity missing` no longer waits for a groom that times out on a large board. A title that qualifies the sibling repository `refs.repo` names (`agent-mem#13`, `owner/repo#N`) now links to it; with no `refs.repo` the propose result suggests setting it. The groom backfill runs first and bounded (32 links, 1 s) before the slow metadata reads.
13
+ - The dashboard gets a Usage view in its Agents view (#683 U4): lifetime totals, the last 14 UTC days of stacked tokens per peer (a day with no record is a gap, never zero), the open pauses and recent limit events, and a task table with a private task shown as `#id [pii]`. It reads the roll-up of `ahub usage` only while the Agents view is open, says the figures are a lower bound while the roll-up is still behind a large log, and keeps `unknown` as the word.
14
+ - Fix #722: a fresh reviewer whose per-review start fails now names a closed cause class (`version`, `binary`, `mcp_inventory`, `plugins_hooks_apps`, `sandbox_proof`, `credential_root`, `timeout`, `unknown`) in the unavailable reason, in hub.log and as the `reviewer` line of `ahub status`, instead of one generic sentence. The class never carries a path, native CLI text or a token; a credential root is named by its path class only. Model-free reproduction of the Codex 0.163.0 reviewer proof under an isolated HOME and CODEX_HOME passed on this machine with empty and synthetic ambient configs, so the failing leg is now readable from the log instead of guessed.
15
+ - Roles capability per kind and form (#525-1): a Claude channel instance may declare `implementer` or `verifier` and then requires its own `worktree` (a planner or reviewer instance still reads the project checkout); a headless Claude instance stays reviewer-only and a headless implementer is refused until #525-4; `planner` is refused on kinds that cannot plan (pi, local, agy) in instance declarations and dropped from `roles` with the ceiling notice.
16
+ - Fix #720: `ahub codex --as <id>` for a headless reviewer instance (`start_mode: "headless"`, `review_mode: "fresh"`) sends the hub `start`, prints the result and returns, as the Claude entry does, instead of failing at terminal recovery after the start succeeded. TUI-only flags are refused for it; the default `ahub codex` TUI path is unchanged.
17
+ - Fix #718: a planner pull of a reserved backlog task now commits the pull and the owner assignment together and offers the task to its exact reserved peer, so no ownerless task with a stale offer is left; an offline, off-roster, paused, full or excluded reserved peer refuses the pull with its reason and no fallback, and an unreserved pull stays lazy.
18
+ - Releases: 0.14.0 includes features, so the minor is bumped.
19
+
20
+ ## 0.13.0
21
+
22
+ - Add the #636 action-policy foundation: a pure classifier, machine-local always-ask configuration, session-bound approval budgets and explicit per-peer enforcement status. Adapter pre-tool gates remain pending; status reports that limit instead of claiming protection.
23
+ - Person-only release approval binds the candidate commit, version and fresh docs; an opt-in init-written Git tag pre-push guard refuses missing or mismatched approval and preserves existing hooks (#481 S6).
24
+ - Bug-labelled tasks gain source-labelled red-first receipts and PR test-integrity readbacks. Enforce blocks review and approval when proof is missing/unknown or a pre-existing test is modified/deleted; advise shows the findings. Status and merge queues retain private metadata screening (#481 S4).
25
+ - Version policy for native agent CLIs (#691): one verified/in-line/trusted/refused decision in `src/adapters/compat.ts` for the Codex and Kimi reviewers, Jcode, agy and Qwen; `ahub agents [--json]` shows it and the person-only `ahub agents trust <id> <version> [--revoke]` accepts an untested version on this machine; refusals name the version and the trust command; a weekly workflow reports upstream drift. Receipts add Codex reviewer 0.163.0 (#697), Kimi reviewer 2.3.0 and Qwen Code 0.26.0 (#714).
26
+ - Extra Jcode instances with their own home, socket and state; hub tools are bound to an owned launch (#685).
27
+ - Usage: ledger events for Codex usage breakdown, budget pause/resume and derived quota resets (#683 U1); an incremental roll-up in `hub.db`, the `usage` control op and `ahub usage [limits|total]` (U2); the console Usage panel (tab `7`) with windows, pauses, last resets and tokens per peer and per selected task (U3).
28
+ - Code structure (#682, #684): optional bounded code facts from ripwire 0.6.7 in the review brief (observed symbols, unknown change kinds, deleted definitions not covered); head-bound task symbol observations kept apart from the task row and dropped on a head, repository or privacy change; the console task detail shows changed symbols (matching, unplanned, untouched) and screened overlaps, and a completed-change notice says `same file, different symbols` only on complete disjoint evidence; plan symbols match by normalized name across overlap notices, grooming and the review brief (#682 phase 1b).
29
+ - SDLC: the hub links lifecycle issue and PR identity from what it can verify (#694); test integrity for bug fixes with red-first receipts and modified pre-existing test detection (#481 S4); `ahub report --dogfood`, a bounded weekly scorecard of real use (#687), with CI flow measured per recorded PR head (#704); a profile-selected review checklist and hub tool descriptions (#507).
30
+ - Console review guidance (#711): `r` on a task in review asks `a approve` or `c request changes`, shows a guide line, refuses an empty changes note and shows the result; an agent shell refused a person-only action is told why; the user-reviewed done notice names the console path.
31
+ - Pull-only peers catch up (#713): `hub_inbox` drains up to 64 KiB oldest first and says how many are waiting; completed-change notices are one per recipient per done, a newer one supersedes the queued one, and none go to unaccepted work; a queued delivery can be discarded while its peer is busy; refusals name their cause; the person's `ahub queue discard --peer <id> --reason <text> [--yes]` and the console Queue `D` clear a backlog. Empty pull-only polling is detected (#681).
32
+ - Fixes: a check's exit and pipes are read through Bun.spawn so an early exit is not lost (#558); snapshot attribution survives own-commit rebases and long reflogs (#612); readiness respects the reserved owner's WIP cap (#655); host timeout kill markers are preserved in the sandbox (#700); the Codex reviewer disables legacy notify (#697).
33
+ - Retired as announced (#481): `sdlc.workflow` (gsd-lite) no longer loads, and the `phase` receipt name is refused; record a `criteria` checklist with `hub_sdlc_record` instead. `local.bash_network: "direct"` still works and still warns; its removal moves to a later release.
34
+ - Releases: a release with any feature bumps the minor version; a fix-only release bumps the patch.
35
+
5
36
  ## 0.12.45
6
37
 
7
38
  The dashboard is redesigned (#469), a machine-local, content-pinned adapter module can add an ACP agent (#451 A4), the peer lists and the CLI surface come from the adapter definitions (#625), and reviews gain structured findings with an Important gate (#481 S3) and plan conformance with a person-only departure acceptance (#481 S2).
package/README.md CHANGED
@@ -1,139 +1,108 @@
1
1
  # agent-hub
2
2
 
3
- A hub on one developer's machine where coding agents work as a team: Claude Code,
4
- Codex, Kimi Code, Pi and a hub-owned local worker (Qwen Code, Jcode and Antigravity
5
- optional) share a message bus and a task board per project and one memory on the
6
- machine, each attached through its own native interface.
3
+ A hub on your own machine where coding agents work together as one team. Claude
4
+ Code, Codex, Kimi Code, Pi and a hub-owned local worker (plus Qwen Code, Jcode and
5
+ Antigravity if you want them) share a message bus, a task board and a memory, each
6
+ connected through its own native interface.
7
7
 
8
- Status: 0.12.45, control protocol 16. Bun + TypeScript, one loopback daemon per
8
+ Status: 0.14.0, control protocol 16. Bun + TypeScript, one loopback daemon per
9
9
  project. Site: [agent-hub.staix.net](https://agent-hub.staix.net).
10
10
 
11
- ## What it does today
12
-
13
- - **Peers, not a pipeline.** Every agent can message the others, and the hub
14
- delivers to each through its native surface (Claude channel plugin, Codex
15
- app-server, ACP, Pi RPC or TUI). Messages are batched into digests, priorities
16
- decide what interrupts a turn, and delivery is durable across restarts.
17
- - **A shared task board.** Tasks are proposed, accepted, done and reviewed through
18
- hub tools. `routing.toml` assigns owners by task class, roles split planning,
19
- implementation and review, and a designated planner can be changed live.
20
- Cause-deduplicated stalls and unread pull-only offers need attention; cancellation
21
- closes work without approving it or releasing its dependents. Owners of
22
- overlapping work in the same repository are told what changed.
23
- - **Shared memory.** Decisions, contracts, fails and findings go to the hub's own
24
- store through the exact-pinned first-party `@staix/agent-mem` library (short-
25
- and long-term tiers, a typed graph, git sync between machines),
26
- next to optional claude-mem capture. Entries belong to their project unless
27
- saved as shared, which makes them readable from every project on the machine.
28
- Agents get memory as a recall preface, as briefs on a task, and through bounded
29
- `hub_memory_search`, `hub_memory_show` and `hub_memory_graph` tools. Agents
30
- can propose corrections; candidates stay outside evidence until a person acts.
31
- - **Local and remote models.** Pi and the local worker run on DGX or local MLX
32
- through an authenticated relay; in-process hub routes select models by task
33
- through a self-hosted OmniRoute gateway. Tasks flagged as PII stay on the on-prem
34
- worker.
35
- - **Guard rails.** The local worker and Pi run in a sandbox, and by default their
36
- writes and shell commands wait for the person's approval; a permission mode
37
- the person picks (`ask-when-needed`, `never-ask`) skips some or all prompts.
38
- Budgets pause a peer before it runs out of quota, and recovery never replays
39
- an effect whose outcome is unknown.
40
- - **Operations.** A terminal console and a local dashboard, one hub per
41
- repository or worktree with a machine-wide view, and `ahub upgrade` that carries
42
- running sessions across versions. Settings reload without a restart where their
43
- apply class allows; guarded values wait for a person's confirmed
44
- `ahub settings reload` in a terminal. The console supports revision-checked task
45
- assignment/unassignment, delivery-queue resolution, per-peer planner WIP limits
46
- and backlog management from the Tasks tab. Peers with independent
47
- tool turns can request one uncertain redelivery; the delivery-only local worker
48
- and headless ACP peers leave held deliveries for the person to resolve.
11
+ ## Quick start
49
12
 
50
- ## Where it is going
51
-
52
- - **Curated memory** ([#412](https://github.com/STAIxBWLB/agent-hub/issues/412)).
53
- The memory core now lives in [agent-mem](https://github.com/STAIxBWLB/agent-mem)
54
- (`@staix/agent-mem`, with the `amem` CLI), which agent-hub uses as its one
55
- exact-pinned first-party dependency. Next, memory becomes curated rather than
56
- captured: compression, removal, correction
57
- and failure-driven management, each shipped only after its contract and a
58
- measurement.
59
- - **Every peer able to do its share** ([#418](https://github.com/STAIxBWLB/agent-hub/issues/418)).
60
- Bounded task-spec reads and opt-in isolated checks are implemented. Installed
61
- managed-Pi verification (AC8) remains open; source and CI results do not certify
62
- that live runtime.
63
- - **Measured, not assumed.** Add-ons and memory are kept or dropped by controlled
64
- comparisons ([#248](https://github.com/STAIxBWLB/agent-hub/issues/248),
65
- [#258](https://github.com/STAIxBWLB/agent-hub/issues/258)).
13
+ You need [Bun](https://bun.sh) 1.4.2 or later on PATH.
66
14
 
67
- ## Install
15
+ ```bash
16
+ bun add -g @staix/agent-hub && ahub setup
17
+ cd <your project> && ahub init && ahub up # daemon plus the operator console
18
+ ```
68
19
 
69
- Requires [Bun](https://bun.sh) 1.4.2 or later on PATH.
20
+ Then start the agents you use, one terminal each, and talk to them:
70
21
 
71
22
  ```bash
72
- bun add -g @staix/agent-hub && ahub setup
73
- cd <your project> && ahub init && ahub up
23
+ ahub claude # Claude Code with the hub channel
24
+ ahub codex # Codex TUI, attached through the hub
25
+ ahub say "Each of you: reply with your name."
26
+ ahub ui # the web dashboard
74
27
  ```
75
28
 
76
- `ahub init --dry-run --json` shows what init would write first. While hubs run,
77
- `ahub upgrade` moves them to the latest release.
78
-
79
- After upgrading from 0.12.30 or earlier, run `ahub jcode --mode headless` once in each
80
- project that uses Jcode to refresh its owned MCP entry before conductor or dashboard
81
- starts of Jcode. The entry's format is unchanged in 0.12.37; a daemon start that still
82
- reports the entry missing or stale is fixed by the same command, which since 0.12.36
83
- writes the running hub's own plugin bundle (#502). Since 0.12.37 the hub accepts jcode
84
- 0.98.0 or later, below 1.0.0, and no longer 0.94.0 (#529).
85
- Since 0.12.39 `ahub jcode` opens Jcode's native TUI on the hub-owned ACP session by
86
- default (#464); `--mode headless` or `ahub settings set peers.jcode.start_mode headless`
87
- keeps headless startup. On 0.12.39 the TUI launcher stopped after the first hub turn;
88
- 0.12.40 opens it again (#564). On 0.12.40 `ahub codex` in a recorded Orca terminal
89
- failed with `invalid remote address`; 0.12.41 starts it again (#586). Since 0.12.42
90
- `ahub agy` accepts the Antigravity CLI 1.4.x line (1.4.2 verified; other patches warn),
91
- because agy updates itself and 0.12.41 refused 1.4.3 (#589). Since 0.12.43 agy's native reads are recorded as peer facts on 1.4.2 and 1.4.3 (#555); 1.4.3 stays unverified, and in its TUI the hub tools appear from the second turn (#267, #607). Since 0.12.44 a headless Kimi or Qwen start made by the hub also opens `ahub watch <peer>` through the terminal provider (#286), an owner turn streak with no recorded writes raises a planner notice (`task_sweep.no_writes_turns`, default 2, 0 disables; #498), a declared agy extra instance is refused at config load (#625), and a standard ACP agent can join a project as a config-only adapter declared in untracked `.agenthub/config.local.json`: headless, off roster by default and pinned to its native identity (#451). Since 0.12.45 the dashboard has a sidebar or tabs, view keys `1`-`7`, a task side sheet, a Plan sheet for the board forms and loading, empty and disconnected states (#469); a person can add a bundled ACP adapter module, pinned by `sha256` in untracked `.agenthub/config.local.json` and imported only by the daemon at startup, and module CI gets the `@staix/agent-hub/conformance` kit (#451); reviews take structured findings, and in SDLC enforce an open Important finding or a changed file outside the plan refuses approval until fixed, amended or accepted by the person with `ahub sdlc accept-departure` (#481); and `ahub roster remove` deletes the roster key of an instance no longer declared (#225).
92
- Cancellation needs a daemon advertising `task_cancel`; controlled downgrades
93
- refuse while cancelled rows exist. Live-test delegated CLI commands need a daemon
94
- advertising `cli_audit_delegated` and refuse with an upgrade remedy otherwise
95
- (#410). For local/Pi implementation, opt in to an available
96
- `checks.runner = "container"`; issue-backed owners also need authenticated
97
- spec access. PII work and designated reviewers retain their separate exemptions.
98
- See [release verification](docs/verification/2026-10-22-0.12.45.md) for the live legs
99
- still open, including #418 AC8, #438 AC7, the native console settings leg (#439 AC5),
100
- real upgrades with reattach (#463) and with a managed terminal and cancelled tasks
101
- (#478), a live headless Claude review (#223), a Claude session over 32 MiB (#517), a
102
- live fresh Codex review (#519), a live split of a real memory home (#536), the live
103
- agy TUI/account leg (#485, hub task #251), Kimi's interactive-TUI coexistence and
104
- usage checks (#533), Jcode's TUI with a personal account, a real provider and hub-tool
105
- calls (#464), a live Jcode tool call held past the inactivity watchdog (#542), a live
106
- steer of a busy Jcode turn (#572) and a real upgrade that exercises the terminal owner
107
- record (#482).
108
-
109
- Before migrating a memory home with `amem`, upgrade every hub using that home to
110
- 0.12.30 or later, check `ahub memory profile-check`, then quiesce and close all memory
111
- consumers. Shared v1 remains the default; installing this release does not migrate
112
- data. The curated shard migration (`amem migrate --shards`, agent-mem 0.4.0) needs
113
- every hub using that home to run 0.12.37 or later, which opens memory through shards
114
- (#534). An older hub (0.12.36 or earlier, agent-mem 0.3.1) does not detect the
115
- split: it keeps reading and writing the old layout, and its writes reach the shards
116
- only through `amem migrate --shards --resume`. Upgrade or stop every such hub before
117
- splitting. `ahub memory profile-check` is split-aware (#536). See [memory profiles and rollout](docs/memory.md#selected-memory-profile).
29
+ `ahub init --dry-run --json` shows what init would write. The
30
+ [Quickstart](docs/quickstart.md) walks through a first session and a working day.
31
+
32
+ ## What you get
33
+
34
+ - **Peers, not a pipeline.** Any agent can message the others. Messages are batched
35
+ into digests, priorities decide what interrupts a turn, and delivery survives
36
+ restarts.
37
+ - **A shared task board.** Work is proposed, accepted, done and reviewed through hub
38
+ tools. Roles split planning, implementation and review, `routing.toml` picks
39
+ owners by task class, and owners of overlapping work are told what changed.
40
+ `ahub sdlc metrics` reads lead times, rework and stalls from board history, and
41
+ optional control bands turn a breach into a backlog card.
42
+ - **Shared memory.** Decisions, contracts, fails and findings are kept through the
43
+ first-party [`@staix/agent-mem`](https://github.com/STAIxBWLB/agent-mem) library
44
+ (short- and long-term tiers, a typed graph, git sync between machines). Agents get
45
+ it as a recall preface, as briefs on a task, and through bounded search tools.
46
+ - **Local and on-prem models.** Pi and the local worker can run on DGX or local MLX
47
+ through an authenticated relay. Tasks marked as PII stay on the on-prem worker.
48
+ - **Guard rails.** The local worker and Pi run in a sandbox, and their writes and
49
+ shell commands wait for your approval unless you choose a looser permission mode.
50
+ Budgets pause an agent before it runs out of quota, and recovery never replays an
51
+ effect whose outcome is unknown.
52
+ - **Operations.** A terminal console and a local dashboard, one hub per repository or
53
+ worktree with a machine-wide view, live settings reload, token usage and quota
54
+ history (`ahub usage` and the dashboard's Usage view), and `ahub upgrade`, which
55
+ carries running sessions across versions.
56
+
57
+ ## Upgrading
58
+
59
+ Run `ahub upgrade` while your hubs are running. Read the notes for your version in
60
+ the [Quickstart](docs/quickstart.md) ("Upgrading to ...") and the
61
+ [Changelog](CHANGELOG.md) first: some releases need a one-time step, such as
62
+ refreshing an agent's MCP entry.
63
+
64
+ Agent CLIs update themselves. `ahub agents` shows which installed versions the hub
65
+ accepts, and `ahub agents trust <id> <version>` accepts an untested one on this
66
+ machine.
67
+
68
+ Before you migrate a memory home with `amem`, read
69
+ [memory profiles and rollout](docs/memory.md#selected-memory-profile). Every hub that
70
+ uses the home must run a version that understands the new layout.
71
+
72
+ ## Where it is going
73
+
74
+ - **Curated memory** ([#412](https://github.com/STAIxBWLB/agent-hub/issues/412)):
75
+ compression, removal, correction and failure-driven management, each shipped only
76
+ after its contract and a measurement.
77
+ - **Every agent able to do its share** ([#418](https://github.com/STAIxBWLB/agent-hub/issues/418)).
78
+ - **Measured, not assumed.** Add-ons and memory are kept or dropped by controlled
79
+ comparisons ([#248](https://github.com/STAIxBWLB/agent-hub/issues/248),
80
+ [#258](https://github.com/STAIxBWLB/agent-hub/issues/258)).
81
+
82
+ Live checks that still need a real account or a person are listed in the
83
+ [release verification report](docs/verification/2026-10-23-0.14.0.md) and the
84
+ [smoke checklist](docs/smoke.md).
118
85
 
119
86
  ## Documentation
120
87
 
121
- - [Quickstart](docs/quickstart.md): install, the first session, a working day
122
- - [Operations guide](docs/operations.md): console, task flow, Pi and models, dashboard, projects, upgrade and recovery
123
- - [Hub memory](docs/memory.md) and [agent-mem](https://github.com/STAIxBWLB/agent-mem)
124
- - [Security notes](docs/security.md): what the hub defends and what it does not
125
- - [Design spec](docs/specs/2026-09-19-agent-hub-design.md): how it is built and why
126
- - [Smoke checklist](docs/smoke.md): what has been verified against real agents
127
- - [Changelog](CHANGELOG.md), [Contributing](CONTRIBUTING.md)
88
+ | Read | For |
89
+ | --- | --- |
90
+ | [Quickstart](docs/quickstart.md) | install, the first session, a working day, upgrade notes |
91
+ | [Operations guide](docs/operations.md) | console, task flow, Pi and models, dashboard, projects, upgrade and recovery |
92
+ | [Hub memory](docs/memory.md) | how memory is stored, shared and recalled |
93
+ | [Security notes](docs/security.md) | what the hub defends and what it does not |
94
+ | [Design spec](docs/specs/2026-09-19-agent-hub-design.md) | how it is built and why |
95
+ | [Smoke checklist](docs/smoke.md) | what has been verified against real agents |
96
+ | [Changelog](CHANGELOG.md), [Contributing](CONTRIBUTING.md) | releases and how to contribute |
128
97
 
129
- `AGENTS.md` and [`docs/agent-notes/`](docs/agent-notes/) hold the rules coding
130
- agents follow when they work on this repository.
98
+ Coding agents that work on this repository follow [`AGENTS.md`](AGENTS.md) and
99
+ [`docs/agent-notes/`](docs/agent-notes/).
131
100
 
132
101
  ## Origin
133
102
 
134
103
  The first idea came from [agent-bridge](https://github.com/raysonmeng/agent-bridge),
135
- which lets Claude Code and Codex talk to each other. agent-hub has since grown
136
- into a different system with its own goals and shares no code with it.
104
+ which lets Claude Code and Codex talk to each other. agent-hub has since grown into a
105
+ different system with its own goals and shares no code with it.
137
106
 
138
107
  ## License
139
108
 
@@ -29,3 +29,9 @@ Background moved out of the area notes for 0.12.45:
29
29
  - Process-tree stops (adapters.md): three review rounds of #113 found snapshot gaps while the tree ran (groups of its own, an unreadable first read, a child started during the grace period).
30
30
  - Local worker (local-worker.md): a bare `private/` deny entry once denied all of `/private/var`; `existsSync` made a dangling link look like a new file, so a write landed at its target.
31
31
  - Suite watchdog (tests.md): macOS CI hit `AHUB_CHECK_HANG_S` while tests still passed (#378, #593).
32
+
33
+ Background moved out of the area notes for 0.13.0:
34
+
35
+ - Jcode MCP entry (#685, adapters.md): jcode gives an MCP entry's own env precedence over the process env (probed on 0.99.0), so a peer id in the shared `.jcode/mcp.json` entry would make every extra instance act as `jcode`.
36
+ - Bulk queue discard (#713, bus.md): a queued row with no journal record of its own carries the journal's global revision, which every discard moves, so a revision read once at the start goes stale after the first discard.
37
+ - Usage roll-up (#683 U2, budget.md): a pass cap smaller than one log line once hung the fold.
@@ -1,6 +1,6 @@
1
1
  # Native adapters and spawned agents
2
2
 
3
- Scope: the Codex, ACP and Pi adapters and the Pi extension (turn `generation` and `activity` events), peer turn state, approvals, and the agent processes the hub spawns and stops. Read before editing `src/adapters/`, `src/pi/`, `src/hub/peers.ts`, `src/hub/permission-mode.ts`, `src/hub/tool-refusal.ts`, `src/hub/child-process.ts`, `src/hub/lifecycle.ts` (shutdown deadline), `src/hub/start-mode.ts`, `src/hub/terminal-open.ts`, `src/memory/capture.ts` (session end), `src/hub/native-hook-launch.ts`, `src/hub/peer-catalog.ts`, the launcher-managed MCP entries (`src/cli/agy-mcp.ts`, `src/cli/kimi-mcp.ts`, `src/cli/jcode-mcp.ts`) or the process-tree stop in `scripts/benchmarks/teardown.ts`, or before adding a native peer.
3
+ Scope: the Codex, ACP and Pi adapters and the Pi extension (turn `generation` and `activity` events), peer turn state, approvals, and the agent processes the hub spawns and stops. Read before editing `src/adapters/`, `src/pi/`, `src/hub/peers.ts`, `src/hub/permission-mode.ts`, `src/hub/tool-refusal.ts`, `src/hub/child-process.ts`, `src/hub/lifecycle.ts` (shutdown deadline), `src/hub/start-mode.ts`, `src/hub/terminal-open.ts`, `src/memory/capture.ts` (session end), `src/hub/native-hook-launch.ts`, `src/hub/peer-catalog.ts`, the launcher-managed MCP entries (`src/cli/agy-mcp.ts`, `src/cli/kimi-mcp.ts`, `src/cli/jcode-mcp.ts`), `src/cli/configured-acp-entry.ts`, the version policy (`src/adapters/compat.ts`, `src/cli/agents.ts`) or the process-tree stop in `scripts/benchmarks/teardown.ts`, or before adding a native peer.
4
4
 
5
5
  - For conceptual architecture, definition fields, generic ACP config and the new-adapter review checklist, see [Adding an AI Tool](../adding-an-ai-tool.md). For definition interfaces, see [registry-types.ts](../../src/adapters/registry-types.ts).
6
6
 
@@ -31,7 +31,7 @@ Scope: the Codex, ACP and Pi adapters and the Pi extension (turn `generation` an
31
31
  - Pi and local approvals (#253): `permit` answers a `ToolApproval` (contract: `local-worker.md`) and `requestPermission` (`src/hub/daemon.ts`) tells the adapter's `ApprovalObserver` what settled it. An expiry counts toward the stop only as `source: "expired"` with `eligibleExpiry` not false (no person answered a request of that peer while it was pending); `person` resets the count, `automatic` (a mode grant, Pi's `always`) never does, and a new turn starts it over. The second one ends the turn, never the peer: local throws `ApprovalWaitStop`, Pi sends `abort_budget` with `cause: "approval"`, and both settle the delivery `completed` with the reason; a Pi whose extension did not announce `approvalTurnAbort` goes offline with the delivery held. `requestPermission` deletes a request from `permissions` before it runs an observer, resolves or reports, and an aborted signal closes it as `cancelled`: a late answer must find nothing to grant, and a failed event, notice or console push never strands the caller.
32
32
  - Pi's `always` grant (#209) is keyed by `executeTool`'s tool name, never parsed from the approval title, and lives only in that Pi start's closure. Never persist it, share it with the local worker, or let the dashboard give it.
33
33
  - Refuse ACP permissions while offline or waiting for watchdog cancellation before consulting automatic or human approvers. Abort pending permission signals on watchdog, offline transition and stop; the daemon withdraws their registered console approvals before any late person answer. Restart creates new requests without reviving withdrawn ones (#311).
34
- - Apply QWEN_MODE_IDS only to a production qwen025 adapter with verified qwen-code 0.25.0 native identity and advertised ids; legacy benchmark ACP stays unmanaged. Await the native acknowledgement before bus admission, preserve existing approval fences, and never map auto or plan. No Qwen quota classification exists until a pinned native refusal is observed.
34
+ - Apply QWEN_MODE_IDS only to a production qwen025 adapter with a qwen-code native identity admitted by compat and advertised ids; legacy benchmark ACP stays unmanaged. Await the native acknowledgement before bus admission, preserve existing approval fences, and never map auto or plan. No Qwen quota classification exists until a pinned native refusal is observed.
35
35
 
36
36
  ## Turns, watchdogs and hub tools
37
37
 
@@ -74,12 +74,15 @@ Scope: the Codex, ACP and Pi adapters and the Pi extension (turn `generation` an
74
74
  ## TUI launchers, native facts and Antigravity
75
75
 
76
76
  - Kimi TUI is a pull-only WsPeer under the one kimi id, with no hub busy/watchdog/permission/quota machinery. Only the person's launcher merges or removes its project MCP entry; a daemon headless start refuses a stale entry and never edits the file. Scrub mode/channel markers from the native child; managed AGENTS instructions and tool descriptions carry the pull rules, not MCP initialize instructions.
77
+ - Empty-inbox polling advice (#681, `InboxPolling` in `src/hub/inbox-polling.ts`) is advisory: never refuse or delay an inbox read for it. The daemon computes it only for a pull-only peer after the pull completed, so a throw drops the advice, never the pulled envelopes; it counts only empty reads with no open, non-backlog task as owner or reviewer, and an assignment resets it. The plugin shows `[polling detected]` only for a well-formed `pollingAdvice` (count >= 3, minutes 0 to 10).
77
78
  - TUI launchers (Kimi, Jcode, agy) write the plugin bundle the hub reports in `status` into their MCP entry, and the CLI's own copy only when the hub reports none (#502). Only the Jcode start path checks the entry against the daemon's own `SERVER_JS` (`jcodeMcpReady`); Kimi TUI admission does not compare it.
78
- - Jcode (#529): accept the native version only through `jcodeVersionSupported` (from `JCODE_MIN_VERSION` up to, not including, `JCODE_BREAKING_VERSION`), in both the version probe and ACP `expectedAgent`; never pin the minor, and lower the breaking version when a release breaks the contract. Every owned jcode process (probe, serve, ACP and TUI) gets `JCODE_OWNED_ENV`, and the private socket name ends in `.sock`.
79
+ - Native CLI versions (#691): every version gate asks `assessVersion` in `src/adapters/compat.ts`; no other file holds a version constant. Add a version to `verified` only with a receipt in `docs/verification/`; a `line` is for contract gates only (an isolation gate never assumes a newer version compatible); the person's `ahub agents trust` admits one exact version on this machine and never skips a runtime proof; `ahub agents` with anything but `--json` stays `console` in `classifyPeerCommand` and refused in `liveTestRefusal` (`src/cli/identity.ts`). A version refusal starts with `VERSION_REFUSAL` and names only versions and the trust command; keep it that way so the daemon may show it.
80
+ - Jcode (#529): accept the native version only through `jcodeVersionSupported` (compat `jcode` line, 0.98.0 up to 1.0.0), in the daemon preflight (`jcodeVersionAsync`: off the event loop, owning its process group, output capped at 64 KiB), the doctor probe (`jcodeVersion`) and ACP `expectedAgent`; never pin the minor, and lower `line.below` when a release breaks the contract. Every owned jcode process (probe, serve, ACP and TUI) gets `JCODE_OWNED_ENV`, and the private socket name ends in `.sock`.
79
81
  - Jcode TUI (#464): use only the current owned launch's top-level `--socket`/`--resume` tuple after native saved-session proof. Prove the save only through `savedSession` (no-follow, single-link regular file, size-bounded, unchanged across the read, its `id` and `messages` matching) and refuse a requested model, provider profile or session that differs from the running launch with `JcodeOptionMismatch`, which names the flag, never its value (#559); both the launcher and `--print-command` report it through `refuseNamedOption` (#574). Hand the tuple only to a person's launcher through `native_tui_launch` (admission: `daemon.md`); previews (`--dry-run`, or `--print-command` from anyone but a person) start nothing and never resolve it. Never synthesize a save or prompt to induce one. Ctrl-C during the save wait and frontend close retain ACP; preserve session/load identity refusal and headless owned recovery metadata, and never record the TUI as a managed terminal (`recordTerminalLaunch`): Jcode has no terminal recovery adapter (#564).
80
82
  - Jcode steer (#572): reach a busy owned jcode only through `nativeSteer` on its private server socket, never by cancelling its turn: `ping` must list `soft_interrupt_ids`, `subscribe` uses `target_session_id` (the ACP session id) and `continue_on_disconnect`, `state` must report `is_processing` (a soft interrupt sent while idle starts a turn by itself), then `soft_interrupt {content, urgent: false, client_id}`. `canSend` rechecks the captured turn, busy state, owned server process, socket identity and session immediately before the write. Every refusal before the send is `failed_safe` with nothing sent; a sent message that is not acknowledged, or whose turn cannot be confirmed, is `needs_review`, never `failed_safe` or `completed`. Read `currentTurn()` before sending and adopt only through `AcpPeer.adoptIntoTurn(turn, ...)`, which refuses a different or ended turn; adopted senders join the turn answer's audience (`replyParent` and `replyAudience` over the turn envelopes, so the hop cap holds), and each steer receipt is `accepted` at once and settles with the turn (`completed` on a normal end, `needs_review` on a cancel, failure, stop or watchdog). Headless Kimi and Qwen have no steer: status reports their waiting important messages only through `importantWaitingStatus` (`src/hub/daemon.ts`) past `budget.stale_min`. A turn that ends and restarts between jcode's `state` reply and the write still receives the message, held `needs_review` and adopted into nobody: keep that ceiling until upstream offers an atomic per-turn steer, and never add a cancel-to-deliver path.
81
83
  - Jcode MCP set (#572): `jcode acp` and `jcode serve` start every server named in `<JCODE_HOME>/mcp.json` and the project's `.jcode/mcp.json`, `.mcp.json` and `.claude/mcp.json`, and no flag or variable selects them (`--mcp-tools`, `JCODE_MCP_TOOLS`, `JCODE_DISABLED_TOOLS` and `JCODE_DISABLE_CLAUDE_MCP` do not). Do not paper over it: `ahub doctor` lists the extra names through `jcodeExtraMcpServers` (`src/adapters/definitions/native-cli-jcode-mcp.ts`), names only, those four paths only, no home search, no link followed. Below the project root or home, `.jcode` and `.claude` must be real directories and the file a regular file (per-component `lstat`, an `O_NOFOLLOW` open and an `fstat` identity recheck); a system alias at or above the configured root stays allowed. Revisit if upstream adds a server selector.
82
- - Antigravity P1 (`src/adapters/agy/`): keep it optional, cloud, off roster by default, implementer/verifier only and pull-only. Until per-mode native prompt behavior is verified, reject hub permission/unattended mappings, headless starts and resume selectors; do not infer a quota source. The launcher refuses extra instances, any `agy --version` outside the 1.4.x line (1.4.2 is T0-verified; other 1.4.x patches launch with a stderr warning, #589), and a hub without the `native_hook_launch` feature.
84
+ - Extra Jcode instances (#685, #368): one `JcodePeer` per declared id (at most 8), each with its own `jcode_home`, socket and state. The person authenticates inside Jcode; the hub passes `-p`, `--model` and `--provider-profile` exactly as declared, handles no model credential or URL, and only checks that the home is a directory the person owns (`jcodeHomeRefusal`: no read or write under it). Never put `AGENTHUB_PEER_ID` in the shared `.jcode/mcp.json` entry (`jcodeMcpEntry`): every owned launch supplies it through `peerChildEnv(<id>)` or the TUI launch env, together with its `AGENTHUB_LAUNCH_ID`. The MCP server with no `AGENTHUB_PEER_ID` in tools mode refuses every tool and never defaults to a peer id. The daemon admits a `tools` hello for a peer whose definition is `owned-acp` only when it carries the launch id of that peer's current owned launch (`JcodePeer.ownsLaunch`, from `jcodeLaunches`); never trust the env alone. `jcode --as <id>` never opens a TUI. Declare the semantic `instances.nativeHome` on a definition, never compare the kind name (`registry-guard`); it is a fixed-builtin capability that `assertNativeCapabilityScope` refuses elsewhere. `checkJcodeInstanceHomes` (`src/hub/instances.ts`, run once at daemon startup) refuses a home equal to or nested in the base Jcode's or another instance's; compare homes only through its `canonicalHome` (`realPath`, case-folded on a volume that folds case, inode), never `resolve()` text. The preview of `--as <id>` passes the instance to `previewJcode` so it shows the daemon's real flags.
85
+ - Antigravity P1 (`src/adapters/agy/`): keep it optional, cloud, off roster by default, implementer/verifier only and pull-only. Until per-mode native prompt behavior is verified, reject hub permission/unattended mappings, headless starts and resume selectors; do not infer a quota source. The launcher refuses extra instances, any `agy --version` that compat `agy` refuses (1.4.2 is T0-verified; other 1.4.x patches launch with a stderr warning, #589; another version needs the person's trust, #691), and a hub without the `native_hook_launch` feature.
83
86
  - Only the person's `ahub agy` launcher manages `.agents/plugins/agent-hub/{plugin.json,mcp_config.json,hooks.json}`. Refuse linked/foreign entries before any write; cleanup only the matching launch. Never write project-level `.agents/hooks.json` or personal Gemini settings. The plugin MCP server is named `agent-hub_agent-hub` by the pinned native client.
84
87
  - Managed native facts use the definition's typed `NativeFactsPort` and the daemon's ephemeral `native_hook_launch` authority. Bind every observation to the daemon, launch, native session, project and current socket generation. Stop requires `fullyIdle`, normal termination and a fresh validated completion row; a missing readback keeps the peer busy. Native fact acknowledgement accepts only SDK ephemeral-message rows in the exact session transcript path. `decodeAgyHook` classifies as `Read` only the observed native read (`view_file` `AbsolutePath`, or 1.4.3 `read_file` `Path`); a `StartLine`/`EndLine` range becomes `offset`/`limit`, so a partial read never counts as a whole-file view. Unobserved edits get no scoped grant or effect attribution. The daemon records Pre/PostToolUse through `Facts.preTool`/`postTool` and offers nothing on them (#555).
85
88
  - `terminalOwner(launchId)` (`src/hub/peers.ts`, #482) answers only for the adapter's current native TUI owner bound to that launch (Codex: the claimed link with that `launchId`, both sockets open; the launcher sends the id as the upgrade's bearer token through `--remote-auth-token-env`, because `codex --remote` refuses any path or query (#586); Pi: a started, verified TUI owner on its requested session); a start acknowledgement is never ownership. A Codex link that is not the claimed one or has a closed socket never adopts a thread.
@@ -88,10 +91,10 @@ Scope: the Codex, ACP and Pi adapters and the Pi extension (turn `generation` an
88
91
 
89
92
  - Keep the default reviewer kind order Claude, Codex, Kimi (selection rules: `tasks.md`).
90
93
  - Keep the retained agy reviewer slot unavailable with exact reason `agy is an implementer peer (#485)`, including explicit reviewer-pool requests. Do not add agy reviewer launch/admission or personal configuration writes to #234; implementer work belongs to #485.
91
- - For fresh Kimi reviewers and task-bound MCP (#533), retain the common per-child task/head/review-revision/generation verdict fences, privacy screening and cleanup-before-settlement. Expose only a host in-process two-tool MCP capability for `hub_review` and `hub_task_show`; never give the native child the full hub control token. Supply source evidence only through the screened review brief/read result and host facts; exclude native Read/file/shell tools. Admit covered Kimi 2.2.0 only when current runtime preparation passes; otherwise keep it unavailable with the refusal reason. Keep actual interactive-TUI/usage ceilings separate from completed live/fake receipts; do not reuse historical Seatbelt/dummy-provider probes as revised-contract passes.
92
- - Start fresh Kimi 2.2.0 without Seatbelt, with private `KIMI_CODE_HOME`, managed-provider-only configuration, clean temporary cwd, explicit empty skills directory and exactly `mcp__agent-hub__hub_review` plus `mcp__agent-hub__hub_task_show` in its agent tools. Use directory symlinks for the person's native `credentials/` and `oauth/`, and link `device_id` to the native identity; do not copy token files, select static-API-key mode or set `KIMI_DISABLE_OAUTH_LOCK`. Let native OAuth share refresh/atomic rename/`proper-lockfile` coordination and the existing Kimi account budget. Revoke verdict authority before shutdown; allow bounded TERM grace while the OAuth lock is held, then use verified owned-group teardown. Report a forced-kill/re-login requirement without attempting host auth repair.
94
+ - For fresh Kimi reviewers and task-bound MCP (#533), retain the common per-child task/head/review-revision/generation verdict fences, privacy screening and cleanup-before-settlement. Expose only a host in-process two-tool MCP capability for `hub_review` and `hub_task_show`; never give the native child the full hub control token. Supply source evidence only through the screened review brief/read result and host facts; exclude native Read/file/shell tools. Admit only compat-covered Kimi versions when current runtime preparation passes; otherwise keep it unavailable with the refusal reason. Keep actual interactive-TUI/usage ceilings separate from completed live/fake receipts; do not reuse historical Seatbelt/dummy-provider probes as revised-contract passes.
95
+ - Start fresh Kimi reviewers without Seatbelt, with private `KIMI_CODE_HOME`, managed-provider-only configuration, clean temporary cwd, explicit empty skills directory and exactly `mcp__agent-hub__hub_review` plus `mcp__agent-hub__hub_task_show` in its agent tools. Use directory symlinks for the person's native `credentials/` and `oauth/`, and link `device_id` to the native identity; do not copy token files, select static-API-key mode or set `KIMI_DISABLE_OAUTH_LOCK`. Let native OAuth share refresh/atomic rename/`proper-lockfile` coordination and the existing Kimi account budget. Revoke verdict authority before shutdown; allow bounded TERM grace while the OAuth lock is held, then use verified owned-group teardown. Report a forced-kill/re-login requirement without attempting host auth repair.
93
96
  - Bind every running print review to its immutable envelope head and review revision. Recheck `Tasks.reviewBindingCurrent` for task-MCP operations, legacy print reads and the verdict authority after awaits; an old child never gains a replacement request. A committed verdict acknowledgement does not authorize subsequent reads or verdicts.
94
97
  - `ReviewerPrintPeer` (`src/adapters/reviewer-print.ts`, #223, #519; `ClaudePrintPeer` is its Claude wrapper) runs one fresh child per review and clears `reviewing` at the leader's exit and first in `kill()`. A stop or watchdog settles `needs_review` at once, before the kill; `completed`, `idle` and `offline` come only after the whole process group is confirmed gone, and a failed cleanup keeps the child handle and stays `busy`. The daemon's `reviewAuthority` compares `reviewToken`: never widen it.
95
98
  - A reviewer backend is a fixed builtin `ReviewerBackend` in a definition's `reviewer`, never config. Its `prepare` must return a complete isolation receipt (matching backend id, binary, version, profile, evidence and `readOnly` true, plus `strictMcp` and `ephemeral` true, or for a `nativePolicy` backend `enforcement: "native-policy"`, `freshSession` and `hubToolsOnly` true) before every child. A `taskMcp` backend refuses without the host's `taskMcp` port and gets only that task's MCP config, never the hub-wide one. `admit(env)` and `launch.current()` are rechecked right before spawn, and a `watchCurrent` launch is polled while the review runs: a change aborts it as `review policy changed`; a missing or failed proof refuses, never a weaker launch or a continuing native session. The daemon puts the reviewer on the bus only after `start()` proved isolation; a failed start or a `start_failed` report marks it in `reviewerUnavailable`, which routing reads as `failing`. An unavailable reviewer variant has no reviewer backend or launch code; `startPeerBody` refuses it before preflight, independently of any native TUI launcher.
96
99
  - Report reviewer failures (`onUnavailable`: `start_failed`, `quota`, `no_verdict`) only after the child's group is confirmed gone and only while its generation is current: a person's stop owns its hold and never reroutes. `quota` comes only from the backend's structured `failure(event)`, never from vendor text. An NDJSON frame over 256 KiB or more than 4 MiB of output aborts the review as `needs_review`.
97
- - Codex reviewer isolation (`src/adapters/codex-review-isolation.ts`): accept only the pinned `codex-cli` version; prove model-free that Apps, plugins and hooks are disabled (`features list`), ambient MCP servers are replaced by disabled stubs, and the hub server alone is enabled with exactly `hub_review` and `hub_task_show`; run the native sandbox fixture probe before every launch; hash the binary once and fence binary and config files by metadata. Credential denies come only from the bounded `HOST_CREDENTIAL_PATHS` and `HOST_CREDENTIAL_ENV` lists, denied at the lexical and canonical path; refuse glob syntax or a root inside the checkout. A root that is dangling or unreadable refuses too; the reason names only its path class (`~/.docker`, `$DOCKER_CONFIG`), never a path or contents. Never pass the control token in a reviewer child's argv, env or MCP config: keep the hub state dir in its `privatePaths`.
100
+ - Codex reviewer isolation (`src/adapters/codex-review-isolation.ts`): accept only a `codex-cli` version that compat `codex-review` admits (verified or the person's trust, #691), and still run every proof below after trust; pass `-c notify=[]` (legacy notify off) on every launch; prove model-free that Apps, plugins and hooks are disabled (`features list`), ambient MCP servers are replaced by disabled stubs, and the hub server alone is enabled with exactly `hub_review` and `hub_task_show`; run the native sandbox fixture probe before every launch; hash the binary once and fence binary and config files by metadata. Credential denies come only from the bounded `HOST_CREDENTIAL_PATHS` and `HOST_CREDENTIAL_ENV` lists, denied at the lexical and canonical path; refuse glob syntax or a root inside the checkout. A root that is dangling or unreadable refuses too; the reason names only its path class (`~/.docker`, `$DOCKER_CONFIG`), never a path or contents. Never pass the control token in a reviewer child's argv, env or MCP config: keep the hub state dir in its `privatePaths`. A refused `prepare` carries a `causeClass` from the closed `REVIEWER_CAUSE_CLASSES` (`version`, `binary`, `mcp_inventory`, `plugins_hooks_apps`, `sandbox_proof`, `credential_root`, `timeout`, `unknown`; `reviewer-print.ts`, #722): `prepare` tracks the proof leg it is on, and `timeout` means a metadata command ran out of its 2 s or the 12 s budget, never an abort. The host builds the reason only through `reviewerUnavailableReason(cause, detail)`: a fixed sentence per class, plus a credential-root path class (`~/.docker: a dangling link`) as the only detail. The one exception is a failed `start()` whose error is a version refusal (`VERSION_REFUSAL`) or comes from a `nativePolicy` backend: the daemon shows that sanitized message, capped at 1000 characters (#691). `ReviewerPrintPeer` itself classes a missing or invalid receipt and an admission change before spawn as `unknown`, a `launch.current()` change before spawn as `binary`, and any other start error as `unknown`. The daemon writes that reason to the `reviewerUnavailable` map (read by routing and shown as the `reviewer` line of `ahub status`), and a `hub.log` line names the class. Never add native CLI text, a path, a version output or a token to the class, the reason or the log.
@@ -1,6 +1,6 @@
1
1
  # Budget, pause and handoff
2
2
 
3
- Scope: quota and native context readings, checkpoints, pause, handoff and resume, and the status line tee. Read before editing `src/hub/budget.ts`, `src/hub/context-window.ts`, `src/cli/statusline-tee.ts`, `src/adapters/definitions/native-quota.ts`, `src/adapters/definitions/native-context.ts`, `src/hub/bus.ts`, `src/hub/delivery-journal.ts`, `src/hub/restart.ts` (pause persistence) or the per-account fanout in `src/hub/instance-budget.ts`.
3
+ Scope: quota and native context readings, checkpoints, pause, handoff and resume, and the status line tee. Read before editing `src/hub/budget.ts`, `src/hub/context-window.ts`, `src/cli/statusline-tee.ts`, `src/adapters/definitions/native-quota.ts`, `src/adapters/definitions/native-context.ts`, `src/hub/bus.ts`, `src/hub/delivery-journal.ts`, `src/hub/restart.ts` (pause persistence), the per-account fanout in `src/hub/instance-budget.ts` or the usage roll-up in `src/hub/usage-rollup.ts`.
4
4
 
5
5
  - Budget: checkpoint first, pause second (a paused peer receives nothing). A handoff that fails is left unmarked so the next tick or hub run retries it; never record it as done.
6
6
  - A handoff needs somebody to hand over to: `canHandOff` is false right after a restart, when no peer is attached yet, and the handoff waits for a later tick instead of stripping tasks of their owner.
@@ -23,3 +23,9 @@ Scope: quota and native context readings, checkpoints, pause, handoff and resume
23
23
  - Release-cut quota admission (`releaseQuotaSnapshot` in `src/hub/daemon.ts`, #481 S11): an account has a quota source only when one of its members' definitions has `budget.reader`, or `budget.resetSoftTokensOnResume` with `kimi_tokens_5h > 0`. Without a source a registered account is reported `no quota source` and never defers; with one, an unknown, stale or below-gate reading defers. Manual and budget-record pauses defer for every attached peer.
24
24
 
25
25
  - Keep `readClaudeTranscriptUsage` at its 32 MiB accounting bound. For native turn completion, follow the admission rule in `daemon.md`.
26
+
27
+ - Limit ledger (#683 U1): write `budget_pause`, `budget_resume` and `quota_reset` only through `Budget.writeLedger` (`hooks.ledger`), at the one pause INSERT in `evaluate` (a new pause that replaces an expired open record first writes that record's `budget_resume`, `by: "hub"`) and the one DELETE in `resume` (`by: "person"` only from `override`); never from a call site that reads `budget_pauses`. `quota_reset` is derived in `deriveResets` from the last reading of each window (`lastSeen`, never `readings`, which a resume clears): a forward `resetsAt` move of more than `RESET_JITTER_MS` (15 min), or a `used` drop of `RESET_DROP` (0.5) or more within one window, is a reset, and anything smaller is the same window. It is deduplicated by `peer|window|resetsAt` in `resetsRecorded`, which the daemon seeds from `events.jsonl` at start so a restart adds none. A reading older than the baseline never counts. Entries carry ids, windows and numbers only: never a reason built from agent text, a checkpoint, a summary or a task title (the reason is the coordinator's own text). A throwing hook never costs the pause or the reading. Codex's `usage` event comes from `onTokenBreakdown` beside `onTokens`; keep `tokens` unchanged.
28
+
29
+ - Usage roll-up (#683 U2): `src/hub/usage-rollup.ts` is the only reader of `events.jsonl` for `ahub usage`, the console Usage panel and the dashboard Usage view (rules: `daemon.md`). A pass reads from the stored offset, keeps whole lines only and commits its rows with the new offset in one transaction; a line longer than the cap is read whole and one past `MAX_LINE_BYTES` is skipped, so the fold always advances. Never store a title, reason or summary there; `limit_events` drops `reason`. issue/pr/release are joined from the board at query time through `TaskLinks`, never stored. A counter with no record behind it is `unknown`, not 0. Claude's total is derived as the sum of its four counters (`claudeReportedTokens`); a source whose input contains the cached part gets none. Every `usage` reply, errors included, answers the rid, and the op is for the console and a configured planner only, never during that planner's private turn. A rebuild from byte 0 must equal the incremental result (`test/usage-rollup.test.ts`). Never present unknown as 0 in a sum: an overall figure with an unknown peer is `partial` and names the peers (`total().unknownPeers`). Never drop rows silently: `limits()` reports `truncated`, `omitted` and the bound. An event-open pause the hub no longer holds is `endedUnknown` (`ahub reset` keeps the log), so `limits(peer, { live })` needs the daemon's held pauses. A poll's fold transaction is IMMEDIATE and rechecks the stored offset, so two roll-ups on one `hub.db` never fold a range twice.
30
+ - Daily usage (#683 U4): `UsageRollup.daily(days, now)` returns the last `days` UTC calendar days (at most 366) ending today, clipped to the first day with a record. A day inside the window with no record is a row with `empty: true`, never zeros. Per peer and day, take the native total when reported, else the counted tokens (`count`), else `unknown`; a day's total is `partial` when some peers are unknown and `unknown` when all are, never a sum that treats them as 0.
31
+
@@ -1,9 +1,8 @@
1
1
  # Bus, digests and replies
2
2
 
3
- - Supervision replaces only pending, never attempted or accepted, envelopes with the same stable key. Preserve the first batch deadline and existing digest/queue ceilings; role/feed revocation withdraws pending notices only. Count supervision from actual transport admission followed by a successful native turn completion, never from enqueue or failure-to-idle.
4
-
5
- Scope: delivery, digests and condensation, reply addressing, priority and limits. Read before editing `src/hub/bus.ts`, `src/hub/envelope.ts`, `src/hub/limits.ts`, `src/hub/delivery-journal.ts`, `src/hub/inference.ts` (digest condensation), or how an adapter addresses a reply or sets its priority.
3
+ Scope: delivery, digests and condensation, reply addressing, priority and limits. Read before editing `src/hub/bus.ts`, `src/hub/envelope.ts`, `src/hub/limits.ts`, `src/hub/delivery-journal.ts`, `src/hub/delivery-superseded.ts`, `src/hub/deferred-hub.ts`, `src/cli/reset.ts`, `src/hub/inference.ts` (digest condensation), or how an adapter addresses a reply or sets its priority.
6
4
 
5
+ - Supervision replaces only pending, never attempted or accepted, envelopes with the same stable key. Preserve the first batch deadline and existing digest/queue ceilings; role/feed revocation withdraws pending notices only. Count supervision from actual transport admission followed by a successful native turn completion, never from enqueue or failure-to-idle.
7
6
  - Lifecycle `deliveryHeld` fences both drain/pull and important steering before owner teardown awaits; retain newly queued work for an explicit restart without altering manual, budget or conductor pauses.
8
7
  - Apply reloaded `batch_max`, `batch_ms` and `queue_cap` only through `Bus.configureScheduling` (#439): it validates the values and changes later batching and admission only. Never re-arm an existing batch deadline, trim or reorder queued work, or touch prefaces, pauses, recovery holds or in-flight delivery from it.
9
8
  - A failed digest is retried one envelope at a time, so a poison envelope cannot take its neighbours down with it.
@@ -16,7 +15,7 @@ Scope: delivery, digests and condensation, reply addressing, priority and limits
16
15
  - Limits admit what is sent: the envelope `newEnvelope` builds (a reply inherits its parent's sender, `digest` resolves to the originals, `capPriority` applies), never the raw `to` and priority. Admit first and build later, and implicit replies all count as broadcasts.
17
16
  - A reply is addressed, never broadcast. An adapter that answers a delivery passes `to: replyAudience(envs)`; anything else with an `inReplyTo` inherits that envelope's sender. Leaving `to` empty fans the message out to every peer.
18
17
  - `digest` is not a peer: addressing a reply at the envelopes the peer was handed sends it nowhere once a delivery was condensed. The bus resolves `digest` back through `lastDelivery.originals`; anything else that reads a reply's `to` has to do the same.
19
- - A pull-only peer (a Claude session without channel pushes, Kimi in its own TUI, or agy) is never drained; `Bus.pull` hands it what one push would take. An attached channel session may also pull its own queue through `hub_inbox` while busy (#493); the same holds and journal completion apply. Drain and pull share `held()` (recovery, an uncertain delivery, any pause, the lifecycle `deliveryHeld` fence) and `remember()` (so `reply_to` resolves), and a drain rechecks `deliverable()` after condensing because such a session can take the peer meanwhile. A pull is journaled `completed` in the same transaction as the bus snapshot, never `accepted`; a failed one puts back only that peer's queue and preface and never reloads the snapshot.
18
+ - A pull-only peer (a Claude session without channel pushes, Kimi in its own TUI, or agy) is never drained by a push; `Bus.pull` hands it its queue oldest first up to `PULL_BUDGET_BYTES` (64 KiB of `frame` text, #713; `takeBudget`), the first item always going so one huge envelope cannot stall it, and a poison one still alone. A push peer, and an attached channel session that is not pull-only, keep `take`'s `DIGEST_MAX`: never raise that for a pull. The daemon's `inbox` reply carries `waiting` and the plugin says `N more waiting: call hub_inbox again before you start work`. An attached channel session may also pull its own queue through `hub_inbox` while busy (#493); the same holds and journal completion apply. Drain and pull share `held()` (recovery, an uncertain delivery, any pause, the lifecycle `deliveryHeld` fence) and `remember()` (so `reply_to` resolves), and a drain rechecks `deliverable()` after condensing because such a session can take the peer meanwhile. A pull is journaled `completed` in the same transaction as the bus snapshot, never `accepted`; a failed one puts back only that peer's queue and preface and never reloads the snapshot.
20
19
  - Record exact task-offer reads only through `DeliveryJournal.completeDelivered` on an active-fenced adapter completion, correlated reply or actual pull, atomically with completion. Capture original offers before terminal pruning and record their ids, never condensed ids; accepted receipts, generic transitions, operator/hub dispositions and restored completed rows are not read evidence.
21
20
  - The `onImportantQueued` nudge for a pull-only peer (#398) is advisory: it never dequeues, steers, journals or settles, and a throw from it never stops an enqueue.
22
21
  - Merge recall and restored prefaces through `mergePrefaces`/`uniqueMemoryLines`; preserve distinct context and new notes, deduplicate memory identities, and keep colliding short prefixes distinct. Use `recallDisplayText`/`recallMemoryLines` before display caps: suppress only matching automatic approval outcomes, retain nonempty rejection details, preserve id headers and complete-word excerpts, and keep token trimming at line boundaries. Keep these changes display-only.
@@ -37,3 +36,6 @@ Scope: delivery, digests and condensation, reply addressing, priority and limits
37
36
  - A review-only instance (a declared extra whose roles are exactly `reviewer`) receives only person messages, hub `review` items and hub `task` items addressed to it. Keep `reviewOnlyItem` in both `silence` (at send) and `relevant` (at delivery) (#223).
38
37
 
39
38
  - Concurrent-edit fan-out is linear in affected tasks: retain each notice's own-task stale/owner fence, but never persist the same change-set notice once for every task pair. Preserve per-task-pair research evidence separately (#576).
39
+
40
+ - Completed-change notices (#713): `Tasks.tellCompleted` publishes one notice per recipient per done through `whileOpenFor`, naming all of that recipient's overlapping tasks, and only for tasks the recipient accepted (`ownerAcceptance` plus an in-work state): never one per overlapping task, never for an offered or proposed task, never again for a repeat done without superseding. The envelope carries `refs.completedFor` (the done task); `Bus.enqueue` replaces an earlier queued, never-attempted notice for the same `completedFor` and recipient through `discardQueued` (a `superseded completed-change notice for task #N` journal outcome), and never touches a user message or another hub notice. `relevant` keeps a grouped notice while any of its tasks is still open for the recipient. Do not widen `completedFor` to anything but the done task id.
41
+ - `resolveDelivery` (#713): a `queued` delivery was never handed to the peer, so a busy peer does not block discarding it; `dispatching` and `accepted` stay refused as `active delivery`, and every other resolution keeps the busy fence. The daemon names the refusal from a closed list (`resolutionRefusal`), never from free error text. `queue discard --peer` goes through `resolveDelivery` row by row, reading each row's current revision just before its discard, never the revision listed at the start.
@@ -8,13 +8,23 @@
8
8
 
9
9
  - Console attention navigation selects a delivery only by an exact visible row id. A missing id or row prints read-only guidance; never select by peer or fall back to the first Queue row.
10
10
 
11
- Scope: the operator console, control WS and its protocol, state files, the dashboard listener and settings, the Claude channel's reconnect, `ahub setup`, git snapshots, upgrade and recovery. Read before editing `src/adapters/definitions/native-recovery*.ts`, `src/adapters/definitions/native-policy-aux.ts`, `src/adapters/definitions/native-config-*.ts`, `src/adapters/definitions/native-cli-*.ts`, `src/cli/console.ts`, `src/cli/console-state.ts`, `src/cli/attention.ts`, `src/ui/needs.ts`, `src/hub/daemon.ts`, `src/hub/ui.ts`, `src/hub/settings.ts`, `src/hub/control-client.ts`, `src/adapters/claude-channel.ts`, `src/hub/restart.ts`, `src/hub/recovery-store.ts`, `src/hub/snapshots.ts`, `src/hub/manager.ts`, `src/hub/lifecycle.ts`, `src/hub/crash.ts`, `src/hub/start-mode.ts`, `src/hub/terminal-open.ts`, `src/hub/recovery-error.ts`, `src/hub/db-timeout.ts`, `src/hub/config-reload.ts`, `src/hub/config-apply.ts`, `src/hub/recovery-continuity.ts`, `src/cli/recovery-presentation-options.ts`, `src/cli/setup.ts`, `src/cli/upgrade*.ts`, `src/cli/terminal-recovery.ts`, `src/cli/recovery-package.ts`, `src/cli/facts-hook.ts`, `src/hub/usage.ts`, `src/hub/memory-profile.ts`, `src/hub/native-hook-launch.ts`, `src/cli/output.ts`, `src/ui/index.html` or the instance modules (`src/hub/instances.ts`, `src/hub/instance-config.ts`, `src/hub/instance-execution.ts`), or before adding a native peer.
11
+ Scope: the operator console, control WS and its protocol, state files, the dashboard listener and settings, the Claude channel's reconnect, `ahub setup`, git snapshots, upgrade and recovery. Read before editing `src/adapters/definitions/native-recovery*.ts`, `src/adapters/definitions/native-policy-aux.ts`, `src/adapters/definitions/native-config-*.ts`, `src/adapters/definitions/native-cli-*.ts`, `src/cli/console.ts`, `src/cli/console-state.ts`, `src/cli/attention.ts`, `src/ui/needs.ts`, `src/hub/daemon.ts`, `src/hub/ui.ts`, `src/hub/settings.ts`, `src/hub/control-client.ts`, `src/adapters/claude-channel.ts`, `src/hub/restart.ts`, `src/hub/recovery-store.ts`, `src/hub/snapshots.ts`, `src/hub/manager.ts`, `src/hub/lifecycle.ts`, `src/hub/crash.ts`, `src/hub/start-mode.ts`, `src/hub/terminal-open.ts`, `src/hub/recovery-error.ts`, `src/hub/db-timeout.ts`, `src/hub/config-reload.ts`, `src/hub/config-apply.ts`, `src/hub/recovery-continuity.ts`, `src/cli/recovery-presentation-options.ts`, `src/cli/setup.ts`, `src/cli/upgrade*.ts`, `src/cli/terminal-recovery.ts`, `src/cli/recovery-package.ts`, `src/cli/facts-hook.ts`, `src/hub/usage.ts`, `src/hub/memory-profile.ts`, `src/hub/native-hook-launch.ts`, `src/cli/output.ts`, `src/ui/index.html`, `src/hub/task-code-group.ts` (dashboard Code group) or the instance modules (`src/hub/instances.ts`, `src/hub/instance-config.ts`, `src/hub/instance-execution.ts`), or before adding a native peer.
12
12
 
13
13
  - The plugin bundle is installed apart from the daemon. Breaking a control WS message contract bumps `PROTOCOL` in `control-client.ts`; a new request answered as unknown or an optional field preserves it, as does additive optional public task metadata in `ui_snapshot` that older clients ignore (such as `deps` in #246).
14
14
  - `peer_stop` is console-only and stops only a hub-owned headless ACP, Pi or local runtime through its adapter. Refuse native-terminal peers and recovery-held mutations; serialize with starts and permission changes, and refuse a recovery `prepare` while a stop runs. An admitted stop holds delivery through `BusOptions.deliveryHeld` before any teardown await (never a console pause that resume can undo, or a global recovery hold that blocks unrelated peers) and withdraws the peer's pending approvals before the adapter stops. A requested stop holds an in-flight delivery as `needs_review` with an ids-only stop reason, suppresses restart/escalation, and never resolves that delivery automatically.
15
+ - Console review guidance (#711): `r` on a task in review opens `reviewChoice` (`src/cli/console-review.ts`, modelled on `modeChoice`) from the row or the open task detail, never a bare prefill; `a` and `c` prefill `review N approved|changes_requested ` with a guide line, a `changes_requested` line with no note is refused in `reduceConsole` before any effect, and the result of a review command is shown as a notice in panels mode (`reviewResult`). The console only prefills and runs `ahub review` as the person: never grant it extra authority, and keep a private task's title out of the choice line (`[pii]`). The CLI cause line (`agentShellCause`, `src/cli/identity.ts`) is printed for the `tools` identity and a person-only hub refusal only, never for the console identity, and never replaces the refusal.
16
+ - Console changed-symbols block (#684 2b): `symbolFields` (`src/cli/console-symbols.ts`) adds `symbols` after `plan` for a task_show-shaped detail only, from the public `codeSymbols`; never from a raw `Task`, never for a private task, never from a stored receipt (one with `repositoryKey` reads as unknown: only the screened public receipt is shown), never as a second copy of the match rule (`planSymbolMatches` in `review-brief.ts`). Unknown is `unknown (no code facts)`, never an empty list.
17
+ - Console Queue `D` (#713) only prefills `queue discard --peer <selected row's peer> --reason `; Enter refuses a missing peer or reason and arms a `y` confirm that names the count, then runs the person's `ahub queue discard ... --yes`. The `queue` op stays console-only; `discard` without `confirmed` only counts, and a confirmed one needs a reason, is refused during recovery or shutdown and discards row by row through `resolveDelivery` (rule: `bus.md`).
18
+ - Console Usage panel (#683 U3): tab `7`, text from `src/cli/console-usage.ts` (pure strings, no terminal or state import), rendered in `console-state.ts`, fetched in `console.ts` `refresh()` only while panel 7 is open. It reads the `usage` op and nothing else: no event-log read, no board field beyond the task id selected in Tasks. A refusal shows `usage unavailable`, never empty usage; an unknown counter is `unknown`, never 0; a private task is `#id [pii]` from the op's `taskPii`, never a title. The `usage` op folds with `catchUp(250)` and answers `behind: true` when it is not done: never answer from one pass of a big log. Keep Left/Right from 7 wrapping like Settings so the `1`..`5` cycle (#309) stays pinned, and keep PAUSE and 7D out of the width drop list.
19
+ - Dashboard snapshot `view` (#683 U4): the daemon's `ui_snapshot` and the manager's forward pass a request's `view` only through `dashboardView` (`DASHBOARD_VIEWS` in `src/hub/ui.ts`); keep that list equal to the page's `DASHBOARD_VIEWS` in `src/ui/index.html`. Any other value is dropped, never forwarded.
20
+ - Dashboard Usage view (#683 U4): `uiSnapshot` builds `usage` only when the request's view is `agents`, never on a hidden view's poll. It folds with `catchUp(100)` and sets `behind: true` when that is not done, so a partial figure is never shown as the whole. A failed read answers `{ unavailable: true }`, never `undefined`, which the page reads as a hidden view and keeps the old figures. The snapshot carries task ids, a `pii` flag and numbers only, never a title; the page shows a private task as `#id [pii]`. Daily figures come only from `UsageRollup.daily` (rule: `budget.md`).
21
+ - Dashboard Code group (#684 2c): `codeGroup` (`src/hub/task-code-group.ts`) is the only dashboard classifier of changed symbols against `plan.symbols`, called in `uiSnapshot` with the public `codeSymbols`, `tasks.publicMetadataText` and `tasks.isPii`; the page (`codeGroupSection`) draws it and never matches names. A private task gets no `codeGroup`. A stored receipt (one with `repositoryKey`), a missing or an unknown one is `state: "unknown"`. A malformed or screened-out plan name is counted in `planWithheld` and marks the group incomplete, never dropped. Lists stop at `CODE_GROUP_MAX` (40) with the rest in `more`, and `counts` are totals. The memory view reads a `node` of up to 512 characters only when it starts with `symbol:` (200 otherwise).
22
+ - SDLC band timer (#481 S7): the daemon's band interval (`bandsEveryMs`, default 5 min) is the only caller of `tasks.proposeFromBands`. It skips while stopping or in recovery, when `tasks.bandsState()` is not ready or `bandsHeld` holds, and when `board.statusRevision()` with the band signature is unchanged since the last pass that ran; it records that key after the write. A failure is one sanitized `hub.log` line. Writer rules: `tasks.md`.
23
+ - `ahub board` (#724): `renderBoard` prints one line per task only with a terminal width and without `--full`; `--full`, `ahub task show` and output without a width keep the full table and SDLC advice. `STAGE_CODES` (`src/cli/output.ts`) keys on blocker-text prefixes from `src/hub/sdlc.ts`: rewording a blocker there updates its code in the same PR. TITLE is cut, never wrapped; STAGE is never dropped (it falls back to the meter and the code); a PII row keeps its `(ahub task show N)` hint while 8 title columns remain.
15
24
  - Settings (#269): the dashboard's `setting`, `setting_undo`, `setting_preview` and `setting_reload` actions and the console-role `settings_get|set|undo|preview|reload` requests go through `settingsSet`, `settingsUndo`, `settingsPreview` (`settingsUndoPreview` for `undo: true`) and `reloadConfig` (`src/hub/daemon.ts`) and the registry in `src/hub/settings.ts`. A new editable setting is a registry entry, never a new write path; a key that chooses what the hub runs or where secrets or task text go joins `NEVER_EDITABLE` instead. Writes go only to `.agenthub/config.local.json` and `.agenthub/routing.local.toml` through `store()`: the loader's own validation first, temp file and rename, never a file git tracks. Authority comes from the dashboard session (`DashboardSession`, `src/hub/ui.ts`) or the console role, never from the request body; `ui_action` from the manager is an ordinary session. Never return a setting's value in an event or notice except through `recordSetting`.
16
25
  - Live reload (#439): `reloadConfig` is the only publication path. Settings writes and undo, `ahub settings reload`, the dashboard reload and the debounced watcher of the four `CONFIG_SOURCE_FILES` (`src/hub/config-reload.ts`) all reach it; `prepareConfiguration` validates the complete candidate before anything is published, and recovery or shutdown holds it. A write or undo passes its own key as `scope` and publishes only that path, and `acknowledgeConfiguration` records only the accepted paths, so other edits stay pending. Every changed key, inside the registry or not, is decided by `authorizeConfigLeaf` (`src/hub/config-apply.ts`): a refused key keeps its accepted value and is reported by name while the others apply, `roster.*` applies only through its own scoped roster write, and a change of the settled planner or conductor holders (`roleHolders` over `resolveSettledRoles`) is guarded even when a role list shrinks. Outside a named write, only the console's `settings_reload` (authority `terminal`) adopts guarded file edits, by sending back the `confirmation` token bound to the candidate `fingerprint` and `configRevision`; changed bytes or accepted state refuse it, and a watcher, dashboard or settings session never confirms. An undo that names `key` and `undoDigest` writes only while `pendingUndoIdentity` still matches both. Keep config-source snapshots plain and cloneable; classify dynamic ids with validated running/source identity scopes, never suffix discovery or a builtin fallback. Compare restart-bound declarations with the running config on every reload, never a pending desired value accepted by an earlier reload; derived defaults stay bound to that running declaration. A `restart` key (`configApply`) keeps its running value and is listed in `pendingRestart`; reload notices and events carry key names and reasons, never values. The console settings editor (the Settings panel, or the Peers panel's `w` WIP shortcut, which opens only an existing `tasks.wip_limits.<peer>` row for a rostered peer listed in status `taskWip`) stays bound to the panel that opened it (`SettingsEdit.panel`) and arms a write only from a preview reply that matches that panel and its open edit's token, key and value; pasted text never confirms. Console Tasks backlog actions (`p`, `a`, `e`, `R`, `c`) only prefill person CLI commands (`backlog pull-ready|plan|return`, `task cancel`) with the row's immutable id and its planning or flow revision; `validateLockedRow` rechecks the row's state and revision at Enter and again at `y`, and no console path writes the board directly (#532).
17
26
  - Retained configuration (#544): a key the reload refused, or a file edit outside a scoped write that still differs from the accepted value, is listed in `retainedConfig` (key and sanitized reason only, never a value) in status, `status.json`, the settings view and the console snapshot until a reload accepts or reverts it (`retainedConfigAfterReload`). A config leaf that selects a peer's native launch belongs in that definition's `cli.launchConfigKeys`: the `start` and `peer_start` replies then carry `retainedLaunchConfig` (the running values), the launcher starts with them and prints `retainedLaunchWarning` before its start request; when a retained value cannot launch (Kimi TUI `kimi_cmd` with no TUI form), the error names the running hub as its source and never echoes the words (#654).
27
+ - `action_policy` (#636) is machine-local only: `stripUntrusted` drops it from any file but `config.local.json`, and `authorizeConfigLeaf` refuses an `action_policy` leaf whose `sourceFile` is not an untracked `config.local.json` before any other check. It applies live (`actionBudgets.policyChanged`); adapters do not enforce it yet, so keep its settings hint saying so until they do.
18
28
  - `pet` is console-local presentation (#483): validate it only through `validatePetConfig`, which both `loadConfig` and the reload schema call, and keep it applied live with no authority (`authorizeConfigLeaf`).
19
29
  - Start modes (#269): every start the hub makes itself goes through `hubStart` (`src/hub/daemon.ts`): the dashboard's `start_peer`, the console-role `peer_start`, the conductor's `start` hook and `pi.auto_start`. It takes the mode from `startMode(peer)` and nothing else; never add a caller-chosen mode, flag or command to it, and never fall back to headless when a terminal cannot be opened (refuse with the manual command). The words a terminal runs are assembled there from `process.execPath`, the CLI entry, the project root and `startWords(peer)`. The only continuity exception is a validated Pi bookmark on `pi.auto_start`: append its recorded session file (or its session id when no file was recorded), backend and model; never pass both session selectors or accept caller-written replacements. Recovery and `startPeer` callers that pass a recorded mode keep it. A launcher with no `--mode` takes the daemon's accepted start mode from `status.json` `startModes` (`readControl`) before the file's value. A TUI start of an `owned-acp` peer (Jcode) from the console's `peer_start` opens its terminal even while the ACP owner is online: that terminal is a frontend on the owner. The conductor and the dashboard answer `already` for an attached one (#559). The real terminal providers are wired only in `src/hub/lifecycle.ts` (`DaemonOptions.terminal`): a daemon built without them, as in every test, opens nothing; a test injects a recording provider and never the real one.
20
30
  - A headless start that `hubStart` made for a peer with `capabilities.watch` (Kimi, Qwen) also opens `ahub watch <peer>` through `openWatchPane` (#286 AC10): the same provider, fixed words from `process.execPath`, `CLI_ENTRY`, the project root and the peer id, one per peer per `PEER_START_RETRY_MS`. It runs only after the start succeeded and never fails it: no provider or a failed open reports `watch.command` in the result and a notice, with the provider's own text in the log only. Never open a pane for an `already` result or a declared instance.
@@ -75,14 +85,16 @@ Scope: the operator console, control WS and its protocol, state files, the dashb
75
85
  - Bind adapter and instance declarations once at startup through the daemon-local `adapterCatalog` (`createProjectAdapterCatalog(loadedConfig.adapters, externalDefinitions)`, the definitions `loadExternalAdapterDefinitions` imported at startup; a reload passes the same `externalDefinitions` to `loadConfig` and imports nothing), its `definitionOf` closure and the `configuredIds`/`extraIds` catalogs; never publish them to the global registry or take them from a request. `adapters` and `instances` are restart-class, and `adapters` is `NEVER_EDITABLE` and in `ALL_MACHINE_LOCAL`. Settings rows come only from `projectSettings(declarations, adapterCatalog)`; a `locked` row (a read-only instance's permission) is refused by `checkSettingValue` and `settingRefusal` for every authority, and the console and dashboard never arm an edit or undo for it.
76
86
  - Configured ACP starts (`startPeerBody`): before effects refuse native launch overrides (model, route, backend, unattended, provider profile, session file, fresh) and an invalid session selector; answer `already` only for the same requested native session and never pass `qwen_cmd`; after a failed start keep the owner with `pendingOwnedProcess` on the bus, let `peer_stop` admit that offline owner and refuse starts until it is confirmed gone, reporting only a withheld-detail reason. In `taskOp`, refuse a read-only instance's op outside `READ_ONLY_HUB_TOOLS` before effects. Publish `configuredAdapters`, `startablePeers` and `readOnlyPeers` in status as bounded ids only. Keep controlled recovery refused (`session.recoveryRefusal`); crash resume passes only a bounded, control-free recorded `sessionId`, never a recorded model.
77
87
  - `validateInstances` admits `review_mode: "fresh"` only on a headless instance whose roles are exactly `["reviewer"]` and whose kind declares a fresh reviewer; a fresh reviewer refuses `worktree` and `permission_mode` (variant selection: `adapters.md`). `review.pool` and `review.rerotate` are validated by `reviewerPoolConfig` at startup and in the config schema: 1 to 16 distinct kinds from `REVIEW_POOL_KINDS`.
88
+ - Channel-kind instances (#525 R1, `validateInstances`): a terminal instance declares roles within its kind's `instances.roles` (Claude: planner, implementer, verifier, reviewer). An `implementer` or `verifier` needs its own `worktree`; a planner or reviewer refuses one. A headless channel-kind instance is a reviewer with roles exactly `["reviewer"]`, and a headless implementer is refused until #525-4. `planner` needs `projection.plannerEligible`: `validateInstances` refuses it, `loadConductorPolicy` drops it with the ceiling notice, and `effectiveInstanceRoles` filters it for instance-keyed `roles`. A declared worktree never proves owner isolation (rule: `tasks.md`).
78
89
  - Every start resolves `resolveInstanceExecutionCwd` and passes that `executionCwd` as adapter cwd, sandbox, grant and Pi session root; set `executionOwners` only for the adapter actually started. Its git calls share one `GIT_BUDGET_MS` allowance through `gitWithin` (#225): a call that finds it spent refuses without starting git; never give each call its own timeout. Extra-instance native state is per instance, while its MCP binding keeps the canonical hub state dir.
79
90
  - Recovery: record an extra Codex launch row only after the start reply supplies `executionCwd`; keep `projectRoot`, `executionCwd` and `terminalRoot` separate and never bind an extra by kind metadata. Abort and archive validate receipts without current declarations; restore, fresh and reattach require them; a screen that only shows a recorded receipt reads it with `restoreReceiptStatus(value, { authority: "historical" })`, never for a decision. `instanceRecoverySourceRefusal` refuses before effects when the source lacks `external_adapter_modules` while `adapter_modules` is declared, and lacks `configured_acp` or `peer_instances` while a configured or declared id is not offline or the source's peer states are unreadable; a source whose such ids all read offline proceeds. Extra-instance turns write `turn_workspaces` in the `turns` transaction and leave the legacy snapshot fields unknown; undo requires the same trusted declaration and validated root.
80
91
 
81
92
  - Name every Claude per-peer state file through `claudeStateFile` (`src/hub/claude-state.ts`) with that peer's id, never a hard-coded builtin name; pick a peer's `terminal-recovery.json` row only through `ownTerminalRow`. `sessionPointers` in `src/cli/reset.ts` must match any new per-peer pointer (#450 I3). Claude state files belong to Claude peers only: select those peers by `transport === "claude-channel"`, never by `identity.mcp === "channel-plugin"` or `turnEnd === "native-hook"` (agy has both), and `claudeStateFile` throws for any other id (#624).
82
93
  - A declared Claude channel extra is never hub-started: keep it out of `STARTABLE_PEERS`, instance start modes and `RESERVED_IDS`. A launcher admits `--as <id>` only when the running daemon's status `activeInstances` lists it (`requireActiveInstance`), never from the config on disk.
94
+ - A declared instance that resolves to its kind's `reviewerVariant` is hub-started (`hubStarted` from `instanceLaunchArguments`): `ahub <kind> --as <id>` refuses launch arguments other than the preview flags, sends one `start` request and returns (Claude #223, Codex #720). It never opens a TUI, writes a launch or terminal-recovery record, or execs; its preview prints `start: "hub"`.
83
95
  - Verify a native Claude Stop and its start baseline only through `readClaudeTranscriptCompletion` (4 MiB backwards chunks, at most 64 MiB plus one boundary byte; incomplete or out-of-bound rows are unavailable), never the size-capped `readClaudeTranscriptUsage`; an `unavailable` refusal leaves the peer busy and notifies once per turn start (#517).
84
96
 
85
- - Classify HEAD moves with bounded, append-fenced per-worktree reflog evidence. Exclude only incoming checkout/merge/pull/proven-fast-forward reset paths; preserve commit/amend/cherry-pick and sequencer replay changes; classify rebase/pull --rebase start checkout as incoming, with overlapping replay paths unknown. Never exclude paths still dirty against end HEAD (autostash/checkout -m included). Unreadable or ambiguous movement/capture evidence is unknown, never an exclusion. Compute a concurrent change set once and publish no more than one conditional notice per affected task. Keep per-task-pair research events. Cache only raw checkout-branch probes within one synchronous burst, and reapply current privacy screening on every status rendering (#576).
97
+ - Classify HEAD moves with bounded, append-fenced per-worktree reflog evidence. Exclude only incoming checkout/merge/pull/proven-fast-forward reset paths; preserve commit/amend/cherry-pick and sequencer replay changes. Take a rebase/pull --rebase start checkout's incoming paths from the single `merge-base --all` of old and upstream to upstream (no base or several is unknown), so replayed own commits stay attributed; a proven fast-forward reset stays incoming even when its target holds the peer's commit from another worktree (#612). Batch `diff-tree --stdin` only for single-parent pick and commit rows; diff squash, fixup, reword and edit rows one by one. Never exclude paths still dirty against end HEAD (autostash/checkout -m included). Unreadable or ambiguous movement/capture evidence is unknown, never an exclusion. Compute a concurrent change set once and publish no more than one conditional notice per affected task. Keep per-task-pair research events. Cache only raw checkout-branch probes within one synchronous burst, and reapply current privacy screening on every status rendering (#576).
86
98
 
87
99
  - Share one monotonic 250 ms snapshot Git budget until the next event-loop I/O turn. Repository/HEAD probes, capture and end diffs consume the same remaining deadline; exhaustion or unreadable diff evidence records unknown trees and no attribution/undo, with one diagnostic per burst. Never substitute a stale baseline or apply a late result (#576). `DaemonOptions.snapshotClock` is host-only (tests hold it, rule: `tests.md`): never read it from configuration or a tool, production leaves it unset (`performance.now`), and the share stays 250 ms with `snapshotBurstMs` still clamped to it. Every deadline comparison goes through `budgetNow(budget)`, never `performance.now()` against `budget.deadline`.
88
100
 
@@ -3,7 +3,8 @@
3
3
  Scope: the hub-native local worker, its tools, path guard, denylist and seatbelt sandbox. Read before editing `src/adapters/local-worker.ts`, `src/local/`, `src/memory/capture.ts` (reads the denylist), `src/hub/facts.ts` (denylist and large-file stamps), `src/hub/git.ts`, the grant filter in `src/hub/permission-mode.ts`, or the sandbox root in `src/hub/instance-execution.ts`.
4
4
 
5
5
  - Nothing the local worker executes may run outside `sandboxedExec`; a new tool that spawns a process goes through it, and a tool that touches a path goes through `guardPath`.
6
- - The sandbox denies home reads by default (toolchain dirs, the project and its real git dir excepted) and all network, loopback included: claude-mem and the Codex app-server listen on loopback without auth. Tests that bind a local port therefore fail when the worker runs them, also with `local.bash_network: true` (the egress proxy keeps loopback closed, and the proxy variables send a test's plain-HTTP `fetch` to the proxy, which refuses it); that is the intended trade-off, and `"direct"` is the switch until 0.13.0 removes it.
6
+ - A timeout or abort in `sandboxedExec` marks the result `(killed: <signal>, timeout?)` from its own `killRequested` flag, never only from the exit signal: a shell may report its killed child as a normal exit 137.
7
+ - The sandbox denies home reads by default (toolchain dirs, the project and its real git dir excepted) and all network, loopback included: claude-mem and the Codex app-server listen on loopback without auth. Tests that bind a local port therefore fail when the worker runs them, also with `local.bash_network: true` (the egress proxy keeps loopback closed, and the proxy variables send a test's plain-HTTP `fetch` to the proxy, which refuses it); that is the intended trade-off, and `"direct"` is the switch until a later release removes it.
7
8
  - The seatbelt profile refuses the `.git` name itself at any depth (creation, rename, symlink, gitfile), `config` and `hooks` directly under it, `commondir` at any depth below it or below an external git dir, and `.agenthub` anywhere in the project, every name folded against the 16 code points HFS+ ignores (#270). A submodule's git dir under the common dir's `modules/` tree gets the same `config`/`hooks` refusal at the depth submodules nest, every term anchored below the discovered modules root so ancestor directories named `refs`/`config`/`hooks` neither disable nor trip the rule, and a `/refs/` or `/logs/` segment below it excludes the path so refs named `config` or `hooks` stay writable (#281); the discovered git dirs, the modules root and the components in between are also refused as directory-entry targets (rename, replace, symlink, recreate). Known residuals: a submodule whose own path holds a whole `refs` or `logs` segment (a substring, `catalogs` included, keeps the refusal; the exclusion matches whole components only), a nested repository in bare layout (the hub never runs git there; docs/security.md), folded spellings of the components between `.git` and a submodule's git dir on a volume that folds names (the 1024-byte SBPL literal cannot hold a folded path; external git dirs carry the same residual since #270), and the submodules of a repository nested inside the project (only the project's own common dir is discovered). An external git dir's `config`/`hooks` rules cover only its own children plus the hooks directory's contents: at any depth they would deny refs named `config` or `hooks` in a worktree or submodule project. The fold is a group alternation, never a character class (the seatbelt regex engine is byte-oriented and a quantifier after a multi-byte class member binds to its last byte), and every pattern keeps its `^` anchor (an unanchored starred alternation does not match). Folded literals never contain the project root: an SBPL string literal dies at 1024 bytes. The cost: `git init`, `git clone` and `git worktree add` do not run inside the sandbox, and neither do `git worktree remove`, `git worktree prune` of a stale record, `git clean -ffd` with a nested repository, `git submodule update --init` (its git dir's `config` write is refused), or `rm -rf` of a directory holding a `.git`.
8
9
  - Build every git invocation outside the sandbox through the single implementation exported by exact-pinned `@staix/agent-mem/platform` (`memoryGitArgv`/`memoryGitEnv`/`memoryGitSync`), re-exported as `hubGitArgv`/`hubGitEnv`/`hubGitSync` by `src/hub/git.ts`. Keep the git export identity, behavior and direct-spawn tripwire checks in `test/hub-git.test.ts` passing; retain home compatibility and Node-safe process-entry checks in `test/memory-parity.test.ts`. The sandboxed git tool in `src/local/tools.ts` retains its own behavior. Use `{ credentials: true }` only for the memory repository the hub creates itself, never a project's repository. Use the core deny policy re-exported by `src/local/deny.ts` for guards, sandbox rules and capture; do not add another denylist.
9
10
  - SBPL strings go through `sbplString` (the plain `"..."` form). In the raw `#"..."` form a backslash escapes nothing, so a `"` in a path ends the literal and the whole profile fails to parse.