@staix/agent-hub 0.13.0 → 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 (40) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +6 -4
  3. package/docs/agent-notes/adapters.md +1 -1
  4. package/docs/agent-notes/budget.md +2 -1
  5. package/docs/agent-notes/daemon.md +8 -1
  6. package/docs/agent-notes/tasks.md +7 -4
  7. package/docs/agent-notes/tests.md +1 -1
  8. package/docs/dogfood.md +10 -7
  9. package/docs/operations.md +45 -13
  10. package/docs/quickstart.md +6 -2
  11. package/docs/sdlc.md +23 -2
  12. package/docs/security.md +9 -5
  13. package/docs/specs/2026-09-19-agent-hub-design.md +38 -0
  14. package/docs/verification/2026-10-22-codex-reviewer-isolation.md +14 -0
  15. package/docs/verification/2026-10-23-0.14.0.md +69 -0
  16. package/docs/verified.json +20 -15
  17. package/package.json +1 -1
  18. package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
  19. package/plugins/agent-hub/server.js +91 -20
  20. package/src/adapters/codex-review-isolation.ts +18 -7
  21. package/src/adapters/definitions/builtin-metadata.ts +1 -1
  22. package/src/adapters/definitions/native-cli-entry-codex.ts +13 -0
  23. package/src/adapters/reviewer-print.ts +34 -7
  24. package/src/cli/help.ts +2 -1
  25. package/src/cli/main.ts +5 -0
  26. package/src/cli/output.ts +47 -2
  27. package/src/cli/status-lines.ts +1 -1
  28. package/src/hub/board.ts +22 -1
  29. package/src/hub/daemon.ts +84 -14
  30. package/src/hub/hub-tools.ts +2 -1
  31. package/src/hub/instances.ts +14 -4
  32. package/src/hub/manager.ts +2 -2
  33. package/src/hub/sdlc-metrics.ts +260 -0
  34. package/src/hub/sdlc.ts +5 -3
  35. package/src/hub/spec-read.ts +31 -0
  36. package/src/hub/task-code-group.ts +52 -0
  37. package/src/hub/tasks.ts +172 -22
  38. package/src/hub/ui.ts +3 -0
  39. package/src/hub/usage-rollup.ts +58 -0
  40. package/src/ui/index.html +273 -2
package/CHANGELOG.md CHANGED
@@ -4,6 +4,19 @@ Issue and pull request numbers in the entries for 0.7.7 and earlier refer to the
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.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
+
7
20
  ## 0.13.0
8
21
 
9
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.
package/README.md CHANGED
@@ -5,7 +5,7 @@ Code, Codex, Kimi Code, Pi and a hub-owned local worker (plus Qwen Code, Jcode a
5
5
  Antigravity if you want them) share a message bus, a task board and a memory, each
6
6
  connected through its own native interface.
7
7
 
8
- Status: 0.13.0, 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
11
  ## Quick start
@@ -37,6 +37,8 @@ ahub ui # the web dashboard
37
37
  - **A shared task board.** Work is proposed, accepted, done and reviewed through hub
38
38
  tools. Roles split planning, implementation and review, `routing.toml` picks
39
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.
40
42
  - **Shared memory.** Decisions, contracts, fails and findings are kept through the
