@staix/agent-hub 0.12.35 → 0.12.36
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.
- package/CHANGELOG.md +14 -0
- package/README.md +8 -6
- package/docs/agent-notes/adapters.md +4 -1
- package/docs/agent-notes/budget.md +1 -1
- package/docs/agent-notes/bus.md +3 -0
- package/docs/agent-notes/daemon.md +6 -2
- package/docs/agent-notes/tasks.md +11 -1
- package/docs/agent-notes/tests.md +5 -1
- package/docs/operations.md +40 -16
- package/docs/quickstart.md +6 -8
- package/docs/sdlc.md +46 -5
- package/docs/security.md +5 -1
- package/docs/specs/2026-09-19-agent-hub-design.md +76 -1
- package/docs/verification/2026-10-19-0.12.36.md +63 -0
- package/docs/verified.json +18 -14
- package/package.json +1 -1
- package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-hub/server.js +385 -29
- package/src/adapters/claude-print.ts +230 -0
- package/src/adapters/definitions/builtin-metadata.ts +17 -1
- package/src/adapters/definitions/native-cli-entry-claude.ts +17 -3
- package/src/adapters/definitions/native-cli-entry-jcode.ts +3 -2
- package/src/adapters/definitions/native-cli-entry-kimi.ts +3 -2
- package/src/adapters/definitions/native-cli-entry-types.ts +2 -0
- package/src/adapters/definitions/native-factories.ts +7 -0
- package/src/adapters/registry-types.ts +8 -2
- package/src/adapters/registry.ts +13 -1
- package/src/cli/help.ts +5 -0
- package/src/cli/identity.ts +2 -1
- package/src/cli/launch.ts +19 -6
- package/src/cli/main.ts +38 -4
- package/src/cli/reset.ts +4 -2
- package/src/cli/statusline-tee.ts +5 -2
- package/src/cli/terminal-recovery.ts +7 -2
- package/src/cli/upgrade-runtime.ts +18 -15
- package/src/hub/board.ts +29 -7
- package/src/hub/bus.ts +11 -5
- package/src/hub/claude-state.ts +37 -0
- package/src/hub/cost-governance.ts +76 -0
- package/src/hub/daemon.ts +181 -73
- package/src/hub/hub-tools.ts +20 -1
- package/src/hub/instances.ts +17 -3
- package/src/hub/review-brief.ts +175 -0
- package/src/hub/routing.ts +17 -2
- package/src/hub/sdlc.ts +205 -4
- package/src/hub/status-roster.ts +10 -0
- package/src/hub/task-sweep.ts +143 -0
- package/src/hub/tasks.ts +544 -26
- package/src/hub/usage.ts +57 -20
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
Issue and pull request numbers in the entries for 0.7.7 and earlier refer to the previous repository, archived on 2026-09-30 when this repository's history was rewritten; the one exception is the open smoke-check issue, formerly #12, which moved here as #1. Numbers in newer entries refer to this repository.
|
|
4
4
|
|
|
5
|
+
## 0.12.36
|
|
6
|
+
|
|
7
|
+
Reviews get their own reviewers and a hub-built brief: a headless Claude reviewer runs a fresh session per review (#450, #223), every review request carries a review brief with a coverage gate (#520), and a second Claude session can join as a reviewer or planner (#450 I3). The planner reviews plans before work starts, WIP admission applies in every lifecycle mode, a person accepts a task's spec issue (#481 S1, S9), and review and CI costs are governed, with identical-tree CI reuse (#481 S10). A Claude session with a transcript over 32 MiB no longer stays busy forever (#517). Restart Claude sessions after upgrading: each keeps the plugin server it started with.
|
|
8
|
+
|
|
9
|
+
- Planner cost governance records review/CI scope on plans and receipts, gates premature reviews by current CI and bot replies, reuses identical-tree full CI and reports bounded merged-PR review/CI costs without board writes (#481 S10).
|
|
10
|
+
- A Claude session whose transcript grew past 32 MiB no longer stays busy forever: turn completion is read from the transcript's last 4 MiB, and a Stop refused because no completed turn is found in that tail now says so on the console, once per turn, instead of only in hub.log (#517).
|
|
11
|
+
- Every review request now carries a review brief built by the hub (no `ocr` CLI): the commit's reviewable files, the excluded ones with reasons (`linguist-generated`, lockfiles, binaries), the rules from `REVIEW.md` and `ahub-review-rules` attributes per file (both read at the merge base, so a change cannot loosen its own review), and a cross-check list. `hub_review` takes `coverage`, and in `enforce` an approval that leaves a reviewable path unaccounted is refused. `ahub review brief <id>` prints it (#520).
|
|
12
|
+
- Planner grooming reports repository-scoped dependencies, overlaps, WIP and capability/issue blockers without changing the board. Plan-review receipts bind the planner, planning revision and metadata-only issue edit evidence; enforce requires a current receipt before offering or accepting build work, advise reports gaps and off does not gate (#481 S9).
|
|
13
|
+
- WIP admission now applies in every lifecycle mode. Planner/person actions can return accepted but untouched work with history and planning retained; work evidence still refuses return, and ordinary accepted-work cancellation/unassignment remains person-only (#481 S9).
|
|
14
|
+
- `ahub jcode` no longer leaves a project MCP entry the daemon rejects as stale when the CLI on PATH and the running hub come from different installs: the hub reports its plugin bundle in `status`, and the Jcode and Kimi TUI launchers write that path (#502).
|
|
15
|
+
- A person accepts a task's spec issue with `ahub sdlc accept <task> --issue <n>`, bound to the issue's body edit time (identity and edit times only, no body). In enforce, build work starts only from a current acceptance: a body edit after acceptance asks for re-acceptance, while comments do not; advise only notifies (#481 S1).
|
|
16
|
+
- Declare a second Claude session as a reviewer or planner (`"claude-review": { "kind": "claude", "roles": ["reviewer"] }` in untracked `.agenthub/config.local.json`) and start it from your terminal with `ahub claude --as claude-review`. It joins as its own channel peer with its own launch, session, context and recovery records; the hub never starts it, and it takes no implementer role or worktree (#450 I3).
|
|
17
|
+
- Run a headless Claude reviewer the hub starts per review (`"claude-review": { "kind": "claude", "roles": ["reviewer"], "start_mode": "headless" }`, then `ahub claude --as claude-review`, which asks the hub to start it): each review request gets a new read-only `claude -p` session that sees the review facts and a bounded diff, never the builder's summary or agent chat. Review requests route to a declared reviewer-only instance first, a fallback to the planner is noted on the task, and each verdict records `reviewContext: fresh | continuing` (#450, hub task #223).
|
|
18
|
+
|
|
5
19
|
## 0.12.35
|
|
6
20
|
|
|
7
21
|
A Claude session started with `ahub claude` gets its hub messages while it is busy (#493), `ahub jcode` starts again (#484), a kind can run several headless instances (#450 I2), and an enabled lifecycle follows the AI-Native SDLC Playbook with gsd-lite retired (#481 S0+S8). Restart Claude sessions after upgrading: each keeps the plugin server it started with.
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ Codex, Kimi Code, Pi and a hub-owned local worker (Qwen Code and Jcode optional)
|
|
|
5
5
|
share a message bus and a task board per project and one memory on the machine,
|
|
6
6
|
each attached through its own native interface.
|
|
7
7
|
|
|
8
|
-
Status: 0.12.
|
|
8
|
+
Status: 0.12.36, 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
|
## What it does today
|
|
@@ -77,16 +77,18 @@ cd <your project> && ahub init && ahub up
|
|
|
77
77
|
|
|
78
78
|
After upgrading from 0.12.30 or earlier, run `ahub jcode --mode headless` once in each
|
|
79
79
|
project that uses Jcode to refresh its owned MCP entry before conductor or dashboard
|
|
80
|
-
starts of Jcode. The entry's format is unchanged in 0.12.
|
|
81
|
-
reports the entry missing or stale is fixed by the same command.
|
|
80
|
+
starts of Jcode. The entry's format is unchanged in 0.12.36; a daemon start that still
|
|
81
|
+
reports the entry missing or stale is fixed by the same command, which since 0.12.36
|
|
82
|
+
writes the running hub's own plugin bundle (#502).
|
|
82
83
|
Cancellation needs a daemon advertising `task_cancel`; controlled downgrades
|
|
83
84
|
refuse while cancelled rows exist. For local/Pi implementation, opt in to an
|
|
84
85
|
available `checks.runner = "container"`; issue-backed owners also need authenticated
|
|
85
86
|
spec access. PII work and designated reviewers retain their separate exemptions.
|
|
86
|
-
See [release verification](docs/verification/2026-10-
|
|
87
|
+
See [release verification](docs/verification/2026-10-19-0.12.36.md) for the live legs
|
|
87
88
|
still open, including #418 AC8, #438 AC7, the native console settings leg (#439 AC5),
|
|
88
|
-
|
|
89
|
-
|
|
89
|
+
real upgrades with reattach (#463) and with a managed terminal and cancelled tasks
|
|
90
|
+
(#478), a live headless Claude review (#223), and a Claude session over 32 MiB
|
|
91
|
+
(#517).
|
|
90
92
|
|
|
91
93
|
Before migrating a memory home with `amem`, upgrade every hub using that home to
|
|
92
94
|
0.12.30 or later, check `ahub memory profile-check`, then quiesce and close all memory
|
|
@@ -49,7 +49,10 @@ Scope: the Codex, ACP and Pi adapters and the Pi extension (turn `generation` an
|
|
|
49
49
|
- Pi TUI hub prompts (#379; an idle snapshot ACK arriving before a personal start inserted a hub prompt into the person's turn): attribute a start, end or settle to a hub turn only by the command's `promptId`, which lasts until the logical `agent_settled`; treat any other start while a hub prompt is pending or running as personal activity that detaches Pi. Settle `failed_safe` only on a `prompt_not_started` refusal bound to the verified owner, session, command id and generation; everything else stays `needs_review`. Refuse roster return without `fencedPrompt`.
|
|
50
50
|
|
|
51
51
|
- Adapter registry (#451): kind-specific policy lives in that peer's `AdapterDefinition` under `src/adapters/definitions/` (runtime definitions `NATIVE_BUILTIN_DEFINITIONS_WITHOUT_QWEN` in `builtin-metadata.ts` plus `qwen.ts`; the daemon constructs every native driver through `start.create` and `DAEMON_PORTS`; `BUILTIN_DEFINITIONS_WITHOUT_QWEN` is the frozen A1 export, read by no runtime consumer). Outside `src/adapters/`, read a semantic field of `definitionOf(id)`: never a raw peer-id comparison, a resolved kind (`kindOf`, `definitionOf(...).kind`) or a constant alias compared with a native name, a vendor-keyed object, a peer-name array, or a closed-list import from `peer-catalog.ts`, `start-mode.ts` or `permission-mode.ts`; the guard resolves aliases and import/re-export bindings. `test/registry-guard.baseline.json` is `[]` and stays empty: a new site moves into a definition, never into the baseline. The only exceptions are the cross-release literals `KNOWN_PEERS` (5), `OPTIONAL_PEERS` (2) and `START_MODE_PEERS` (3), pinned separately in `test/registry-guard.test.ts`. `LaunchContext.nativeCommand` carries only validated config argv (or its redacted preview), never model input; a definition's `permission.grants` text is what `permissionGrant` shows, ahead of the built-in wording. Keep native mode-id maps in `definitions/*-native.ts`, free of registry factories (`test/adapter-import-order.test.ts`).
|
|
52
|
-
- Registry load runs `assertDaemonEngine` and `assertNativeCapabilityScope` before publication. A definition that is not a fixed builtin (object identity in `fixedBuiltinDefinitions`, never a kind name) may not declare `start.acpCommand`, `attach.admission`, `budget.resetSoftTokensOnResume`, `session.crashIdentity`, a `pi-rpc` daemon port or `
|
|
52
|
+
- Registry load runs `assertDaemonEngine` and `assertNativeCapabilityScope` before publication. A definition that is not a fixed builtin (object identity in `fixedBuiltinDefinitions`, never a kind name) may not declare `start.acpCommand`, `attach.admission`, `budget.resetSoftTokensOnResume`, `session.crashIdentity`, a `pi-rpc` daemon port, `routing` or `reviewerVariant`. Only `definitionOf(id, instances)` picks a kind's `reviewerVariant`, for a declared extra whose own `start_mode` is `"headless"`. A new field that depends on fixed-builtin assumptions or host-global native state joins that list (`test/adapter-capability-scope.test.ts`). `trust.onPrem` admits PII work (rule: `tasks.md`).
|
|
53
53
|
- `validatePiSession` (`src/pi/session.ts`) throws `PiSessionUnresumable` only on positive evidence (ENOENT, a file outside the managed session directory, an unparsable or mismatched header) and `PiSessionBookmarkMismatch` only when a valid header names another session id; every other read failure is rethrown unchanged, because the daemon retires a Pi continuity bookmark only on those two classes (#463). `PiPeer` rethrows a mismatch with the neutral header-id message and never claims a bookmark exists (`test/pi-session-validation.test.ts`).
|
|
54
54
|
|
|
55
55
|
- Spawn with `peerChildEnv(<instance id>)`, not the kind name. An ACP `kind` comes only from the host declaration, never from the id suffix or agent text.
|
|
56
|
+
|
|
57
|
+
- TUI launchers (Kimi, Jcode) write the plugin bundle the hub reports in `status` into their MCP entry, and the CLI's own copy only when the hub reports none (#502). Only the Jcode start path checks the entry against the daemon's own `SERVER_JS` (`jcodeMcpReady`); Kimi TUI admission does not compare it.
|
|
58
|
+
- `ClaudePrintPeer` (#223) 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.
|
|
@@ -11,7 +11,7 @@ Scope: quota and native context readings, checkpoints, pause, handoff and resume
|
|
|
11
11
|
|
|
12
12
|
- Keep native context occupancy (`src/hub/context-window.ts`, readers in `src/adapters/definitions/native-context.ts`) separate from quota accounting; never use Codex's accumulated `total` as occupancy or estimate Pi counters.
|
|
13
13
|
- Context checkpoint completion requires its request id and the current attached peer/native session. Invalidate its transport generation synchronously at a new peer claim, before asynchronous recall or attachment. Never pause, reassign or replace a session on context pressure. Refuse private-turn, PII-pattern and all associated open PII-task summaries (owner or reviewer, including in_review, with the current routing policy; use `isClosedTaskState` to exclude approved and cancelled work) before local persistence or cloud memory; never broadcast checkpoint text.
|
|
14
|
-
- Invalid/stale context readings expose unknown and do not rearm a crossing. Latch a crossing only after its event is accepted; reconsider paused/recovery-held crossings after release with fresh current-session readings. Tee context fields contain finite numeric values or null only, bound to daemon instance, launcher and native session.
|
|
14
|
+
- Invalid/stale context readings expose unknown and do not rearm a crossing. Latch a crossing only after its event is accepted; reconsider paused/recovery-held crossings after release with fresh current-session readings. Tee context fields contain finite numeric values or null only, bound to daemon instance, launcher and native session. Context and session go to the per-peer file `claudeStateFile(base, peer)` (the builtin keeps the unsuffixed name); `claude-usage.json` stays one shared file per account.
|
|
15
15
|
|
|
16
16
|
- A provider hard refusal ends a reading override and supersedes a soft evaluation still awaiting its checkpoint; never let the obsolete checkpoint overwrite that pause. An ACP provider quota refusal reports a hard budget reading and confirms the live pause before the adapter requeues work, so the pause fences retries and the existing relay handles assignments. Use the observed window length for an unknown reset; repeated quota refusals while paused do not consume poison-envelope retries.
|
|
17
17
|
- Kimi's ACP `{used, size}` occupancy is a context reading (`acpContext`, source `acp_usage_update`) bound to the adapter's own session id: never a `tokens` event or a budget window, and an invalid shape reports unknown, not the previous value. A gate crossing sends a checkpoint request only to definitions with `budget.checkpoint` (Claude and Codex); an ACP peer gets the event and the notice (#285).
|
package/docs/agent-notes/bus.md
CHANGED
|
@@ -31,3 +31,6 @@ Scope: delivery, digests and condensation, reply addressing, priority and limits
|
|
|
31
31
|
- A hub-native peer (Pi, the local worker) does not get to call its own message `important`: `capPriority` caps it unless the delivery it answers held an `important` envelope addressed to it. Check the delivery, not `replyParent`, which ties on hop and takes the later item. Do not bypass it by setting `priority` in the adapter.
|
|
32
32
|
|
|
33
33
|
- Routing never chooses a pull-only owner or reviewer automatically. Pass live pull-only ids and role-specific explicit naming to the pure assign function; an automatic singleton is not named. Only a honored persisted owner reservation, explicit owner nomination/claim, or authorized reviewer nomination bypasses that eligibility refusal, never roster, PII, pause or capability guards.
|
|
34
|
+
|
|
35
|
+
- `relevant` may return a string: the envelope is dropped with that string as its journal discard reason. Read relevance only through `irrelevance()`.
|
|
36
|
+
- A review-only instance (a declared extra whose roles are exactly `reviewer`) receives only person messages, hub `review` items and hub `task` items addressed to it. Keep `reviewOnlyItem` in both `silence` (at send) and `relevant` (at delivery) (#223).
|
|
@@ -24,7 +24,7 @@ Scope: the operator console, control WS and its protocol, state files, the dashb
|
|
|
24
24
|
- Defer hub notices only for an authenticated recovery `prepare` or `commit` whose target features include `recovery_deferred_hub`, or for a restored daemon. Otherwise `setRecoveryHold(true)` moves the deferred notices into queues before commit records expected integrity, and a bare `fenceRecovery` defers nothing. Abort and lease expiry release the bus hold before clearing the operation; if that release fails, keep the operation and let expiry retry on its 1 s timer (#478).
|
|
25
25
|
- Resume a prepared `commitSent` source that is unavailable or database-busy inside the existing bounded shutdown poll; never re-send commit, re-prepare or close terminals again on that path. Require confirmed `stopped` before target startup, refuse a replacement instance, and retain the prepared receipt on timeout.
|
|
26
26
|
- Keep restoration receipts explicitly tagged as pending, waiting with origin/binding, done with binding, or failed. Project waiting origins as `waiting:pending` and `waiting:failed`; preserve the chosen binding and origin across interruption instead of inferring completion from an object.
|
|
27
|
-
- A zero-turn Claude session has no transcript, so it can never be resumed: every identity gate that compares session ids (restore, coordinator verify, daemon readiness) must tolerate a fresh session while the transcript is absent and stay strict the moment one exists. The commit snapshot records `sessionPersisted` for the restored daemon, and snapshots too old to have it are re-derived from disk. Never treat "session id changed" as proof of a lost conversation without checking the transcript first. A post-restore detachment repair (#463) is bound to its last accepted session even when its transcript is absent; never use the zero-turn exception to replace that recorded conversation.
|
|
27
|
+
- A zero-turn Claude session has no transcript, so it can never be resumed: every identity gate that compares session ids (restore, coordinator verify, daemon readiness) must tolerate a fresh session while the transcript is absent and stay strict the moment one exists. An instance's transcript counts as missing only on positive ENOENT/ENOTDIR of its own reported path (`transcriptPersisted`, `pathEvidence`); with no reported path or another lstat error, treat it as persisted. The commit snapshot records `sessionPersisted` for the restored daemon, and snapshots too old to have it are re-derived from disk. Never treat "session id changed" as proof of a lost conversation without checking the transcript first. A post-restore detachment repair (#463) is bound to its last accepted session even when its transcript is absent; never use the zero-turn exception to replace that recorded conversation.
|
|
28
28
|
- The terminal-recovery launcher signature is the shared pinned `processSignature` (`src/pi/process-signature.ts`): a record written by one CLI invocation must compare equal when a later invocation re-reads the live launcher under another TZ/LC_ALL (#177). In-process `process.env` mutation does not reach Bun.spawnSync's default child environment, so cross-environment regressions are tested with real subprocesses. A row written by 0.12.12 or older carries the unpinned signature, which `launcherState` also compares (#228). A launch-record row that fails validation is unknown for the peer it may belong to and is written back unchanged, never filtered out (#228).
|
|
29
29
|
- Judge every recorded owner pid (hub manifest, registry claim, recovery runner, manager owner, launcher record) with `processLiveness` in `src/pi/process-signature.ts`, never a bare `process.kill(pid, 0)`: after a reboot a recorded pid can belong to another process, even one we may not signal, and the bare probe wedged recovery and `ahub up` (#226). A writer records `processSignature(process.pid)` beside its pid; a registry claim stores it as `<instance id> <signature>` because an older daemon's claim rewrites the pid but not that column. Records without a signature keep the bare-pid answer; unknown is never gone.
|
|
30
30
|
- A restored daemon accepts a changed native session id (Kimi and local aside: they restart as fresh workers) in two ways only: the #64 rule for a Claude session that never persisted a transcript, and the operation-fenced `recovery-waivers.json`, which the coordinator writes wherever it accepts a session other than the planned one: a reconnect-only Claude (#206), an operator-chosen fresh session, the plan's `freshStart` of a Codex thread the hub saw start with no turn, or a zero-turn Claude it accepted (#215). Never waive on any other evidence. Re-preparation after an expired lease keeps every effect receipt, and `restored:<peer>` = `failed` never counts as restored. Judge a restoration launcher by its `terminal-recovery.json` record and process signature (and a `terminal_handle_stale` answer), never by the terminal's exit; an unreadable record is unknown, never "none".
|
|
@@ -39,7 +39,7 @@ Scope: the operator console, control WS and its protocol, state files, the dashb
|
|
|
39
39
|
- Recovery judges the registry claim as it is now (`inspectRecovery` re-reads `registeredProjects()`), never the plan's frozen copy; a registry that cannot be read is not inspected, never stopped (#226).
|
|
40
40
|
- `inspectRecovery` catches only `RecoveryDeferredHubError` from `readRestartSnapshot`: keep the stopped state and return the static `snapshotError` blocker without a `snapshot` binding. Never echo rejected content, repair the snapshot or offer resume from it (#478).
|
|
41
41
|
- The managed Claude hook (`src/cli/facts-hook.ts`) may only add `permissionDecision: "allow"` on `PreToolUse`, and for `ask-when-needed` only what `projectFileTool` accepts: a hub that is down, slow or refuses decides nothing. The daemon puts `permission` in a `facts` reply only for the hook of this instance's recorded `ahub claude` launch (the same `launchId`, not `--unattended`), and a permission-only hook (`hookPurpose`) never drives busy state (#242).
|
|
42
|
-
- `ahub claude` (`src/cli/launch.ts`, #270) publishes `claude-launch.json` for every launch against a running hub, atomically and without a lock: hooks, permission status, session observation and recovery read its `launchId`, so never make it wait for native-proof state. The native child's pid and signature go only into the private `claude-native-<binding hash>.json`, keyed by the exact launch record, written once under `claude-launch.lock` and removed at that child's own exit; never put them in `claude-launch.json`, where a late callback of an earlier launch could replace a newer record. Crash cleanup of a previous launch's settings file (`cleanupStaleClaudeSettings`) needs the launcher gone by `processLiveness` and that native proof, and for a `direct-child` proof the native pid absent from an available `processTable`: a direct child may exec in place, so its signature certifies nothing. A script or interpreter gets no proof and keeps its settings file.
|
|
42
|
+
- `ahub claude` (`src/cli/launch.ts`, #270) publishes `claude-launch.json` (`claude-launch.<id>.json` for an `--as` instance) for every launch against a running hub, atomically and without a lock: hooks, permission status, session observation and recovery read its `launchId`, so never make it wait for native-proof state. The native child's pid and signature go only into the private `claude-native-<binding hash>.json`, keyed by the exact launch record, written once under `claude-launch.lock` and removed at that child's own exit; never put them in `claude-launch.json`, where a late callback of an earlier launch could replace a newer record. Crash cleanup of a previous launch's settings file (`cleanupStaleClaudeSettings`) needs the launcher gone by `processLiveness` and that native proof, and for a `direct-child` proof the native pid absent from an available `processTable`: a direct child may exec in place, so its signature certifies nothing. A script or interpreter gets no proof and keeps its settings file.
|
|
43
43
|
|
|
44
44
|
- Keep SQLite recovery contention in the existing blocked receipt as retryable `database_busy`, retaining bounded throw-site frames and the ordinary `ok: false` rid reply. Use a neutral control error; the receipt's next actions name resume or the partway disposition. Clear a superseded failure on abort, disposal, fresh-session choice or a refused resume of a disposition. After a commit request, treat busy inspection as unconfirmed and keep polling only inside the existing shutdown bound.
|
|
45
45
|
- Recovery inspection uses its existing 30 s control cap for the first status read and consumes a structured busy reply without a second status probe; ordinary lifecycle probes keep their default. Treat `busy_timeout` as a ceiling for eligible waits, not a guarantee that every write waits: a WAL snapshot conflict may fail immediately. Account for synchronous 3 s hub-database waits and serial timer callbacks when choosing a timeout; several waits may consume the 30 s control margin. Never multiply the busy wait through retries or infer stopped from a shorter probe's timeout.
|
|
@@ -61,3 +61,7 @@ Scope: the operator console, control WS and its protocol, state files, the dashb
|
|
|
61
61
|
- Bind instance declarations once at startup through the daemon-local `definitionOf` closure and the `extraIds` catalogs; never publish them to the global registry or take them from a request. `instances` is restart-class, and its settings rows come only from `projectSettings(declarations)`.
|
|
62
62
|
- 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. Extra-instance native state is per instance, while its MCP binding keeps the canonical hub state dir.
|
|
63
63
|
- 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; refuse before effects when `peer_instances` is missing. 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.
|
|
64
|
+
|
|
65
|
+
- 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).
|
|
66
|
+
- 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.
|
|
67
|
+
- Verify a native Claude Stop and its start baseline only through `readClaudeTranscriptCompletion` (bounded tail), never the size-capped `readClaudeTranscriptUsage`; an `unavailable` refusal leaves the peer busy and notifies once per turn start (#517).
|
|
@@ -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` or `src/hub/
|
|
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` or `src/hub/review-brief.ts`.
|
|
4
4
|
|
|
5
5
|
- 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.
|
|
6
6
|
- Post-await assignment publication is fenced by current role authority: supersession returns the current task without a saved-delivery notice; reviewer-only changes refresh current metadata and do not suppress a valid owner delivery. A replacement notice goes only to a role that actually received the request, not to an unread-only queue.
|
|
@@ -38,3 +38,13 @@ Scope: the task flow, assignment, the state machine and the hub's MCP tools. Rea
|
|
|
38
38
|
|
|
39
39
|
- Resolve any id that may be an extra instance with `definitionOf`/`kindOf`/`familyOf(id, this.d.instances?.())`; 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`.
|
|
40
40
|
- 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.
|
|
41
|
+
|
|
42
|
+
- 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.
|
|
43
|
+
|
|
44
|
+
- Preserve planner-reviewed cost keys when an accept replaces only file-list intent; explicit enforce scope drift needs a new planner receipt. New enforce plan-review receipts require typed review/CI scopes; keep legacy missing scope unspecified until its planning revision invalidates it. Run premature-review admission before done/cohort writes, recheck state and actor authority after every await, and keep admission separate from the review brief. Scope is cost metadata, never a permission or reviewer bypass.
|
|
45
|
+
- Cost reports recheck planner/private-turn authority and current linked privacy before each underlying external read and final projection. Do not emit a stale/relinked observation as a complete zero-cost row. S7 owns band configuration and automatic proposal writes.
|
|
46
|
+
|
|
47
|
+
- `Tasks.review` captures `reviewAuthority` once and rechecks it in `valid()` after every await, right before each write; a headless reviewer's verdict holds only while the same child runs for that task. Never cache the answer (#223).
|
|
48
|
+
- 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.
|
|
49
|
+
- 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.
|
|
50
|
+
- `Board.update`'s untouched-return exception is the only way owned or accepted work returns to backlog: keep every condition (exact event, owner and reviewer null, state, `expectedFlowRevision`, non-empty note, no work evidence) and reach it only through `Tasks.returnUntouchedToBacklog` (#481 S9).
|
|
@@ -13,7 +13,7 @@ Scope: tests, fakes and the test gate. Read before adding or changing a test or
|
|
|
13
13
|
- Cross-process fixtures wait for the consuming client to process its handshake (the channel connected diagnostic), not merely the daemon's attached/idle state. Deadline-routing fixtures hold and release responses at the fallback decision; do not place a fixed sleep between deadline fractions and assume scheduler headroom (#287). Receipt-order fixtures use the normal watchdog for ordinary I/O, trigger expiry after observed native acceptance, and await a consumed transport barrier before asserting on late results.
|
|
14
14
|
- 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).
|
|
15
15
|
- 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.
|
|
16
|
-
- 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. 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. Keep that job name: branch protection and `scripts/ci-gates.mjs` read it (#457).
|
|
16
|
+
- 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. 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).
|
|
17
17
|
|
|
18
18
|
- `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.
|
|
19
19
|
- The whole-suite watchdog is `AHUB_CHECK_HANG_S` (default in `scripts/check.sh`, hosted macOS value in `.github/workflows/check.yml`; #378, macOS CI hit it while tests still passed): raise it only when the suite was still progressing at the cutoff, never for a hung test, and keep the per-test 20 s timeout.
|
|
@@ -22,3 +22,7 @@ Scope: tests, fakes and the test gate. Read before adding or changing a test or
|
|
|
22
22
|
- Keep CI's Bun version sourced from `.bun-version` through the pinned setup-bun action; update the version file rather than adding per-job version literals.
|
|
23
23
|
- Release verification requires both OS checks and the aggregate `seeded guards` success for an identical tree, a tag matching `package.json` on `origin/main`, and fresh ancestor-bound documentation stamps. Preserve the separate npm OIDC publish job and GitHub Release write job; neither job gets the other's credential authority.
|
|
24
24
|
- `test/conformance/` runs real adapters against fake transports. Record a case that does not apply as an explicit unsupported outcome, never synthetic success, and never change native production behavior to satisfy a generic case.
|
|
25
|
+
|
|
26
|
+
- Keep the CI admission lock around the whole controller workflow, not just its decision job. Preserve successful identical-tree full proof and required job names; a skipped job, pending controller or unavailable active inventory is never a successful full gate. CI reuse tests use fixed metadata fixtures and never dispatch real duplicate workflows.
|
|
27
|
+
|
|
28
|
+
- Headless-reviewer tests run `test/fakes/fake-claude-print.ts` through `command` or a PATH stand-in; a test that reaches the daemon's reviewer start must never run the real `claude`.
|
package/docs/operations.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Operations guide
|
|
2
2
|
|
|
3
|
-
This guide describes ahub 0.12.
|
|
3
|
+
This guide describes ahub 0.12.36 and control protocol 16. Live verification
|
|
4
4
|
results and remaining prerequisites are recorded separately in [the smoke ledger](smoke.md).
|
|
5
5
|
|
|
6
6
|
## Agent-operated live verification
|
|
@@ -802,7 +802,13 @@ includes), read the check result, and list what is
|
|
|
802
802
|
unmet (`hub_review` takes `unmet`, `ahub review ... --unmet <item>`). A reviewer
|
|
803
803
|
that missed the request, and the owner, read the done summary and check result
|
|
804
804
|
with `hub_task_show {id}`: the task's public view with its history, as the
|
|
805
|
-
conductor sees it, and only a stub for a PII task. Other peers are refused.
|
|
805
|
+
conductor sees it, and only a stub for a PII task. Other peers are refused. Each
|
|
806
|
+
request also carries the hub's review brief for the task's commit: reviewable and
|
|
807
|
+
excluded files with reasons, the `REVIEW.md` and `ahub-review-rules` rules read at
|
|
808
|
+
the merge base, and a cross-check list; `ahub review brief <id>` prints it.
|
|
809
|
+
`hub_review` takes `coverage {reviewed, skipped}`: in enforce an approval that leaves
|
|
810
|
+
a reviewable path unaccounted is refused, and advise notes the gap in the verdict
|
|
811
|
+
(#520). The hub
|
|
806
812
|
records how each review turned out, per implementer, reviewer and class:
|
|
807
813
|
approved; caught (changes were requested and the owner's redo was approved);
|
|
808
814
|
contradicted (within a week, work on the same file or symbol failed its check
|
|
@@ -1785,6 +1791,7 @@ A Claude session started with `ahub claude` receives pushes when it is idle. Whi
|
|
|
1785
1791
|
- `hub_inbox` reads the daemon queue (and any local failed-push buffer) in a channel session too. A read is one `completed` delivery, like a tools-only read; holds refuse it the same way.
|
|
1786
1792
|
- An important message is pushed into the running turn (steered) instead of waiting for the turn to end. Mid-turn reading was verified on Claude Code 2.1.302; an older client that drops it leaves the delivery `accepted` until a person resolves it. Ordinary messages still wait for the idle push or `hub_inbox`.
|
|
1787
1793
|
- Do not use `[FYI]` for PR ready, done, submitted, ruling-needed, blocker or changed-contract messages: they need action. `hub_task_done` and `hub_review` already notify their recipients.
|
|
1794
|
+
- Turn completion is read from the transcript's last 4 MiB, whatever its size. When no completed turn is found there, the console says once per turn that the peer stays busy and its messages wait in its queue (#517).
|
|
1788
1795
|
|
|
1789
1796
|
## Graceful shutdown
|
|
1790
1797
|
|
|
@@ -1971,21 +1978,21 @@ section is what they run.
|
|
|
1971
1978
|
|
|
1972
1979
|
Upgrade running projects with the target release's own coordinator. It accepts
|
|
1973
1980
|
a running source on control protocol 9 (0.6.x), 10 (0.7.0 through 0.12.0),
|
|
1974
|
-
11 (0.12.1 and 0.12.2), 12 (0.12.3), 13 (0.12.4 through 0.12.15), 14 (0.12.16), 15 (0.12.17 through 0.12.19) or 16 (0.12.20 through 0.12.
|
|
1981
|
+
11 (0.12.1 and 0.12.2), 12 (0.12.3), 13 (0.12.4 through 0.12.15), 14 (0.12.16), 15 (0.12.17 through 0.12.19) or 16 (0.12.20 through 0.12.36), and only
|
|
1975
1982
|
a target on its own protocol, so the target's coordinator fits every supported
|
|
1976
1983
|
source and carries every recovery fix released up to it. Protocol 8 and older
|
|
1977
1984
|
(0.5.x and earlier) are refused as `manual-bootstrap-required`. Run from the
|
|
1978
1985
|
project directory, without replacing the global CLI first:
|
|
1979
1986
|
|
|
1980
1987
|
```bash
|
|
1981
|
-
bunx --package @staix/agent-hub@0.12.
|
|
1982
|
-
bunx --package @staix/agent-hub@0.12.
|
|
1988
|
+
bunx --package @staix/agent-hub@0.12.36 ahub upgrade --to 0.12.36 --dry-run
|
|
1989
|
+
bunx --package @staix/agent-hub@0.12.36 ahub upgrade --to 0.12.36 --yes
|
|
1983
1990
|
```
|
|
1984
1991
|
|
|
1985
1992
|
| Running now | Coordinator to use |
|
|
1986
1993
|
| --- | --- |
|
|
1987
1994
|
| 0.6.x (protocol 9) | the target's, through `bunx` as above |
|
|
1988
|
-
| 0.7.0 through 0.12.0 (protocol 10), 0.12.1 and 0.12.2 (protocol 11), 0.12.3 (protocol 12), 0.12.4 through 0.12.15 (protocol 13), 0.12.16 (protocol 14), 0.12.17 through 0.12.19 (protocol 15), 0.12.20 through 0.12.
|
|
1995
|
+
| 0.7.0 through 0.12.0 (protocol 10), 0.12.1 and 0.12.2 (protocol 11), 0.12.3 (protocol 12), 0.12.4 through 0.12.15 (protocol 13), 0.12.16 (protocol 14), 0.12.17 through 0.12.19 (protocol 15), 0.12.20 through 0.12.36 (protocol 16) | the target's, through `bunx` as above |
|
|
1989
1996
|
| any supported source, with the installed CLI already at the target | `ahub upgrade` below, which is the same coordinator |
|
|
1990
1997
|
| 0.5.x or earlier (protocol 8 and older) | not supported: bootstrap by hand with the matching CLI |
|
|
1991
1998
|
|
|
@@ -2017,14 +2024,14 @@ projects first:
|
|
|
2017
2024
|
|
|
2018
2025
|
```bash
|
|
2019
2026
|
ahub restart --dry-run
|
|
2020
|
-
ahub upgrade --to 0.12.
|
|
2027
|
+
ahub upgrade --to 0.12.36 --dry-run
|
|
2021
2028
|
```
|
|
2022
2029
|
|
|
2023
2030
|
Apply only after reviewing the plan:
|
|
2024
2031
|
|
|
2025
2032
|
```bash
|
|
2026
2033
|
ahub restart --yes
|
|
2027
|
-
ahub upgrade --to 0.12.
|
|
2034
|
+
ahub upgrade --to 0.12.36 --yes
|
|
2028
2035
|
ahub recovery status [<operation-id>]
|
|
2029
2036
|
ahub recovery resume [<operation-id>]
|
|
2030
2037
|
ahub recovery abort [<operation-id>]
|
|
@@ -3056,7 +3063,7 @@ that the file is untracked; a dropped value is reported by the settings result.
|
|
|
3056
3063
|
|
|
3057
3064
|
## SDLC artifact receipts and gates
|
|
3058
3065
|
|
|
3059
|
-
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. S1-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.
|
|
3066
|
+
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). S2-S7, S11 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.
|
|
3060
3067
|
## Optional Qwen Code peer
|
|
3061
3068
|
|
|
3062
3069
|
Select Qwen per project with `ahub roster add qwen`; it defaults off and is absent
|
|
@@ -3084,7 +3091,7 @@ Qwen controlled recovery and automatic crash resume load the recorded conversati
|
|
|
3084
3091
|
|
|
3085
3092
|
## Optional Jcode peer
|
|
3086
3093
|
|
|
3087
|
-
Use ahub roster add jcode, then ahub jcode --mode headless [--model <alias>] [--provider-profile <name>]. The launcher reads roster membership from the hub's status reply; a hub older than 0.12.35 does not send it, so the launcher stops with an upgrade-or-restart remedy and starts nothing (#484). The person launcher configures only agent-hub-jcode in .jcode/mcp.json; run ahub init afterwards to ignore the untracked machine-path file. Conductor/dashboard starts read that entry without editing it. Preview starts nothing, and ahub stop jcode stops only the owned private daemon/ACP groups. jcode_cmd, jcode_home and jcode_provider_profile are machine-local and never editable from settings.
|
|
3094
|
+
Use ahub roster add jcode, then ahub jcode --mode headless [--model <alias>] [--provider-profile <name>]. The launcher reads roster membership from the hub's status reply; a hub older than 0.12.35 does not send it, so the launcher stops with an upgrade-or-restart remedy and starts nothing (#484). The person launcher configures only agent-hub-jcode in .jcode/mcp.json; run ahub init afterwards to ignore the untracked machine-path file. It writes the plugin bundle the running hub reports in `status`, so a CLI from another install no longer leaves a stale entry; `ahub kimi` does the same for Kimi's TUI entry (#502). Conductor/dashboard starts read that entry without editing it. Preview starts nothing, and ahub stop jcode stops only the owned private daemon/ACP groups. jcode_cmd, jcode_home and jcode_provider_profile are machine-local and never editable from settings.
|
|
3088
3095
|
|
|
3089
3096
|
After upgrading to the recipient-retry release, a Jcode project MCP entry written before `AGENTHUB_DELIVERY_ONLY` was added is intentionally stale. Once per project, the person runs `ahub jcode --mode headless` before using conductor/dashboard starts. The launcher rewrites only the owned entry with the delivery-only marker, preserving foreign entries and refusing collisions; recovery relaunches already use this CLI path. Readiness checks never rewrite the file, and an explicit false marker or any other drift remains refused. Until that rewrite, daemon starts keep their actionable stale-entry refusal rather than advertising a retry tool Jcode cannot use.
|
|
3090
3097
|
|
|
@@ -3267,10 +3274,12 @@ The person uses `ahub backlog pull-ready <id> <revision> <reason>` or Pull withi
|
|
|
3267
3274
|
WIP on a card. Explicit manual pull/assignment retain their existing authority.
|
|
3268
3275
|
|
|
3269
3276
|
Capacity defaults to one per peer. `ahub settings set tasks.wip_limits.codex 2`
|
|
3270
|
-
uses the existing machine-local registry path; integers 0..32 apply
|
|
3271
|
-
with 0 disabling planner admission for that
|
|
3272
|
-
|
|
3273
|
-
owned unfinished work plus unowned planner-pull reservations.
|
|
3277
|
+
uses the existing machine-local registry path; integers 0..32 apply live (the next
|
|
3278
|
+
admission reads the current value), with 0 disabling planner admission for that
|
|
3279
|
+
peer. Status and the agent legend count
|
|
3280
|
+
owned unfinished work plus unowned planner-pull reservations. The same limit gates
|
|
3281
|
+
acceptance and self-claim in every lifecycle mode, so 0 refuses new acceptances; a
|
|
3282
|
+
current owner's redo of sent-back work keeps its slot (#481 S9).
|
|
3274
3283
|
|
|
3275
3284
|
Pending unowned, unstarted pull can be undone at its latest owned revision via
|
|
3276
3285
|
`hub_backlog_undo` or the existing planning undo control, restoring its earlier
|
|
@@ -3430,7 +3439,7 @@ reference markers remain protected. Split-part plan schemas follow the selected
|
|
|
3430
3439
|
project profile. Terminal tables preserve the visible read for stages and use
|
|
3431
3440
|
a fresh peer status read on the same authenticated connection.
|
|
3432
3441
|
|
|
3433
|
-
Integer WIP editors
|
|
3442
|
+
Integer WIP editors save a value, including zero and inheritance; it applies at the next admission. GitHub intake accepts dot-prefixed repository names within the existing bounded owner/repository identity. Split undo also refuses children referenced by another task's persisted phase checklist.
|
|
3434
3443
|
|
|
3435
3444
|
Declining rework returns changes_requested to To Do before rerouting. When a planner-pulled task has no eligible destination capacity, it remains ownerless in To Do and is retried after capacity clears, preserving the declined-owner exclusion.
|
|
3436
3445
|
|
|
@@ -3519,7 +3528,22 @@ restart the hub. For example, a reviewer can use:
|
|
|
3519
3528
|
Start them with `ahub codex --as codex-review` and
|
|
3520
3529
|
`ahub kimi --as kimi-review`. Codex keeps its native TUI and its own hub-owned
|
|
3521
3530
|
app-server proxy; extra Kimi, Qwen, Pi and local instances use headless starts.
|
|
3522
|
-
Extra
|
|
3531
|
+
Extra Jcode instances are unavailable. An extra Claude instance is a reviewer or
|
|
3532
|
+
planner you start yourself: declare it with `roles` only (for example
|
|
3533
|
+
`"claude-review": { "kind": "claude", "roles": ["reviewer"] }`, no `start_mode` or
|
|
3534
|
+
`worktree`) and run `ahub claude --as claude-review` in a terminal. It keeps its own
|
|
3535
|
+
`claude-*.claude-review.json` state files.
|
|
3536
|
+
|
|
3537
|
+
A headless Claude reviewer is run by the hub instead (#223): declare
|
|
3538
|
+
`"claude-review": { "kind": "claude", "roles": ["reviewer"], "start_mode": "headless" }`
|
|
3539
|
+
and start it with `ahub claude --as claude-review`, which asks the running hub to start it (nothing runs in that terminal); `ahub stop claude-review` stops it. It holds no process while idle; each
|
|
3540
|
+
review request runs one new `claude -p` session (never resumed) that may only read
|
|
3541
|
+
the checkout and call `hub_review` and `hub_task_show`, with no user settings,
|
|
3542
|
+
hooks or plugins. It gets the review facts and a bounded diff of the head, never
|
|
3543
|
+
the builder's summary or agent chat, and the board records its verdicts as
|
|
3544
|
+
`reviewContext: fresh`. Review requests go to it first; while it is not started,
|
|
3545
|
+
the planner reviews and the task's assignment note says `reviewer=planner (fallback)`.
|
|
3546
|
+
A missing
|
|
3523
3547
|
`peer_instances` feature on an old daemon or upgrade target requires a supporting
|
|
3524
3548
|
version before launching or restoring extras.
|
|
3525
3549
|
|
package/docs/quickstart.md
CHANGED
|
@@ -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.12.
|
|
20
|
+
bun add -g github:STAIxBWLB/agent-hub#v0.12.36
|
|
21
21
|
ahub setup
|
|
22
22
|
```
|
|
23
23
|
|
|
@@ -30,17 +30,15 @@ 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.12.
|
|
33
|
+
## Upgrading to 0.12.36
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
Restart Claude sessions after upgrading: each keeps the plugin server it started with, and `hub_review` now takes `coverage`. Every review request for a non-private task with a commit carries a review brief the hub builds from that commit: reviewable files, excluded ones (`linguist-generated` in `.gitattributes`, lockfiles, binaries) and rules from `REVIEW.md` or an `ahub-review-rules` attribute, read at the merge base. `ahub review brief <id>` prints it (#520).
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
WIP admission now applies in every lifecycle mode, `off` included: an owner at its WIP limit (default one open task per peer) cannot accept or claim another task, while a redo of its own sent-back work keeps its slot. Raise it with `ahub settings set tasks.wip_limits.<peer> N` (applies at the next admission); a limit of 0 refuses every new acceptance for that peer (#481 S9).
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
Only in `enforce`: build work needs the person's `ahub sdlc accept <task> --issue <n>` and a current plan review (`hub_plan_review`, or `ahub sdlc plan-review`); an approval must account for every reviewable path of the brief; and a review request waits for the PR head, green required checks and answered bot threads. `advise` reports the same gaps; `off` adds none (#481, #520).
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
A task now moves to another peer after the third consecutive `changes_requested` instead of the second (#488). To keep the old threshold, run `ahub settings set tasks.escalate_after 2` in a terminal (1 to 10, applies at the next verdict), or set `"tasks": { "escalate_after": 2 }` in `.agenthub/config.json`. In `ask-when-needed`, Claude, Pi and `local` now ask before writing under `.agent`, `_agents`, `_agent`, `.gemini` or `.grok`, like other agent configuration (#485).
|
|
41
|
+
If the daemon refused a Jcode or Kimi TUI entry as stale because the CLI on PATH and the running hub came from different installs, run `ahub jcode --mode headless` or `ahub kimi` again (#502). For a second Claude session as reviewer or planner, or a headless Claude reviewer, see [instances](operations.md#additional-headless-peer-instances). A Claude session whose transcript passed 32 MiB no longer stays busy after its turns end (#517).
|
|
44
42
|
|
|
45
43
|
From 0.12.30 or earlier, existing Jcode project MCP entries need one refresh before conductor or dashboard starts. Once per project, the person runs:
|
|
46
44
|
|
package/docs/sdlc.md
CHANGED
|
@@ -12,9 +12,9 @@ The built-in lifecycle follows the [AI-Native SDLC Playbook](https://claude.com/
|
|
|
12
12
|
|
|
13
13
|
| Stage id | Playbook stage | Acting role | Artifact or exit gate | Current enforcement |
|
|
14
14
|
| --- | --- | --- | --- | --- |
|
|
15
|
-
| intent | Plan | person/planner | intent or backlog identity |
|
|
16
|
-
| spec | Design | person/planner | issue identity and scope |
|
|
17
|
-
| plan | Build | planner/owner | plan, branch, existing predecessor gates |
|
|
15
|
+
| intent | Plan | person/planner | intent or backlog identity | person spec acceptance required at build entry in enforce |
|
|
16
|
+
| spec | Design | person/planner | issue identity and scope | spec acceptance/body drift checked at build entry |
|
|
17
|
+
| plan | Build | planner/owner | plan, branch, existing predecessor gates | current planner review required at build admission; conformance pending S2 |
|
|
18
18
|
| build | Build | owner | PR identity/current head and local check on that head | entering review, when enabled |
|
|
19
19
|
| test | Test | verifier/reviewer | required checks and local gate on the reviewed head | approval, when enabled; integrity/evals pending S4/S5 |
|
|
20
20
|
| review | Deploy | assigned reviewer | current head-bound verdict, no pending change request/open thread; AC checklist under Compliance | approval, when enabled; structured findings pending S3 |
|
|
@@ -29,7 +29,7 @@ Stored `implement` and `verify` stage ids read as `build` and `test`. A write th
|
|
|
29
29
|
| Slice | Playbook play | Delivery in this slice |
|
|
30
30
|
| --- | --- | --- |
|
|
31
31
|
| S0 | Plan, Design, Build, Test, Deploy, Maintain stage model | Nine built-in bases, canonical ids, stored receipt aliases and playbook labels |
|
|
32
|
-
| S1 | Accepted intent/spec and spec drift |
|
|
32
|
+
| S1 | Accepted intent/spec and spec drift | Person-only `ahub sdlc accept`, bound to the issue's body edit time; build entry needs it in enforce (see Spec acceptance below) |
|
|
33
33
|
| S2 | Accepted plan and diff conformance | Pending; existing task plans remain authoritative without a conformance readback |
|
|
34
34
|
| S3 | Bugs, Security and Compliance review passes | Pending structured findings; existing reviewer separation and head-bound review remain |
|
|
35
35
|
| S4 | Red-first tests and test integrity | Pending receipts/gates; repository test-integrity rules still apply |
|
|
@@ -37,6 +37,8 @@ Stored `implement` and `verify` stage ids read as `build` and `test`. A write th
|
|
|
37
37
|
| S6 | Production approval gate | Pending person-only release authorization; release report stays read-only |
|
|
38
38
|
| S7 | Metrics, deterministic control bands and follow-up intent | Pending; no automated metric triage or security baseline claimed |
|
|
39
39
|
| S8 | Compliance acceptance-criteria checklist | Head-bound checklist retained; legacy workflow and phase compatibility deprecated; task import removed |
|
|
40
|
+
| S9 | Planner grooming and plan review | Read-only digest, current plan-review receipt at build admission, all-mode WIP and accepted-untouched return |
|
|
41
|
+
| S10 | Planner cost governance | Typed review/CI scope, premature-review admission, identical-tree CI reuse and read-only cost report |
|
|
40
42
|
|
|
41
43
|
S0/S8 establish vocabulary and checklist compatibility only. Later slices must retain the same authority contract: the issue is the spec, Tasks owns mutations, GitHub owns PR/check/tag facts, and the hub stores bounded identities, timestamps, hashes and counts instead of remote bodies. No slice writes GitHub or grants capabilities. Advise shows readiness without blocking; enforce applies only the named implemented gates. A full live advise cycle through S1, merge, release and S6 authorization remains pending and cannot be certified by S0/S8 source tests.
|
|
42
44
|
|
|
@@ -62,7 +64,7 @@ The acceptance-criteria checklist belongs to the Compliance pass over the same b
|
|
|
62
64
|
|
|
63
65
|
## Delivery and verification state
|
|
64
66
|
|
|
65
|
-
Head-bound review/approval gates, bounded custom definitions, read-only merge/release reports
|
|
67
|
+
Head-bound review/approval gates, bounded custom definitions, read-only merge/release reports, S0/S8 vocabulary/checklist migration, S1 spec acceptance and S9 planner admission and S10 cost governance are implemented in source. S2-S7 and S11 and the native advise-through-merge/release cycle remain pending. Installation, native acceptance and release authorization require separate receipts.
|
|
66
68
|
|
|
67
69
|
## Initial commands
|
|
68
70
|
|
|
@@ -72,6 +74,12 @@ A person records ids and a claimed local receipt with `ahub sdlc record 12 '{"re
|
|
|
72
74
|
|
|
73
75
|
`ahub sdlc status` lists bounded local projections without polling every remote PR. `ahub sdlc explain 12` or `status 12` performs a fresh structured readback, with a console-only boundary and no remote body retention. Shared console/dashboard projections explicitly show unknown external evidence; they do not certify freshness from cached data. Default task rows and contracts remain unchanged when off.
|
|
74
76
|
|
|
77
|
+
# Spec acceptance (#481 S1)
|
|
78
|
+
|
|
79
|
+
- The person accepts a task's spec issue with `ahub sdlc accept <task> --issue <n> [--repo owner/repository]` (the repository defaults to the task's receipt). Agent shells and live tests are refused. The hub reads the issue's `lastEditedAt` and `updatedAt` through a fixed GraphQL query and stores them with the issue identity and the time of acceptance; it never reads or stores the body. `hub_sdlc_record` cannot write an acceptance, and relinking the receipt to another repository or issue drops it.
|
|
80
|
+
- Build entry is the `implement` or `bulk_edit` task's acceptance (`hub_task_accept`), or a recorded stage change into a build stage by anyone but the person. In enforce, build entry needs a current acceptance: the hub rereads the issue and refuses with `spec acceptance missing`, `spec changed after acceptance` (the body was edited; comments only move `updatedAt` and do not count), `spec edit time unknown` (the readback failed) or `spec issue unknown` (the receipt names no issue). Advise posts the same text as a notice and lets the work start; off reads nothing.
|
|
81
|
+
- `ahub sdlc status <task>` and `explain` show `spec: {issue, state}` (`missing`, `current`, `changed` or `unknown`) when the lifecycle is enabled and the receipt names an issue. The spec state is not a merge-readiness blocker: its gate is build entry.
|
|
82
|
+
|
|
75
83
|
# Acceptance-criteria checklist
|
|
76
84
|
|
|
77
85
|
- A task's reviewer or the person records a head-bound `criteria` receipt through `hub_sdlc_record` or `ahub sdlc record` (an off or older daemon's tool schema still offers `phase`). Its ids name the issue's acceptance criteria. The board owns the receipt and its actor/history; no writable STATE/PLAN/HANDOFF file is introduced.
|
|
@@ -193,3 +201,36 @@ After dashboard authentication, those two delivery operations get a 35-second HT
|
|
|
193
201
|
The connection idle setting is not a guaranteed 10-second deadline for an active handler. The actual HTTP fixture records that an ordinary action also completes after 11.5 seconds; that legacy behavior is retained. The owner approved correcting only the newly added test's mistaken ordinary-action failure expectation. Manager control request caps are separate from HTTP connection idle behavior. Delivery HTTP results omit the internal control-request rid.
|
|
194
202
|
|
|
195
203
|
- Runtime doc checker ships explicitly in the npm tarball and package verification asserts its presence. Merge queue criteria readiness derives each holder's current board checklist view; absent or blocked required checklists remain unknown/blocked.
|
|
204
|
+
|
|
205
|
+
# Planner plan review (#481 S9)
|
|
206
|
+
|
|
207
|
+
A configured planner reads `hub_planner_groom` (or the person runs `ahub backlog groom`) before choosing build work. The digest reports missing text-only dependencies, repository-scoped duplicate issues and path/symbol overlaps, over-WIP owners, recorded supersession, issue edit/closed-state evidence and capability, queue or offline blockers. It proposes no board mutation. Missing repository or issue evidence stays unknown; the planner still evaluates issue rulings and the person's priorities. Private task text and references are withheld.
|
|
208
|
+
|
|
209
|
+
The planner records `hub_plan_review` with the task id, current `planningRevision` and a reason. The person's CLI is `ahub sdlc plan-review <task> <revision> <reason...>`. Tasks records the actor, planning revision, observation time and host-read issue identity/edit time. The fixed GraphQL query requests metadata only, never an issue body or comments; comment-only activity does not invalidate the body-edit receipt. Caller-supplied timestamps are refused. A changed revision or issue edit requires another review; unavailable metadata never counts as a current review. The native CLI cannot delegate this person-attributed write through `--live-test`.
|
|
210
|
+
|
|
211
|
+
For implementation/bulk-edit tasks and an explicit build-base stage, enforce refuses an offer or acceptance without a current receipt. Advise reports the gap and off performs no lifecycle gate. Spec acceptance and planner review are independent readbacks bound to one task snapshot before acceptance. The ordinary synchronous acceptance path cannot bypass the enforce readback. A self-claim can remain assigned and proposed while its prerequisites are missing, with no offer or accepted event. A saved planner review can release previously held proposed work; delivery failure leaves that saved receipt intact.
|
|
212
|
+
|
|
213
|
+
Two board rules apply in every lifecycle mode:
|
|
214
|
+
|
|
215
|
+
- Acceptance and self-claim, including a `done` that implicitly accepts proposed work, respect the owner's effective planner WIP limit. The target task is excluded when checking capacity, and updating an already accepted plan or resubmitting changes_requested work does not consume another slot, provided its current owner accepted it after the latest ownership change. Another unaccepted offer cannot block that redo. In enforce, a changes_requested resubmission still revalidates spec acceptance and the plan review; an issue body edit requires the person to accept the revised spec and the planner to review it again before re-acceptance or done. A refusal names the owner, limit and open task ids before a board write. The existing planner capacity accounting includes owned open work and active planner-pull reservations.
|
|
216
|
+
- A planner or the person can return accepted but untouched work with `hub_backlog_return_untouched`, or `ahub backlog return <task> <revision> <reason...>`. A done/check/review/integration entry or recorded branch, commit, PR or local/review head refuses the return. History and planning fields remain; former role requests are withdrawn through the existing delivery path. A successful return permits replanning and pulling again without erasing historical acceptance. General unassignment and cancellation of accepted work remain person-only. Planner changes to pinned or frozen work remain refused.
|
|
217
|
+
|
|
218
|
+
Enabled lifecycles send an attached planner one grooming pointer for a changed board revision; a queued pointer is not duplicated. Proposal, approval and planning writes flow through the existing revision and task-sweep notice path. External issue changes are read during grooming, offering and accepting, rather than by a separate watcher.
|
|
219
|
+
|
|
220
|
+
The digest is bounded to 500 task rows (open work plus structured cancelled supersession evidence) and 256 findings, with at most eight issue metadata reads sharing four seconds. Spec acceptance and planner-review staleness are compared independently. Truncation and unavailable evidence are explicit. Qualified foreign repositories are compared within their own key. Without a proven repository key an absolute outside-project path remains unknown; the dedicated repository-overlap resolver is tracked in #476.
|
|
221
|
+
|
|
222
|
+
S9 ceilings: a private build task with an issue reference cannot satisfy enforce admission: privacy forbids the external issue metadata read, and no person override for a metadata-free plan receipt exists. Grooming pointers are coalesced only while queued, per board revision; there is no minimum time interval after delivery, so a busy board may produce one delivered pointer per revision.
|
|
223
|
+
|
|
224
|
+
# Planner review and CI cost governance (#481 S10)
|
|
225
|
+
|
|
226
|
+
At plan review, batch small related fixes into one area branch and PR; keep release preparation separate from feature work. A plan can name `review: {scope: "none" | "delta" | "full", reviewers: N}` and `ci: "focused" | "full" | "reuse"`. None requires zero reviewers; delta/full require one to eight. These are expected costs, not permission to bypass the task's reviewer, CI or privacy gates. Use one reviewer for a small change; split a large review by area with one receipt owner. After a fix, review the delta against the previous findings. S2 will compare planned scope with completed coverage.
|
|
227
|
+
|
|
228
|
+
The planner can provide scope with `hub_plan_review`; the person can run `ahub sdlc plan-review <task> <revision> <reason...> --review full --reviewers 1 --ci full`. Tasks records the explicit scope on the plan and its revision-bound receipt. A file-list update retains omitted cost fields; an explicit scope change cannot replace a reviewed scope during enforce acceptance. Old plans and receipts remain unspecified and are never filled by guessing. Advise reports missing scope. In enforce, every newly recorded receipt needs both scopes; an existing scope-less receipt remains valid until its planning revision changes. Enforce acceptance cannot add owner-supplied cost keys to that legacy receipt; the planner must record an explicit scope first.
|
|
229
|
+
|
|
230
|
+
A review request is premature if its task head does not match the PR head, configured required checks are not green, or a pending bot thread is unanswered. A resolved thread is settled; an unresolved one needs a user reply after its last bot comment. The hub reads identity, timestamps, authorship type and resolution only, never comment bodies. Without explicit required checks the native floor is both OS checks plus `seeded guards`. Advise reports the gap; enforce refuses before the requesting done/cohort writes. A private turn may finish its own PII task in advise with a generic unknown-readiness notice and no remote metadata read; the private-turn refusal for ordinary work remains. Delayed checks revalidate before completing. Background roster/quota role changes remain fact, but enforce holds a new review envelope without an admission readback; refresh it through a manual reviewer assignment after CI and replies are ready. The brief and verdict coverage belong to #520, separate from this admission.
|
|
231
|
+
|
|
232
|
+
CI controllers are serialized across the repository for the entire workflow, without canceling an admitted controller. `queue: max` preserves up to 100 waiting controllers instead of replacing the previous pending run ([GitHub concurrency documentation](https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/control-workflow-concurrency)). This is intentionally stronger than a tree-only lock and retains the required job names and read-only token permissions. Before PR or push gates, `scripts/ci-reuse.mjs` reuses successful full proof for the identical Git tree, including its workflow. On reuse the two OS matrix jobs still expand and report their exact required names, while the seeded aggregate reports its exact name. They run only a `Reuse verified identical-tree full gate` marker step naming the original proof; full setup and gates are skipped. Skipped jobs and successful reuse-reporting jobs never count as fresh full proof. A running identical-tree gate refuses duplicate admission; unknown active coverage refuses safely. Metadata reads are bounded to 100 recent successes/active runs and 240 calls within 60 seconds. Pending serialized controllers are not admitted gates. Run focused tests locally and one full gate at the final head; a known flake warrants only one rerun of its failed job with recorded evidence.
|
|
233
|
+
|
|
234
|
+
`hub_sdlc_costs` and the person's `ahub sdlc costs [owner/repo]` report linked merged PRs without changing the board. A review round is a board done-to-verdict handoff, not each reviewer in a multi-reviewer round. An observed handoff still counts when its declared scope was none. The report counts distinct check-workflow run ids, extra attempts (`run_attempt - 1`), full re-reviews, delta rounds and summed review intervals, with GitHub review counts alongside them. Unspecified scope and unavailable evidence stay unknown/incomplete. Only current public links are projected; a private counterpart withholds the whole PR. Up to 16 linked PR reads share 20 seconds; each PR covers at most 100 commits, reviews and recent check-workflow runs, and the pure aggregator bounds 500 tasks, 200 PRs and 10,000 history entries. Truncation is explicit. S10 exposes pure band-breach proposal data; S7 owns rolling baselines, band configuration, automatic scheduling and board-card creation.
|
|
235
|
+
|
|
236
|
+
S10 policy ceilings are tracked in hub task #230: metadata failures remain fail-closed; the repository-wide queue can delay newer heads and releases; unknown thread author kinds remain unknown in enforce; and advise still retries concurrent state/authority changes. These policies are deferred, with no silent relaxation in this slice.
|
package/docs/security.md
CHANGED
|
@@ -169,7 +169,7 @@ Please report vulnerabilities privately through GitHub's "Report a vulnerability
|
|
|
169
169
|
- A settings session is as strong as the console for these settings and no stronger elsewhere. A mode change is the console's own request with the same refusals; tool approvals stay deny-only for Pi and local; there is still no shell, file, task-detail or launch-command API. `ahub ui --settings` and `ahub settings` pass the same identity gate as `ahub permission`, which is an operating policy, not authentication. The hostile-agent boundary above applies unchanged: an agent with an unrestricted shell as the same user can impersonate the console, and so can open a settings session.
|
|
170
170
|
- Every change writes a `settings` event (the key, the value before and after, the source `dashboard` or `terminal`) and a line in `hub.log` and open consoles; opening a settings session is logged too. The unified dashboard (`ahub ui --all`) forwards settings actions as an ordinary session and opens no settings session.
|
|
171
171
|
- Configuration reload (issue #439). The daemon watches `config.json`, `config.local.json`, `routing.toml` and `routing.local.toml` and re-reads the complete candidate after a change, as the dashboard's Reload configuration and `ahub settings reload` do; an invalid candidate applies nothing. Each changed key is then authorized on its own against its file's actual git provenance. A privacy tightening (local-only PII, the model screen on, added PII patterns, narrower roles or capability lists, unless it moves the planner or conductor) and a safe registered setting apply from any file. Every other change, machine-local fields included, keeps its running value when its file is tracked or git cannot answer. From an untracked file such a guarded change is adopted only by `ahub settings reload` in an interactive terminal, after it lists each guarded key with its full before and after values and the person types `y` within 30 seconds; the confirmation is bound to the file contents and accepted state it was shown for. File watches and dashboard reloads, a settings session's included, never confirm, and no reload changes the roster: membership moves only through the roster setting. Keys whose consumers are bound at start (memory, the gateway and MLX, checks, the privacy and sandbox policy of existing sessions, the terminal provider and others) stay pending until a restart.
|
|
172
|
-
- Start modes. A start the hub makes itself (the dashboard's Start control, `peer_start` from a console child, the conductor's `hub_peer_start`, `pi.auto_start`) takes the peer's start mode. Through settings only a settings session or the terminal can set it to headless; a config file can also set `peers.<id>.start_mode`, the shared one included (it is not a machine-local field: headless was the only form for Pi, Kimi and the local worker before this change, a hub-made headless Codex start is the app-server the hub already ran for it, and a start mode grants nothing a permission mode or a capability controls). A declared extra instance accepts only `headless` from any surface. For a TUI the hub opens a terminal running `<runtime> <its own entry point> --project <this project> <peer>` (and `--mode tui` for Pi): every word is the hub's own, the command line is those words each quoted for a POSIX shell, and no request field, tool argument or agent message is part of it. In a `terminal.open` template `{command}` is that quoted line and `{title}` and `{cwd}` are raw text for argv elements of their own; each placeholder is substituted once, so a project path that itself contains a placeholder, a quote or `$` stays one word. A provider's own error text goes to `hub.log`, not to the caller. The terminal comes from `terminal.open`, a machine-local command template that config trust refuses from a committed file and that no settings surface can write, or from Orca. The opened terminal's environment carries no agent marker, so `ahub` there has the console's identity. Without a provider the start is refused with the command to run by hand; it is never downgraded to headless. A conductor can start a peer but cannot choose its mode.
|
|
172
|
+
- Start modes. A start the hub makes itself (the dashboard's Start control, `peer_start` from a console child, the conductor's `hub_peer_start`, `pi.auto_start`) takes the peer's start mode. Through settings only a settings session or the terminal can set it to headless; a config file can also set `peers.<id>.start_mode`, the shared one included (it is not a machine-local field: headless was the only form for Pi, Kimi and the local worker before this change, a hub-made headless Codex start is the app-server the hub already ran for it, and a start mode grants nothing a permission mode or a capability controls). A declared extra instance accepts only `headless` from any surface; a Claude channel instance accepts no start mode, and a headless Claude instance must have roles exactly `["reviewer"]` and no worktree. For a TUI the hub opens a terminal running `<runtime> <its own entry point> --project <this project> <peer>` (and `--mode tui` for Pi): every word is the hub's own, the command line is those words each quoted for a POSIX shell, and no request field, tool argument or agent message is part of it. In a `terminal.open` template `{command}` is that quoted line and `{title}` and `{cwd}` are raw text for argv elements of their own; each placeholder is substituted once, so a project path that itself contains a placeholder, a quote or `$` stays one word. A provider's own error text goes to `hub.log`, not to the caller. The terminal comes from `terminal.open`, a machine-local command template that config trust refuses from a committed file and that no settings surface can write, or from Orca. The opened terminal's environment carries no agent marker, so `ahub` there has the console's identity. Without a provider the start is refused with the command to run by hand; it is never downgraded to headless. A conductor can start a peer but cannot choose its mode.
|
|
173
173
|
|
|
174
174
|
## Multi-project manager (protocol 7)
|
|
175
175
|
|
|
@@ -341,6 +341,10 @@ Stall causes are persisted and deduplicated by cause and replanning revision. Pl
|
|
|
341
341
|
|
|
342
342
|
`hub_delivery_retry` grants an eligible recipient one retry of its own `needs_review` work, never completion/discard authority or pause removal. Durable per-original evidence survives journal pruning; unknown retry history or a used retry requires the person. Hub presence prefaces are excluded from attempted-work identity, so a shared context preface does not consume an unrelated message's retry. Delivery-only local and ACP peers (including Jcode) cannot invoke recipient retry; tool exposure hints do not replace the daemon's actual-adapter guard. Their held work requires person resolution. Before transport handoff, storage failure preserves the queue and preface; after handoff an uncertain effect is never automatically replayed.
|
|
343
343
|
|
|
344
|
+
A declared instance whose only role is reviewer gets review requests, task messages addressed to it and the person's messages; agent chat, digests and presence are discarded and journaled, and it gets no memory recall (#223). The headless Claude reviewer runs one new `claude -p` per review with `--setting-sources ""`, `--strict-mcp-config`, `--no-session-persistence`, tools limited to Read, Grep, Glob, `hub_review` and `hub_task_show`, and `dontAsk`. Its prompt holds hub facts and a bounded diff, never the builder's summary, and its `hub_task_show` drops the builder's notes. Changes touching `local.deny` or `pii.paths` are withheld and passed as `Read` deny rules, which Claude enforces, not the hub. Private tasks are never given to it. Its `hub_review` counts only while the child started for that task still runs; a stop or exit revokes it first, and surviving processes hold the delivery as `needs_review` and keep the reviewer busy.
|
|
345
|
+
|
|
346
|
+
The review brief (#520) withholds private changes whole (both sides of a move) and counts them; a rule file that fails the private-data screen is named, not shown. Attributes and rules are read at the merge base, so a change cannot loosen its own review. Spec acceptance, plan review, premature-review and cost reads fetch GitHub metadata only (edit times, check states, thread authorship and resolution), never issue bodies or comments. The CI workflow keeps `contents: read` and `actions: read`.
|
|
347
|
+
|
|
344
348
|
## Bounded task specs and isolated checks (#418)
|
|
345
349
|
|
|
346
350
|
`hub_task_spec` reads only the GitHub issues named by a current owner's or reviewer's task in this project's origin repository, including the validated canonical GitHub intake source when its title/body has no issue token. Bare `#N` references count only in the title; detail references require `owner/repo#N` or `https://github.com/owner/repo/issues/N`. Bare detail numbers denote hub tasks, and arbitrary file paths are not parsed as issue references. A qualified reference cannot begin after a slash or a name character, so `src/hub/tasks.ts#12` is a file path. Routing uses this same parser. Without a GitHub project identity, bare title numbers do not require spec access, but explicit qualified/URL references, `refs.repo` and imported GitHub identity still fail closed. The daemon resolves authenticated and container executables outside the project, pins their absolute paths and uses fixed arguments; repository-writable PATH shims are refused. Runner Git resolves its host executable through a restricted PATH. For specs, the model receives no credential or arbitrary authenticated URL capability. Foreign/transferred issues, more than three references, bodies over 64 KiB and aggregate results over 80 KiB fail explicitly. Spec fetches share a 25-second budget inside the control client's 30-second cap; each gh call remains bounded to 15 seconds. Current PII, secret and private-path policy withholds the complete spec rather than returning a partial contract. PII turns and PII tasks refuse these tools. The independent `spec_read` and `check_run` capabilities apply when a peer has a narrowed capability list. Arguments and results are excluded from capture. Proven deterministic pre-effect guards return PreEffectToolRefusal so Pi settles the exact refusal receipt; generic transport/storage failures and failures after durable receipt/output changes remain uncertain, never reclassified from error text.
|