41
43
  first-party [`@staix/agent-mem`](https://github.com/STAIxBWLB/agent-mem) library
42
44
  (short- and long-term tiers, a typed graph, git sync between machines). Agents get
@@ -49,8 +51,8 @@ ahub ui # the web dashboard
49
51
  effect whose outcome is unknown.
50
52
  - **Operations.** A terminal console and a local dashboard, one hub per repository or
51
53
  worktree with a machine-wide view, live settings reload, token usage and quota
52
- history (`ahub usage`), and `ahub upgrade`, which carries running sessions across
53
- versions.
54
+ history (`ahub usage` and the dashboard's Usage view), and `ahub upgrade`, which
55
+ carries running sessions across versions.
54
56
 
55
57
  ## Upgrading
56
58
 
@@ -78,7 +80,7 @@ uses the home must run a version that understands the new layout.
78
80
  [#258](https://github.com/STAIxBWLB/agent-hub/issues/258)).
79
81
 
80
82
  Live checks that still need a real account or a person are listed in the
81
- [release verification report](docs/verification/2026-10-23-0.13.0.md) and the
83
+ [release verification report](docs/verification/2026-10-23-0.14.0.md) and the
82
84
  [smoke checklist](docs/smoke.md).
83
85
 
84
86
  ## Documentation
@@ -97,4 +97,4 @@ Scope: the Codex, ACP and Pi adapters and the Pi extension (turn `generation` an
97
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.
98
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.
99
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`.
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`.
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.
@@ -26,5 +26,6 @@ Scope: quota and native context readings, checkpoints, pause, handoff and resume
26
26
 
27
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
28
 
29
- - Usage roll-up (#683 U2): `src/hub/usage-rollup.ts` is the only reader of `events.jsonl` for `ahub usage`. 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.
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.
30
31
 
@@ -8,7 +8,7 @@
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.
@@ -16,6 +16,11 @@ Scope: the operator console, control WS and its protocol, state files, the dashb
16
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
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
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.
19
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`.
20
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).
21
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).
@@ -80,11 +85,13 @@ Scope: the operator console, control WS and its protocol, state files, the dashb
80
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.
81
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.
82
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`).
83
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.
84
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.
85
91
 
86
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).
87
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"`.
88
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).
89
96
 
90
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).
@@ -1,6 +1,6 @@
1
1
  # Task board and hub tools
2
2
 
3
- Scope: the task flow, assignment, the state machine and the hub's MCP tools. Read before editing `src/hub/tasks.ts`, `src/hub/board.ts`, `src/hub/routing.ts`, `src/hub/task-sweep.ts`, `src/hub/settled-roles.ts`, `src/hub/conductor.ts`, `src/hub/supervision.ts`, `src/hub/tool-refusal.ts`, `src/hub/hub-tools.ts`, `src/hub/review-brief.ts`, `src/hub/reviewer-pool.ts`, `src/hub/reviewer-pool-config.ts`, `src/hub/release-admission.ts`, `src/hub/plan-conformance.ts` or `src/hub/no-writes.ts`.
3
+ Scope: the task flow, assignment, the state machine and the hub's MCP tools. Read before editing `src/hub/tasks.ts`, `src/hub/board.ts`, `src/hub/routing.ts`, `src/hub/task-sweep.ts`, `src/hub/settled-roles.ts`, `src/hub/conductor.ts`, `src/hub/supervision.ts`, `src/hub/tool-refusal.ts`, `src/hub/hub-tools.ts`, `src/hub/review-brief.ts`, `src/hub/reviewer-pool.ts`, `src/hub/reviewer-pool-config.ts`, `src/hub/release-admission.ts`, `src/hub/plan-conformance.ts`, `src/hub/sdlc-metrics.ts` or `src/hub/no-writes.ts`.
4
4
 
5
5
  - Scope every task-place comparison through `TaskRepositories` (`src/hub/task-repository.ts`, #476): explicit `refs.repo`, else a common outside-absolute-ref repository or parent, else this project. Preserve relative display spelling within that key and gate overlap, contradiction, replay and held evidence by equal keys. Warm/cache repository identity at task mutation and startup; never repeat Git reads for unchanged overlap inputs. Set `refs.repo` for work in another repository; repo-only accept metadata never grants execution or file access.
6
6
  - Tool callers are models: MCP `inputSchema` is not enforced on the way in. Normalize at the boundary (`cleanRefs`) before anything reaches the board, and never throw after a board write: a publish or notify that follows a `review()` or `done()` board write goes through the one `releaseNotice` guard, which turns a failed publish into a console notice, drops a failed notify and never retries (#231, #243). An assignment's delivery once its board write is saved (#276, #297): the move is fact, the failed publish is not retried in place (the journal's own recovery covers a storage error, and a retry may duplicate a publish that actually left), and a person looks for a queued or needs_review delivery with `ahub queue list --peer <owner>` (then `ahub queue show <id>`, or the accepted entry in `ahub task show <id>` if the journal is down) before re-sending with `ahub task assign <id> <owner>`. The distinction is made once, in `assignOwner`: a failure after the write prints the one console notice from `undeliveredNotice` right there, once, and throws `AssignmentUndeliveredError` carrying the saved task; callers (`review()`'s repeated rejection, `escalate` and its callers, the ready offer, both relays, the idle sweep, rescreen) decide control flow by the type and print nothing more, and `propose`, `assignTo` and `decline` additionally let the error reach the caller in its turn, which is a person or peer that can act. Two exceptions get no notice, by construction: a row superseded meanwhile, meaning a later history entry that moves or settles the task (another assignment, an unassign, the owner's accept, done, approval, close), while entries that do neither (the hub's quiet bookkeeping: answers, reviewer changes, idle sweeps, check results) leave the assignment undelivered and the notice stands, so the original error goes to the caller as before only on supersession; and a claim's own accepted write and "claimed by" line stay outside the delivery rule (a claim is its own hand-over; its overlap and routing announcements to earlier owners are deliveries and share the wrapper, so a failure there does read as one). A failure before the write stays a plain `Error` (#254), a failed write leaves the ready offer unconsumed (#231), and the notices before the write (waits, no-owner, the reserved-owner line) go through `releaseNotice` too, so those pre-write notices cannot fail their caller.
@@ -32,7 +32,8 @@ Scope: the task flow, assignment, the state machine and the hub's MCP tools. Rea
32
32
 
33
33
  - Criteria checklists (`criteria`, retired alias `phase`) are written only through Tasks.recordLifecycle by that task's reviewer/person, after the PII screen. Refuse a checklist task list before any write; never change dependencies, accepted owners or reservations from a checklist. Keep verified checklist claims separate from externally read CI, and bind them to the current reviewed head. Screen `checklistOf(next)`, never `next.criteria` alone: off stores a phase write under `phase`. Plain off (no retired `workflow`) keeps the #257 receipt bytes, the schema-1 export and the `phase` tool schema; advertise `criteria` only when daemon status carries `lifecycleCriteria`. Export only derived sanitized board/config projections.
34
34
 
35
- - A planner patch that names `reserved` at all, the same value included, is refused while `hasPersonReservation` holds; planner pull and grooming both ask it (#378).
35
+ - A planner patch that names `reserved` at all, the same value included, is refused while `hasPersonReservation` holds (#378).
36
+ - A planner pull of a reserved backlog task (#718) admits only that exact peer, whoever reserved it: refuse when it is excluded, ineligible or at its planner WIP cap, never fall back to another peer. It goes through `assignOwner` with `plannerPull`, whose `guard` rechecks authority, revision and owner right before `Board.pullAssigned` writes `backlog pulled` and the assignment in one transaction; a stale prior offer is withdrawn, never reused, and the pull is not undoable through planning. An unreserved pull stays the ownerless `Board.pullPlanned` move.
36
37
  - `done()` gates the SDLC transition once: pass `gated` to `complete()` when nothing was awaited since, and after any awaited gate readback refuse if another done already moved the task, in every mode (#382).
37
38
  - A changed `reviewedHead`, or one supplied with a changed repo or PR, needs the reviewer or a person; a head dropped because the owner relinked the PR does not (#382).
38
39
 
@@ -46,9 +47,10 @@ Scope: the task flow, assignment, the state machine and the hub's MCP tools. Rea
46
47
 
47
48
  - Resolve any id that may be an extra instance or a configured kind through the startup-bound project catalog: `this.d.adapterCatalog` with `this.d.instances?.()` in Tasks, `opts.adapterCatalog` in `assign()` and `relayCandidates`, and the `adapterCatalog` hook in `Conductor`/`conductorStart`; a bare `definitionOf(id)` answers builtins only. Only a kind's routing preference expands to its instances; manual ids, reservations and nominations stay exact. Reviewer independence compares families: take the own family only when no other is eligible, record it through `assignmentNote` (`sameFamilyFallback=true`), explicit nominations included; persist the `instance`/`family` refusal codes as `policy`.
48
49
  - Tool publication (`projectToolList`, `projectConductorTools`, `configuredToolContext` in `src/hub/hub-tools.ts`) only narrows the list or adds start targets, using authenticated status metadata (`configuredAdapters`, `startablePeers`, `readOnlyPeers`); in a configured project, malformed or incomplete metadata publishes the read-only set and adds no start target. Publication is never authority: `taskOp` refuses read-only mutations independently, and MCP task and conductor refusals keep their text and set `isError: true`.
50
+ - SDLC metrics and bands (#481 S7): `sdlc-metrics.ts` is pure (history in, numbers out, no clock, no I/O); every metric a task cannot derive is `null`/`unknown`, never 0, and a private task is only counted as withheld. Stale-head re-reviews need both heads on a round (`reviewCost.head` on the done, `reviewFindings.head` on the verdict): never derive them from event order, the board cannot produce an `approved` followed by a `done`. `Tasks.sdlcMetrics` is the one read (planner or person, refused during a private turn) and `Tasks.proposeFromBands` the one writer. It is held like every host writer (`sweepHeld`, `bus.isRecoveryHeld`, `bus.storageError`), keys a breach by its episode (`BandResult.episode`, derived from history so a restart finds the same key), writes nothing while the metric has an open band card, and records a log notice in `band_notices` so it is announced once. Card text is fixed words, numbers and task ids, screened through `signalsFor` and `candidateTextAllowed` like the recurring-finding card; never put task titles, notes or PR text in it, and never open a GitHub issue. Drift honours `min_delta` like every tier. `[bands]` is validated by `bandsConfig` with field-level paths; do not add a tier or a metric without a test that fires it.
49
51
  - Admit a declared extra as owner of `implement`, `bulk_edit` or `test` work only with the current implementer role and `ownerIsolation[id] === true`, the daemon's proof that the live adapter started in `executionRoots[id]` outside the canonical cwd. Never derive it from the declaration's `worktree`, its roles or a reload.
50
52
 
51
- - Lifecycle identity (#694): the hub links `sdlc.repo`/`issue` only through `Tasks.linkIssue` (the one issue `planIssueIdentity` resolves) at plan review, done and the groom backfill, and stages a done's PR through `Tasks.identityLink`/`withLink` (the link carries the repository and the commit or branch it was read for, and is dropped when the done's effective repository or head changed during the read; the issue is re-resolved at apply time; `reviewRequestAdmission` compares the board copy through the same `stage` projection) (`openPrFor`: the one open PR whose head is the done's full commit, or with no commit its branch; a full 100-row page or a malformed row is unknown). Persist a done's link only in the admitted done update or `done (checking)`, never before a gate that can still throw `PreEffectToolRefusal` (#254); reapply it after every re-read in `complete()`. Write a first link with `linkedReceipt`, never `sdlcReceipt` (its relink rule would drop red-first and local-gate records); never relink an existing or foreign identity, never parse agent text for a PR, skip PII tasks before any read, and run the groom backfill after its authority checks with its own bound.
53
+ - Lifecycle identity (#694): the hub links `sdlc.repo`/`issue` only through `Tasks.linkIssue` (the one issue `planIssueIdentity` resolves) at propose and accept (`linkOnWrite`, #723: a local parse, never a read, never a refusal, advise and enforce only), plan review, done and the groom backfill (`backfillIssueLinks`: first in `groomingDigest`, 32 links and 1 s per call, before the slow metadata reads), and stages a done's PR through `Tasks.identityLink`/`withLink` (the link carries the repository and the commit or branch it was read for, and is dropped when the done's effective repository or head changed during the read; the issue is re-resolved at apply time; `reviewRequestAdmission` compares the board copy through the same `stage` projection) (`openPrFor`: the one open PR whose head is the done's full commit, or with no commit its branch; a full 100-row page or a malformed row is unknown). Persist a done's link only in the admitted done update or `done (checking)`, never before a gate that can still throw `PreEffectToolRefusal` (#254); reapply it after every re-read in `complete()`. Write a first link with `linkedReceipt`, never `sdlcReceipt` (its relink rule would drop red-first and local-gate records); never relink an existing or foreign identity, never parse agent text for a PR, skip PII tasks before any read, and run the groom backfill after its authority checks with its own bound.
52
54
  - Spec acceptance (#481 S1) is written only by `Tasks.acceptSpec` for the person; never accept it from a tool or a receipt. The daemon uses `Tasks.acceptReviewed` to bind spec and plan readbacks to one task snapshot before `Tasks.accept`; retain its synchronous missing-spec and plan-admission refusals. Apply WIP and plan admission before `done` implicitly accepts proposed work and before cohort writes. Never read or store an issue body: identity and edit times only.
53
55
  - Test integrity (#481 S4, `src/hub/test-integrity.ts`): in enforce only the person relinks an established `sdlc.repo` or `issue`. A supplied `redFirst` that fails `publicMetadataText` on a public task is refused pre-effect, and `publicView` drops one that fails it later. Integrity is read only for a non-PII task with current PR evidence; otherwise it is unknown with a blocker, never a pass. Enforce adds `test integrity:` blockers to review admission and the merge queue. All gh reads of one lifecycle gate or status share one 20 s deadline; never give each read its own.
54
56
 
@@ -61,7 +63,7 @@ Scope: the task flow, assignment, the state machine and the hub's MCP tools. Rea
61
63
  - Departure consent is written only by `Tasks.acceptPlanDeparture` for the person (`ahub sdlc accept-departure <task> <head> <reason...>`; no peer tool) on a currently known departure at the current PR head, never on `unknown`, and never for a PII task. It binds repo, PR, head, plan revision and fingerprint, the inventory, `planScopeIdentity` and this host's `departureGeneration`, so a restart or a change to any of them invalidates it (`validPlanDepartures`). Status and merge-queue reads never write the board: they only note observations and invalidations in memory (`observedPlans`, `invalidatedPlanDepartures`), which `planObservation()` attaches to the next owner or reviewer transition write. Off captures no observation or revision; a plan or refs change still invalidates an existing acceptance.
62
64
  - The review coverage gate (#520) pins `refs.commit` and the sdlc receipt and rechecks them in `valid()`; an unavailable brief is never an empty one (enforce refuses, advise notes). Read brief attributes and rules at the merge base (`--attr-source`, `check-attr --source`, `git show <base>:`), never from the head or the checkout.
63
65
  - A `routing.reviewOnly` definition is refused as owner inside `assign()`'s eligibility check, for every class, nomination and reservation; keep it there, not at call sites. A definition with `reviewer.unavailable` is refused as reviewer there too (code `policy`); unavailable never means pass.
64
- - A definition's `routing.allowedRoles` is a fixed role ceiling (#543): `assign()` refuses a peer whose required role (reviewer for the reviewer role or the `review` class, planner for `plan`, otherwise implementer) is outside it, with code `policy`, and `loadConductorPolicy` drops project `roles` entries outside it with a config notice. Never widen it from project config.
66
+ - A definition's `routing.allowedRoles` is a fixed role ceiling (#543): `assign()` refuses a peer whose required role (reviewer for the reviewer role or the `review` class, planner for `plan`, otherwise implementer) is outside it, with code `policy`, and `loadConductorPolicy` drops project `roles` entries outside it with a config notice. `planner` also needs `projection.plannerEligible` (#525-1): `loadConductorPolicy` drops it from a kind's entry and `effectiveInstanceRoles` from an instance-id entry. Never widen either from project config.
65
67
  - Reviewer pool (#519): `selectReviewerPool` is pure and called only inside `assign()`, which builds the candidates (declared reviewer-only `routing.reviewOnly` instances) and their blocked reasons; Tasks passes only `reviewPool` (`pool`, `rerotate`, `failed`, `required`). With no fresh reviewer declared and no configured `review.pool`, routing stays the legacy path. The seed is the linked PR number, else the task id; compute its modulo over configured kinds with declared candidates, including blocked/offline/paused/quota/failing candidates, and do not renumber by availability; append undeclared-kind trace entries (`no declared reviewer`) after the declared walk without assigning them modulo shares or changing a selected reason; the implementer's family is skipped while another can run, and a delta review keeps the last `changes_requested` family unless `rerotate`. `required` (an explicit `review.pool`) exhaustion goes to the person with `reviewBlocked`; only the optional default pool may fall back to an eligible planner, other family first. Record the choice through `reviewMetadata` (`reviewerFamily`, `reviewerReason`, including `same-family fallback`), never in note text alone. PII tasks, `review`-class tasks and a reviewer a person named (`reviewer changed`, reason `manual`) stay outside the pool.
66
68
  - `Tasks.retryReviewer` is host-only (the daemon's reviewer `onUnavailable`), never a tool or CLI path. It acts only while the failed peer is still the reviewer of an `in_review`, non-PII, not manually named task, with the same review head, the request's `reviewRequestRevision` and a current child generation; `valid()` rechecks all of it after every await and right before the write, which also passes the flow revision. Each failed peer is tried once per head and round (`reviewerFailure` history). `reviewRequestRevision` counts only role events (`done`, `reviewer changed`, `reviewer unavailable`, ownership), so bookkeeping never fences a request; keep `refs.reviewRevision` and `refs.commit` on the review envelope.
67
69
  - Release trains (#481 S11): change trains only through `Tasks.newRelease`, `configureRelease`, `assignRelease` and `cutRelease` (planner or person), and refuse every `hub_release_*` in a PII turn. `assignRelease` writes only `release` and refuses a frozen, shipped or dropped release on either side; private or pinned scope is person-only. One active or frozen train at a time. A cut freezes only through `Board.freezeRelease` after a fresh `releaseWindow` (host lease from `readReleaseHostAdmission`, quota, `current()`, at most 5 s old); unknown evidence defers, and the window is closed in `finally`. A cut never publishes, tags or upgrades, and upgrade and recovery never consult `releaseWindow`. Keep two of `RELEASE_TRAIN_HISTORY_LIMIT` slots for cut and ship; a configuration no-op appends nothing. An S9 plan receipt survives only contiguous planning revisions that changed exactly `["release"]`.
@@ -73,3 +75,4 @@ Scope: the task flow, assignment, the state machine and the hub's MCP tools. Rea
73
75
  - At done, add `no writes recorded since acceptance` to review only when the daemon proves no owner touches since the first acceptance after the current ownership boundary and an empty acceptance-to-now diff, with no branch, review head or PR evidence. Preserve that baseline and earlier touches across plan-revising re-acceptance. Require a canonical-root D2 result to pass the same elsewhere-hint and exclusive-current-worktree checks before treating it as negative evidence. Bind extra-instance evidence to its current verified execution owner/root; missing, private, disabled, expired-budget or restart-lost baselines stay unknown. Capture through the guarded `TasksDeps.ownerAccepted` callback for explicit accept/self-claim only; an implicit done acceptance has no pre-work baseline and remains unknown. Do not capture private work for this advisory, block done, or accept evidence flags from models.
74
76
 
75
77
  - `ahub report --dogfood` (#687, `src/hub/dogfood.ts`, `src/hub/dogfood-read.ts`) opens `hub.db` read-only with byte and row caps; an unavailable or incomplete source reports `null`, never 0, and per-PR GitHub reads run only when the task privacy inventory is complete.
78
+ - Qualified titles (#723): `planIssueIdentity` links a sibling `repo#N` only when `refs.repo` names that repository (`siblingTitleRefs`, `spec-read.ts`), and an `owner/repo#N` only when it is `refs.repo`, else this project's repository; a sibling shorthand beside a bare `#N` is two identities and links neither. A title that names another repository with no `refs.repo` does not link, and `refsRepoHint` tells the proposer to set it (advice in the `hub_task_propose` result, never a refusal; none in mode off, for a PII task or once `refs.repo` or an issue is set). There is no retitle path on the board: a title is fixed at propose.
@@ -16,7 +16,7 @@ Scope: tests, fakes and the test gate. Read before adding or changing a test or
16
16
  - A bounded polling wait must assert its condition after the last attempt, or document why giving up is intentional. Append-only event histories wait for a correlated closing event, not removal of the opening event. Before changing a slow wait, identify the unreached state and repair its cause (#296).
17
17
  - A test of turn or notice behavior that is not about the snapshot budget yields an I/O turn (`await new Promise(r => setImmediate(r))`) between captures: captures in one synchronous burst share the 250 ms budget and may correctly record unknown evidence. A daemon test whose assertion needs a turn's snapshot or end evidence passes `snapshotClock: () => 0` (`DaemonOptions.snapshotClock`, #661) so a slow runner cannot spend that budget; never lengthen the budget instead. A wait on concurrent-edit evidence correlates the expected turn ids and paths (#617).
18
18
  - A request that a test expects a stop or shutdown to cut gets its rejection handler where the request is created, never after the awaited teardown: a late `catch` races the reset and the run fails on an unhandled rejection.
19
- - Run `bun scripts/seeded-check.ts` before reporting the seeded-guard gate complete. Keep the `scripts/seeds.json` cases sequential; require the same named test green before its seeded assertion fails. Seed rot, survivors, setup/compiler failures, timeout/watchdog termination and process leaks fail the gate. A change that moves or rewrites a line a `scripts/seeds.json` case searches for updates that case's search and replacement in the same PR (seed rot fails the gate), keeping its mutant effect and named test. Never run the full seed runner from a unit test or mutate the source checkout; inspect preserved failed fixtures before removing them. CI runs it as four sequential Linux shards (`--shard i/4`) beside the ordinary check, separately from `scripts/check.sh`; the required `seeded guards` job passes only when every shard passed, or when the decision job reused a verified identical-tree full gate (`admission == 'reuse'`). Keep that job name: branch protection and `scripts/ci-gates.mjs` read it (#457).
19
+ - Run `bun scripts/seeded-check.ts` before reporting the seeded-guard gate complete. After changing seeded guard logic, run its affected seeds even when search-anchor counts are unchanged. Put asynchronous calls inside `resolves`/`rejects` expectations so a mutated refusal fails a named assertion. Keep the `scripts/seeds.json` cases sequential; require the same named test green before its seeded assertion fails. Seed rot, survivors, setup/compiler failures, timeout/watchdog termination and process leaks fail the gate. A change that moves or rewrites a line a `scripts/seeds.json` case searches for updates that case's search and replacement in the same PR (seed rot fails the gate), keeping its mutant effect and named test. Never run the full seed runner from a unit test or mutate the source checkout; inspect preserved failed fixtures before removing them. CI runs it as four sequential Linux shards (`--shard i/4`) beside the ordinary check, separately from `scripts/check.sh`; the required `seeded guards` job passes only when every shard passed, or when the decision job reused a verified identical-tree full gate (`admission == 'reuse'`). Keep that job name: branch protection and `scripts/ci-gates.mjs` read it (#457).
20
20
 
21
21
  - `scripts/check.sh` pins the test environment (#610): `GIT_CONFIG_GLOBAL=/dev/null`, `NO_COLOR` unset, `TERM=xterm-256color`, temp root under the OS per-user temp dir, agent markers cleared. A test that needs a person's git config or color settings sets them itself. `--changed` selects tests with `scripts/changed-tests.mjs`: changed tests (nested suites included) plus every test that names a changed file directly; docs need none; any other changed file no test names, and any gate, package or shared-fixture change, runs everything.
22
22
  - `scripts/check.sh` holds the repo's heavy-job slot (`<git common dir>/ahub-heavy.lock`, #398) for the whole run, so a test never runs the real gate; `test/check-lock.test.ts` runs a copy in a scratch repo.
package/docs/dogfood.md CHANGED
@@ -11,9 +11,11 @@ agent-hub is developed with agent-hub. This page lists every feature, whether it
11
11
  | Feature | State here | Measured by |
12
12
  | --- | --- | --- |
13
13
  | Message bus, digests, priorities | on | `ahub report`: messages, digests, overflow, undeliverable; delivery latency from events |
14
- | Task board, routing, roles | on (claude planner and reviewer; codex, jcode, agy implementers) | board history: lead time from propose to approve, review rounds, escalations |
14
+ | Compact board (#724) | on | `ahub board` at 80, 120 and 200 columns: one line per task (test/board-compact.test.ts) |
15
+ | Task board, routing, roles | on (claude planner, reviewer and implementer; codex, jcode, agy implementers) | board history: lead time from propose to approve, review rounds, escalations |
16
+ | Roles per kind and form (#525-1) | on: claude holds the implementer role in the project checkout; Claude implementer instances in their own worktree are off (none declared here) | tasks claude implements (`ahub status` flow roles, board owner history); refusals naming `this kind cannot plan` or `needs its own worktree` |
15
17
  | Overlap notices | on; plan symbols match by normalized name (arguments stripped, a bare name matches the last segment, two qualified names must be equal; #682 phase 1b) | `ahub report`: overlap warnings, task pairs; false-overlap rate, and symbol-only matches on bare common names (#682) |
16
- | Planner WIP caps and backlog pull (#655) | on (`tasks.wip_limits.codex = 2`) | held-ready entries, offers refused for capacity |
18
+ | Planner WIP caps and backlog pull (#655, #718) | on (`tasks.wip_limits.codex = 2`); reserved-pull handoff on after release | board history/offer readback: reserved `backlog pulled` -> `assigned` -> `accepted` for the same peer; stale or unowned offers, capacity refusals and held-ready entries |
17
19
  | Task sweep (stalls, idle owners) | on | stall and sweep events per week |
18
20
  | Turn-free coordination | off: experimental; needs a separate measured trial | coordination events, conflict notices |
19
21
  | Pull-only peers (agy) | on; `hub_inbox` drains to 64 KiB (#713) | unread offers, empty-inbox polling (#681); queued count and oldest queued age per pull-only peer in `ahub status`; reads that said `N more waiting` |
@@ -28,16 +30,17 @@ agent-hub is developed with agent-hub. This page lists every feature, whether it
28
30
  | Console changed-symbols block (#684 phase 2b) | on with 2a data; matching, unplanned and untouched against the plan; overlap lines follow in #379; related-note count remains phase 4 | task detail shows the block or `unknown (no code facts)`; no block for a private task |
29
31
  | Console review guidance (#711) | on after release; `r` verdict choice, hints, visible result, agent-shell cause line, notice names the console | a person approving from the console without a failed first try; refusals that named the cause; `ahub review` calls from agent shells that were refused |
30
32
  | Console symbol-overlap advice (#684, #379) | on after release; read-only task detail, observed names only | other task IDs/shared paths and screened different-symbol verdict; no pair involving PII, no claim on unknown/incomplete evidence |
33
+ | Dashboard task sheet Code group (#684 phase 2c) | on after release; read-only, observed names only, none for a private task | task sheet Code group shows the changed symbols or `unknown (no code facts)`; symbol clicks that reach a non-empty graph once phase 3 lands |
31
34
  | Review brief, review findings, Important gate (#481 S3) | advise | Important findings per PR, rounds to approval |
32
35
  | Spec acceptance (#481 S1) | advise | build entries without acceptance |
33
- | Lifecycle identity linked by the hub (#694) | on in advise | tasks still showing `issue identity missing` or `PR identity missing` at review |
36
+ | Lifecycle identity linked by the hub (#694, #723) | on in advise; links at propose and accept from the title and `refs.repo`, with a groom backfill as the safety net | open tasks still showing `issue identity missing` right after propose (should be only titles that name no issue or a qualified title without `refs.repo`) |
34
37
  | Planner plan review (#481 S9) | advise | plans reviewed before build |
35
38
  | Plan conformance (#481 S2) | advise | PRs with files outside the plan |
36
39
  | Test integrity for bug fixes (#481 S4) | advise | bug PRs without a red-first receipt, modified pre-existing tests |
37
40
  | Required checks | advise (`check (ubuntu-latest)`, `check (macos-latest)`, `seeded guards`) | first-pass CI rate, same-head reruns (`hub_sdlc_costs`) |
38
41
  | Release authorization (#481 S6) | pending (#199) | releases with a receipt |
39
- | SDLC metrics and control bands (#481 S7) | pending (#200) | its own report |
40
- | Fresh headless reviewers (#223, #519) | off: needs an `instances` entry in the person's `config.local.json` | reviews by fresh sessions, findings they add |
42
+ | SDLC metrics and control bands (#481 S7) | `ahub sdlc metrics` on; bands off until `[bands]` is set in `.agenthub/sdlc.toml`, and they write cards only in advise or enforce (in off the report still shows tiers). Completion checks are off here, so `first_pass_check` and the PR-based figures read unknown | its own report; breach episodes per week by tier, and band cards the planner accepted rather than dropped |
43
+ | Fresh headless reviewers (#223, #519) | off: needs an `instances` entry in the person's `config.local.json` | reviews by fresh sessions, findings they add; a per-review start failure names a closed cause class (#722): `ahub status` reviewer line and hub.log `[cause: ...]`, with the share of failures that are `unknown` |
41
44
  | Completion checks per class | off: the implementer already runs `scripts/check.sh`; a hub-run copy would double the heavy-slot use | time to verdict when on |
42
45
 
43
46
  ## Memory and knowledge
@@ -53,7 +56,7 @@ agent-hub is developed with agent-hub. This page lists every feature, whether it
53
56
  | Feature | State here | Measured by |
54
57
  | --- | --- | --- |
55
58
  | Quota budgets and pauses | on | pauses, resets, time paused (#683) |
56
- | Token usage per peer and task | `ahub report` and, from the roll-up in `hub.db`, `ahub usage [--by peer\|task\|issue\|pr\|release\|day\|model]`, `usage limits` and `usage total` (#683 U2); console and dashboard views pending (U3, U4) | tokens per merged PR |
59
+ | Token usage per peer and task | `ahub report` and, from the roll-up in `hub.db`, `ahub usage [--by peer\|task\|issue\|pr\|release\|day\|model]`, `usage limits` and `usage total` (#683 U2); console tab `7` (U3) and dashboard Usage view (U4) shipped | tokens per merged PR |
57
60
  | Context gate | off: `context.gate = 0` | context-pressure events |
58
61
  | Always-ask action classes (#636) | foundation only; enforcement pending (#312, #313) | classed calls asked, budget refusals |
59
62
  | Permission modes | on, person-chosen | approval prompts and their wait times |
@@ -67,7 +70,7 @@ agent-hub is developed with agent-hub. This page lists every feature, whether it
67
70
  | --- | --- | --- |
68
71
  | Dogfood scorecard (#687) | on after release; read-only weekly receipt | `ahub report --dogfood --since 7d --json`; bounded evidence and explicit unknowns |
69
72
  | Console and dashboard | on | control-request latency, connection timeouts in `hub.log` |
70
- | Usage view (#683) | CLI (`ahub usage`, U2) and console Usage tab `7` (U3) shipped; dashboard panel pending (U4) | its own panels |
73
+ | Usage view (#683) | CLI (`ahub usage`, U2), console Usage tab `7` (U3) and dashboard Usage view (U4) shipped | its own panels |
71
74
  | Settings reload | on | reloads applied, pending restarts |
72
75
  | Config editing from the console and dashboard (#689) | pending; hand edits of `config.local.json` meanwhile | edits applied, refused by validation, pending restarts |
73
76
  | Upgrade and recovery | on, per release | upgrade receipts in `docs/verification/` |
@@ -150,6 +150,15 @@ per peer (`#id [pii]` for a private task, never a title). The panel asks the hub
150
150
  `usage unavailable`. The numbers come from the roll-up of `ahub usage`, so the console and the CLI agree. A request folds the event log first, within 250 ms;
151
151
  when a very large log is not folded yet the answer carries `behind: true`, the panel says `still catching up` and `ahub usage` prints a warning on stderr, so a partial number is never shown as the whole.
152
152
 
153
+ The dashboard's Usage view (#683 U4) sits in the Agents view, under Usage budgets, and shows the same roll-up: the lifetime, without-cache and cache-read totals with
154
+ the peers whose figure is unknown, the last 14 UTC days as stacked columns per peer (a day with no record is a `no record` column, never zero), a limits table with the
155
+ open pauses and the last 20 pause, resume and reset events, and a task table (state, owner, PR, input, output, cache read, cache write, total). Every figure names its
156
+ source like the console does and `unknown` stays the word. A private task shows `#id [pii]` and never its title. The page asks for the figures only while the Agents view is open
157
+ (`view: "agents"` in the snapshot request), so a hidden view costs no roll-up work on the 1 s poll. When the roll-up is still behind a very large log the snapshot carries
158
+ `usage.behind: true` and the view says the figures are a lower bound until it catches up; a roll-up that cannot be read shows `unavailable`, never the last figures. When there
159
+ are more than 20 limit events the table says how many older ones it leaves out and that `ahub usage limits` lists them. Colours are design tokens only, the view reflows to one column at 600 px, and its
160
+ tables and chart scroll inside themselves.
161
+
153
162
  Reviewing from the console (#711): on a task in review in Tasks (a selected row, or the open task detail), `r` asks which verdict first on the prompt line,
154
163
  `review #372 "<title>": a approve c request changes Esc cancel` (the title is quoted and cut, a private task shows `[pii]`). `a` puts `review 372 approved ` on
155
164
  the input line with a guide line above it (`Enter approves #372. You can type a note first. Esc cancels.`); `c` puts `review 372 changes_requested ` there and
@@ -961,6 +970,17 @@ ahub board
961
970
  ahub task show <task-id>
962
971
  ```
963
972
 
973
+ At a terminal width `ahub board` prints one line per task (#724): TITLE is cut
974
+ with `...`, dependencies are an AFTER column (`#113,+2`), and STAGE is the
975
+ progress meter with the stage and a short next-step code (`intent: issue?`,
976
+ `review: CI?`, `ready`), explained by a legend line. A column that is `-` on
977
+ every row is left out. When TITLE would get fewer than 30 columns (or fewer
978
+ than its longest title), REVIEWER, CLASS and AGE give way in that order, then
979
+ STAGE drops the stage name; at a width still too narrow AFTER goes and OWNER is
980
+ cut.
981
+ `ahub board --full`, `ahub task show <id>` and piped output without `COLUMNS`
982
+ keep the whole title and the full SDLC advice.
983
+
964
984
  The owner accepts with `hub_task_accept`, makes the change, and reports with
965
985
  `hub_task_done` including a summary and refs. The reviewer receives a review
966
986
  request and responds with `hub_review`. A console review uses:
@@ -1037,6 +1057,8 @@ brief's own (a pair of qualified names must be equal, otherwise the last segment
1037
1057
  an incomplete observation says an untouched symbol may have changed; a private task shows nothing. Names are quoted like every agent-written string, at most 40 are
1038
1058
  listed and the rest are counted. The block is advice: deleted definitions are not reported, so it never proves a plan was or was not followed.
1039
1059
 
1060
+ The dashboard's task sheet has the same block as a "Code" group (#684 phase 2c): the changed symbols as buttons marked `matching` or `unplanned` against the plan, the plan names nothing changed under as `untouched`, and the same advisory line (deleted symbols are not reported, an incomplete observation may hide a changed one). The hub classifies them (`codeGroup` on each task of the UI snapshot, from the same public observation the console reads), so the page never matches names itself. A symbol opens the Memory tab with its canonical node (`symbol:<name>@<path>`) as the graph start; the list is capped at 40 with the count of what is left out, and a summary line counts matching, unplanned and untouched before the cap. A plan name the hub withholds (screened out or malformed) is counted, and the line then says a symbol marked unplanned may be in the plan. Without code facts the group reads `unknown (no code facts)`, and a private task has no group at all, so the sheet shows nothing for it.
1061
+
1040
1062
  Task detail also shows an `overlaps` block (#379), using the hub's read-only
1041
1063
  `symbolOverlaps` projection of its existing open-task/other-owner overlap selection.
1042
1064
  Each row names the other task ID and screened shared paths. A row says "same file,
@@ -1212,7 +1234,7 @@ and its end `budget_resume`, with the window, the reset time and who ended it (`
1212
1234
  `ahub budget resume`). A window's reset writes `quota_reset` when its `resetsAt` moves forward or its `used`
1213
1235
  falls by 0.5 or more inside one window id, once per peer, window and `resetsAt`. These events hold ids and
1214
1236
  numbers only, add no protocol version and are ignored by a reader that does not know them. `ahub report`
1215
- does not show them: `ahub usage` and `ahub usage limits` read them (below), and the console's Usage tab shows each peer's last resets (#683 U3). A dashboard usage view is pending (U4).
1237
+ does not show them: `ahub usage` and `ahub usage limits` read them (below), and the console's Usage tab shows each peer's last resets (#683 U3). A dashboard Usage view shows the same roll-up in the Agents view (#683 U4, see [Operator console and panels](#operator-console-and-panels)).
1216
1238
 
1217
1239
  `ahub usage` reads the same events from an incremental roll-up in `hub.db` instead of rescanning the log (#683):
1218
1240
 
@@ -3509,7 +3531,7 @@ that the file is untracked; a dropped value is reported by the settings result.
3509
3531
 
3510
3532
  ## SDLC artifact receipts and gates
3511
3533
 
3512
- The playbook lifecycle (#257, #481) is off without `.agenthub/sdlc.toml`. When enabled its stages are intent, spec, plan, build, test, review, merge, release and maintain, grouped as Plan, Design, Build, Test, Deploy and Maintain. Advise shows blockers; enforce checks implemented head-bound review and approval gates. Stored implement/verify receipts read as build/test; new enabled receipts use canonical ids. See [the SDLC contract](sdlc.md) for gates, required check names, local receipts, console-only readbacks and the S0-S8 delivery table. A person accepts a spec issue with `ahub sdlc accept <task> --issue <n>` (S1). The planner records plan reviews with review and CI scope (`hub_plan_review`, `ahub sdlc plan-review`) and grooms with `ahub backlog groom`; `ahub backlog return <task> <revision> <reason>` returns accepted, untouched work (S9). `ahub sdlc costs [owner/repo]` reports review rounds and CI runs of merged PRs without writing the board (S10). S11 release trains ship in 0.12.37 (see [release scheduling](#planner-release-scheduling)). S3 structured review findings (an open Important finding blocks approval in enforce; `max_nits` in `sdlc.toml` caps nits) and S2 literal-path plan conformance with the person-only `ahub sdlc accept-departure <task> <head> <reason...>` are in source; see [findings](sdlc.md#structured-review-findings-481-s3) and [plan conformance](sdlc.md#plan-conformance-481-s2). S4-S7 and a full live advise/release cycle remain pending. Owners/reviewers record bounded identities through `hub_sdlc_record`; these grant no additional authority. Off output, receipt and schema shapes stay unchanged.
3534
+ The playbook lifecycle (#257, #481) is off without `.agenthub/sdlc.toml`. When enabled its stages are intent, spec, plan, build, test, review, merge, release and maintain, grouped as Plan, Design, Build, Test, Deploy and Maintain. Advise shows blockers; enforce checks implemented head-bound review and approval gates. Stored implement/verify receipts read as build/test; new enabled receipts use canonical ids. See [the SDLC contract](sdlc.md) for gates, required check names, local receipts, console-only readbacks and the S0-S8 delivery table. A person accepts a spec issue with `ahub sdlc accept <task> --issue <n>` (S1). The planner records plan reviews with review and CI scope (`hub_plan_review`, `ahub sdlc plan-review`) and grooms with `ahub backlog groom`; `ahub backlog return <task> <revision> <reason>` returns accepted, untouched work (S9). `ahub sdlc costs [owner/repo]` reports review rounds and CI runs of merged PRs without writing the board (S10). `ahub sdlc metrics [--since <YYYY-MM-DD|ISO time>]` (the planner tool `hub_sdlc_metrics`, planner or person, read-only) reports lead times, first-pass approval and check rates, rework rounds, stale-head re-reviews and open stalls from board history, with `unknown` counted and never 0; optional `[bands.<metric>]` in `sdlc.toml` add control bands that the daemon checks every five minutes in advise or enforce, writing a hub notice (`log`), a read-only `triage` backlog task (`diagnose`) or a `plan` backlog card (`propose`), one per breach episode and never a GitHub issue (S7, see [metrics](sdlc.md#sdlc-metrics-and-control-bands-481-s7)). With the lifecycle on, a task's issue identity is linked when it is proposed or accepted, from the one issue it names (an imported GitHub issue, or a `#N`, `owner/repo#N` or issue URL in its title); a title that names a sibling repository (`agent-mem#13`) links only when `refs.repo` names it, and without `refs.repo` the propose result says to set it. `ahub backlog groom` also backfills up to 32 links per call, within 1 s (#723). S11 release trains ship in 0.12.37 (see [release scheduling](#planner-release-scheduling)). S3 structured review findings (an open Important finding blocks approval in enforce; `max_nits` in `sdlc.toml` caps nits) and S2 literal-path plan conformance with the person-only `ahub sdlc accept-departure <task> <head> <reason...>` are in source; see [findings](sdlc.md#structured-review-findings-481-s3) and [plan conformance](sdlc.md#plan-conformance-481-s2). S4 [test integrity](sdlc.md#test-integrity-481-s4) and S6 [release authorization](#release-approval-and-optional-tag-guard) are in source too. S5 continuous evals, release metrics and bands, and a full live advise/release cycle remain pending. Owners/reviewers record bounded identities through `hub_sdlc_record`; these grant no additional authority. Off output, receipt and schema shapes stay unchanged.
3513
3535
  ## Optional Qwen Code peer
3514
3536
 
3515
3537
  Select Qwen per project with `ahub roster add qwen`; it defaults off and is absent
@@ -3784,6 +3806,14 @@ unapproved dependencies, frozen scope and overriding person P0 order/reservation
3784
3806
  The person uses `ahub backlog pull-ready <id> <revision> <reason>` or Pull within
3785
3807
  WIP on a card. Explicit manual pull/assignment retain their existing authority.
3786
3808
 
3809
+ A pull of a reserved backlog task (#718) assigns it to exactly that reserved peer
3810
+ in the same write and sends it a fresh owner offer, so it can accept at once. A
3811
+ reserved peer that cannot take it (offline, paused, excluded, at its WIP limit)
3812
+ refuses the pull before any change, naming the peer and its reason; it is not
3813
+ passed over to another peer. `ahub route explain` previews the same reserved-only
3814
+ choice. An unreserved pull keeps reserving a slot as above. A reserved offer that
3815
+ was never started can return untouched and keeps its reservation.
3816
+
3787
3817
  Capacity defaults to one per peer. Each declared instance has its own `tasks.wip_limits.<instance>` key. `ahub settings set tasks.wip_limits.codex 2`
3788
3818
  uses the existing machine-local registry path; integers 0..32 apply live (the next
3789
3819
  admission reads the current value), with 0 disabling planner admission for that
@@ -4099,11 +4129,13 @@ run yourself in this project outside the hub has no peer id: its hub tools refus
4099
4129
  every call with that reason and never default to `jcode`, and a process that only
4100
4130
  sets `AGENTHUB_PEER_ID` without an owned launch is refused by the hub.
4101
4131
 
4102
- An extra Claude instance is a reviewer or
4103
- planner you start yourself: declare it with `roles` only (for example
4104
- `"claude-review": { "kind": "claude", "roles": ["reviewer"] }`, no `start_mode` or
4105
- `worktree`) and run `ahub claude --as claude-review` in a terminal. It keeps its own
4106
- `claude-*.claude-review.json` state files.
4132
+ An extra Claude instance is one you start yourself: declare it with `roles` (for example
4133
+ `"claude-review": { "kind": "claude", "roles": ["reviewer"] }`) and run `ahub claude --as claude-review` in a
4134
+ terminal. It keeps its own `claude-*.claude-review.json` state files. A planner or reviewer instance reads the
4135
+ project checkout and refuses `worktree`. From #525-1 an `implementer` or `verifier` instance is accepted and
4136
+ requires its own `worktree` (for example `"claude-impl": { "kind": "claude", "roles": ["implementer"],
4137
+ "worktree": ".worktrees/claude-impl" }`). The hub does not route code tasks to it until #525-5 proves its execution root (`ahub route explain` shows `owner admission: declared write owner requires a verified isolated execution root` for implement, test and bulk_edit tasks). A headless Claude instance stays a reviewer (`roles`
4138
+ exactly `["reviewer"]`); a headless implementer is refused until #525-4.
4107
4139
 
4108
4140
  A headless Claude reviewer is run by the hub instead (#223): declare
4109
4141
  `"claude-review": { "kind": "claude", "roles": ["reviewer"], "start_mode": "headless" }`
@@ -4135,7 +4167,7 @@ From 0.12.37 reviews can rotate across fresh headless Claude and Codex reviewers
4135
4167
  }
4136
4168
  ```
4137
4169
 
4138
- Start them with `ahub claude --as claude-review`, `ahub codex --as codex-review` and `ahub kimi --as kimi-review`; stop with `ahub stop <id>`. Fresh Claude and Codex variants share the project checkout; the Kimi variant runs in a clean temporary working directory and reviews only the screened brief and host facts. All fresh variants refuse a custom worktree or permission mode. Codex and Kimi require `review_mode: "fresh"`; without it, an existing headless instance with the reviewer role keeps its native app-server/ACP behavior, worktree and permission options and does not join this pool. Claude retains its #223 automatic headless-reviewer declaration. The agy reviewer slot remains automatically unavailable (`agy is an implementer peer (#485)`). A failed or unsupported fresh start never downgrades to a native continuing session. Ordinary implementer instances retain their own adapters. The default logical pool order is Claude, Codex, Kimi. Only configured kinds with at least one declared reviewer count as slots, including declared ones that are offline, paused, quota-limited or failing, so availability never renumbers them; configured kinds with no declaration are listed after the walk as `no declared reviewer`. `review.rerotate` defaults to false. Pool selection starts at PR number modulo the number of declared kind slots, or task id when there is no PR, then skips the implementer's family and unavailable candidates. Delta review prefers the preceding eligible independent family in a fresh child. A preceding same-family fallback does not outrank a newly available independent family. `ahub route explain` shows the same pure walk.
4170
+ Start them with `ahub claude --as claude-review`, `ahub codex --as codex-review` and `ahub kimi --as kimi-review`; stop with `ahub stop <id>`. For the Claude and Codex reviewers the command asks the running hub to start the reviewer and returns: nothing runs in that terminal, it takes no launch arguments, and `--print-command` or `--dry-run` prints a hub-start note instead of a launch (#720). Fresh Claude and Codex variants share the project checkout; the Kimi variant runs in a clean temporary working directory and reviews only the screened brief and host facts. All fresh variants refuse a custom worktree or permission mode. Codex and Kimi require `review_mode: "fresh"`; without it, an existing headless instance with the reviewer role keeps its native app-server/ACP behavior, worktree and permission options and does not join this pool. Claude retains its #223 automatic headless-reviewer declaration. The agy reviewer slot remains automatically unavailable (`agy is an implementer peer (#485)`). A failed or unsupported fresh start never downgrades to a native continuing session. Ordinary implementer instances retain their own adapters. The default logical pool order is Claude, Codex, Kimi. Only configured kinds with at least one declared reviewer count as slots, including declared ones that are offline, paused, quota-limited or failing, so availability never renumbers them; configured kinds with no declaration are listed after the walk as `no declared reviewer`. `review.rerotate` defaults to false. Pool selection starts at PR number modulo the number of declared kind slots, or task id when there is no PR, then skips the implementer's family and unavailable candidates. Delta review prefers the preceding eligible independent family in a fresh child. A preceding same-family fallback does not outrank a newly available independent family. `ahub route explain` shows the same pure walk.
4139
4171
 
4140
4172
  Codex requires a verified `codex-cli` (0.146.0 or 0.163.0) or one the person trusts on this machine, and a passing bounded runtime receipt before its model child starts. Both verified versions passed the [2026-10-22 model-free production admission](verification/2026-10-22-codex-reviewer-isolation.md). A refusal names the installed and verified versions. Either set `codex_bin` to a verified executable, or run `ahub agents trust codex-review <version>`: trust lifts only the version pin, and every proof below still runs before each child (#691). The producer checks native binary/config stability, configured MCP inventory with only the two hub tools, effective Apps/plugins/hooks disablement, an explicit empty legacy `notify` command and a named filesystem profile. Apps needs separate feature readback because configured inventory omits runtime connectors. Web search is requested disabled; its effective turn mode is not read back and managed requirements can select Cached (see the verification ceiling). Each launch proves public-read, exact/glob/symlink private-denial, standard host credential denial and write-denial in temporary fixtures. Standard credential and configured tool-home/XDG subtrees are denied independently of project `local.deny`. Its sandboxed shell is filesystem read-only with private paths denied and network configured disabled; it is not Claude's Read/Grep/Glob-only tool whitelist. No credential home is copied.
4141
4173
 
@@ -4145,7 +4177,7 @@ A dangling or unreadable standard/configured credential root makes the Codex rev
4145
4177
 
4146
4178
  agy is not a reviewer (person decision, 2026-10-19): it is an optional implementer peer (see [Optional Antigravity peer](#optional-antigravity-agy-peer)), and its retained reviewer slot is outside the default pool and always unavailable with `agy is an implementer peer (#485)`. A manual pool accepts only registered reviewer kinds (Claude, Codex, Kimi, agy), so local/Pi are refused. An explicit pool without declared candidates blocks for person action; an omitted pool retains legacy routing without declarations and falls back to an eligible planner when fresh candidates are exhausted. The exhausted pool walk remains recorded; without an eligible planner, the person reviews. A manual pool may name agy, or a Kimi reviewer whose preparation fails; the reason appears as an unavailable skip, and they never run or count as approval. Do not substitute plan mode for isolation.
4147
4179
 
4148
- A failed start or any child ending without a saved verdict advances to the remaining eligible candidates; no quota meaning is guessed from text. A same-family fallback is a distinct fresh reviewer session. Exhaustion records `blocked: no reviewer can run`; with no explicit pool the eligible planner fallback is retained, while an explicit pool or absent eligible planner leaves person action. It never auto-approves. A per-review isolation preparation failure excludes that peer from later tasks until a verified restart. Recovery targets must advertise `reviewer_pool` when a pool is explicitly configured or fresh reviewer instances are declared. Verdict history retains `reviewContext` and adds `reviewerFamily` and `reviewerReason` from the host.
4180
+ A failed start or any child ending without a saved verdict advances to the remaining eligible candidates; no quota meaning is guessed from text. A same-family fallback is a distinct fresh reviewer session. Exhaustion records `blocked: no reviewer can run`; with no explicit pool the eligible planner fallback is retained, while an explicit pool or absent eligible planner leaves person action. It never auto-approves. A per-review isolation preparation failure excludes that peer from later tasks until a verified restart. Its reason names one cause from a closed list (`version`, `binary`, `mcp_inventory`, `plugins_hooks_apps`, `sandbox_proof`, `credential_root`, `timeout`, `unknown`) with a fixed hint, on the peer's `reviewer` line in `ahub status` and in a `hub.log` line, and never a path, native CLI text or token (#722). Only the Codex preparation classifies its failures; other reviewers report `unknown`. Recovery targets must advertise `reviewer_pool` when a pool is explicitly configured or fresh reviewer instances are declared. Verdict history retains `reviewContext` and adds `reviewerFamily` and `reviewerReason` from the host.
4149
4181
 
4150
4182
  Fake-CLI tests and CI cover these host contracts. [Offline isolation evidence](verification/2026-10-19-reviewer-isolation.md) and fake-CLI tests do not prove an account/model review or observed network denial. Native acceptance remains a separate [manual smoke leg](smoke.md#reviewer-pool-native-legs-519-2026-10-19); existing #223 Claude behavior has no new live proof here. Kimi's authenticated fresh-verdict receipts, and its still-open interactive-TUI coexistence and usage checks, are tracked in [Kimi smoke legs](smoke.md#kimi-reviewer-and-task-bound-mcp-533).
4151
4183
 
@@ -4192,7 +4224,7 @@ For `gh release create` and `npm publish`, use the exact literal command after `
4192
4224
 
4193
4225
  Use `ahub sdlc train report <release-id>` for scope readiness and recorded release metrics. The planner creates future release rows, activates one train, and assigns tasks through the release-only hub tools. A due date requests a cut review; it never clears unfinished work. Move missed tasks to the next row with a reason, then use `ahub sdlc train cut <release-id> <revision>` from the person's terminal or `hub_release_cut` as the planner. The cut retains a short host admission lease through freeze and defers on held gate slots or unknown/quota-limited peer windows with a real quota source. Accounts without a quota reader (Kimi included when `kimi_tokens_5h` is zero) report `no quota source`; manual and budget-record pauses still defer. It does not run release preparation or publish anything. Schedule bundle/check/docs work under separate heavy admission and retain the person's native verification, authorization and upgrade window. A hotfix records its enumerated cause and keeps every gate.
4194
4226
 
4195
- The per-release "gates run" record is deferred to S6/S7. Person ruling 2026-10-19 supersedes the earlier S11 upgrade-admission ruling: upgrades use pre-S11 recovery readiness (no busy peers, pending approvals resolved, completion checks settled) and retain the existing operation checks. S11 adds no host-admission, quota or all-idle requirement to upgrade. An active/frozen configured train still requires recovery-target `release_trains` compatibility. Release-cut admission remains unchanged: follow its refusal's applicable exit by stopping or resuming the paused peer, adjusting `ahub budget set`, or using `ahub release drop`. Missing/shadowed `dot` requires the resource controller or a PATH correction for the cut; nested admission requires retrying the cut outside the outer lease.
4227
+ The per-release "gates run" record is not built: S7 shipped task metrics and bands without it, and release metrics with the gates-run record are the next slice. Person ruling 2026-10-19 supersedes the earlier S11 upgrade-admission ruling: upgrades use pre-S11 recovery readiness (no busy peers, pending approvals resolved, completion checks settled) and retain the existing operation checks. S11 adds no host-admission, quota or all-idle requirement to upgrade. An active/frozen configured train still requires recovery-target `release_trains` compatibility. Release-cut admission remains unchanged: follow its refusal's applicable exit by stopping or resuming the paused peer, adjusting `ahub budget set`, or using `ahub release drop`. Missing/shadowed `dot` requires the resource controller or a PATH correction for the cut; nested admission requires retrying the cut outside the outer lease.
4196
4228
 
4197
4229
 
4198
4230
  ## Codex cancellation identity notice
@@ -4202,10 +4234,10 @@ could not identify a cancelled native turn. Reattach the native session before
4202
4234
  further hub delivery. A late completion keeps this hold; verified native
4203
4235
  reattachment or TUI detachment clears it. The notice contains fixed guidance, without native ids.
4204
4236
 
4205
- ## Roles per SDLC stage (#525, planned)
4237
+ ## Roles per SDLC stage (#525)
4206
4238
 
4207
- The design for planner, implementer and reviewer instances per stage is in the spec section "Amendment: roles per SDLC stage (#525)". Nothing in it is available yet. Each implementation slice (525-1 to 525-6) adds its operator steps here when it ships. Until then, the instance rules in "Additional headless peer instances" above are current. The planned changes an operator will see:
4239
+ The design for planner, implementer and reviewer instances per stage is in the spec section "Amendment: roles per SDLC stage (#525)". Slice 525-1 is available: role capability per kind and form, Claude channel instances as implementer or verifier in their own worktree, and `planner` refused on kinds that cannot plan (pi, local, agy and configured ACP agents): an instance declaring it is refused with `this kind cannot plan`, and a `roles` entry keeps its other roles with the notice `roles.<peer>: ignored roles outside the adapter ceiling`. The operator steps for 525-1 are in "Additional headless peer instances" above; each later slice (525-2 to 525-6) adds its own here when it ships, and until then those instance rules are current. The planned changes an operator will see:
4208
4240
 
4209
- - A Claude instance with `["implementer"]` and a `worktree`, as a terminal instance (started with `ahub claude --as <id>`) or as `start_mode: "headless"` (`permission_mode` `ask` or `ask-when-needed`; `never-ask` refused).
4241
+ - Code tasks routed to a terminal Claude implementer instance once its execution root is proven (525-5), and a headless Claude implementer with `start_mode: "headless"` (`permission_mode` `ask` or `ask-when-needed`; `never-ask` refused) (525-4).
4210
4242
  - The `planner` setting accepts a list of peers or instance ids; each task records its own planner, and only that planner or the person runs planner tools on it.
4211
4243
  - A reviewer is never a peer that implemented the task, and a plan's author does not record its own plan review while another planner is available.
@@ -17,7 +17,7 @@ ahub setup # installs the Claude Code channel plu
17
17
  Or use the matching GitHub release:
18
18
 
19
19
  ```bash
20
- bun add -g github:STAIxBWLB/agent-hub#v0.13.0
20
+ bun add -g github:STAIxBWLB/agent-hub#v0.14.0
21
21
  ahub setup
22
22
  ```
23
23
 
@@ -30,6 +30,10 @@ and upgrade or crash recovery, see the [operations guide](operations.md). To
30
30
  move running projects to a newer release later, run `ahub upgrade` in a
31
31
  terminal: it reviews the plan with you, applies it and follows it.
32
32
 
33
+ ## Upgrading to 0.14.0
34
+
35
+ At a terminal width `ahub board` now prints one line per task: the title is cut with `...`, dependencies move to an AFTER column, STAGE is the meter with a short next-step code (`intent: issue?`, `review: CI?`, `ready`) and a legend line explains them; `ahub board --full`, piped output without `COLUMNS` and `--json` keep the whole title and SDLC advice (#724). The dashboard's Agents view gains a Usage view under Usage budgets: lifetime totals, the last 14 UTC days of tokens per peer, the open pauses and recent limit events, and a task table in which a private task shows only as `#id [pii]` (#683 U4). A task sheet gains a Code group: the changed symbols marked `matching` or `unplanned` against the plan, the plan names nothing changed under as `untouched`, and a symbol opens the Memory tab on its node; a private task has no group (#684). `ahub sdlc metrics [--since <date>]` and the planner tool `hub_sdlc_metrics` (planner or person, read-only) report lead times, first-pass approval, rework rounds, stale-head re-reviews and stalls from board history, with `unknown` where the board cannot tell. An optional `[bands.<metric>]` table in `.agenthub/sdlc.toml` turns a breach into a hub notice, a read-only `triage` backlog task or a `plan` backlog card, once per episode, only while `mode` is `advise` or `enforce`, and never a GitHub issue (#481 S7; see [SDLC metrics and control bands](sdlc.md#sdlc-metrics-and-control-bands-481-s7)). In advise and enforce a task's issue identity is now linked when it is proposed or accepted, with no GitHub read; a title that names another repository (`agent-mem#13`) links only when `refs.repo` names it, and the propose result suggests setting it (#723). A fresh reviewer whose start fails now names a cause class (`version`, `binary`, `mcp_inventory`, `plugins_hooks_apps`, `sandbox_proof`, `credential_root`, `timeout` or `unknown`) on the `reviewer` line of `ahub status` and in `hub.log` (#722). A Claude channel instance in `.agenthub/config.local.json` may declare `implementer` or `verifier` and then needs its own `worktree`, while a planner or reviewer instance refuses one; the hub routes no code tasks to such an instance until #525-5, and a headless Claude instance stays reviewer-only. `planner` is refused on a pi, local or agy instance declaration and dropped, with a notice, from a `roles` entry for those kinds (#525-1; see [Roles per SDLC stage](operations.md#roles-per-sdlc-stage-525-planned)). A planner pull of a reserved backlog task now assigns it to the reserved peer and offers it at once; when that peer is offline, off roster, paused, full or excluded, the pull is refused with the reason and no other peer takes it (#718). `ahub codex --as <id>` for a headless fresh reviewer instance starts it and returns (#720). Restart Claude sessions so they load the 0.14.0 plugin. Coming from 0.12.45 or older, the notes below apply too.
36
+
33
37
  ## Upgrading to 0.13.0
34
38
 
35
39
  One version policy now gates the Codex and Kimi fresh reviewers, Jcode, agy and Qwen (#691): `ahub agents` lists each installed CLI with its version and verdict, a version outside the verified ones (or, for Jcode and agy, outside their compatible line) is refused with the trust command, and `ahub agents trust <id> <version>` accepts that exact version on this machine; only the person can run it, and the reviewers' isolation proofs still run. See [agent CLI versions](operations.md#agent-cli-versions). If you use Jcode, run `ahub jcode` once per project: the hub-owned `.jcode/mcp.json` entry no longer names a peer, `ahub status` and `ahub doctor` show the old entry as not ready until it is rewritten, and the hub admits Jcode's hub tools only from an owned launch (#368). Extra Jcode instances, each with its own `jcode_home`, can be declared in `.agenthub/config.local.json` and started with `ahub jcode --as <id>` (#685; see [Jcode operations](operations.md#optional-jcode-peer)). Run `ahub init` once per project to refresh the hub block in `AGENTS.md`: it tells pull-only agents never to poll `hub_inbox` or CI on a timer and to run one heavy job per repository at a time (#681). `ahub usage` (with `limits` and `total`) and the console's Usage panel (`7`) show tokens per peer, task, issue, PR, release, day or model and the quota pause and reset history (#683); `ahub report --dogfood` prints a read-only scorecard (#687). In the console, `r` on a task in review now asks `a` (approve) or `c` (request changes, which needs a note) (#711), and a task's detail shows an overlaps block and, when ripwire 0.6.7 is on the daemon's PATH, its changed symbols, which the review brief lists too (#684). A pull-only peer now drains its queue up to 64 KiB per `hub_inbox` read, a queued delivery can be discarded while its peer is busy, and the person clears one peer's queue with `ahub queue discard --peer <id> --reason <text> --yes` (#713). `ahub sdlc authorize-release <version>` records the person's release approval, and `ahub init --release-guard` installs an optional tag pre-push guard (#481 S6; see [release approval](operations.md#release-approval-and-optional-tag-guard)). As announced since 0.12.x, `sdlc.workflow = "gsd-lite"` no longer loads and the `phase` receipt name is refused (#481): remove the setting and record a `criteria` checklist with `hub_sdlc_record`. Restart Claude sessions so they load the 0.13.0 plugin. Coming from 0.12.44 or older, the notes below apply too.
@@ -140,7 +144,7 @@ or set `OMNIROUTE_API_KEY`. The `.agenthub/routing.toml` that `ahub init` writes
140
144
  | --- | --- |
141
145
  | `ahub say [@peer] <text>` | talk; start with `[STATUS]` to let it batch, `[FYI]` for the record only |
142
146
  | `ahub task propose [--class <c>] <title>` | put work on the board; the hub picks an owner and a reviewer, and the class if you name none |
143
- | `ahub board` | who has what; `ahub task show <id>` for one task |
147
+ | `ahub board` | who has what, one line per task at a terminal width; `--full` for whole titles and SDLC advice, `ahub task show <id>` for one task |
144
148
  | `ahub task cancel <id> --reason <text>` | close obsolete work without approving it or stopping a native turn |
145
149
  | `ahub settings set planner <peer>` | pin the planner; status reports whether it came from settings, roles or the automatic choice |
146
150
  | `ahub settings reload` | apply edited config files without a restart; guarded values wait for your `y` in this terminal, and restart-only keys stay pending |