@staix/agent-hub 0.12.35 → 0.12.37
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 +25 -0
- package/README.md +15 -8
- package/docs/agent-notes/adapters.md +9 -2
- package/docs/agent-notes/budget.md +3 -2
- package/docs/agent-notes/bus.md +3 -0
- package/docs/agent-notes/daemon.md +11 -3
- package/docs/agent-notes/tasks.md +14 -1
- package/docs/agent-notes/tests.md +6 -2
- package/docs/memory.md +3 -1
- package/docs/operations.md +74 -18
- package/docs/quickstart.md +7 -9
- package/docs/sdlc.md +72 -5
- package/docs/security.md +11 -3
- package/docs/smoke.md +18 -2
- package/docs/specs/2026-09-19-agent-hub-design.md +108 -3
- package/docs/verification/2026-10-19-0.12.36.md +63 -0
- package/docs/verification/2026-10-19-0.12.37.md +60 -0
- package/docs/verification/2026-10-19-jcode-0.98.1.md +30 -0
- package/docs/verification/2026-10-19-reviewer-isolation.md +119 -0
- package/docs/verified.json +23 -15
- package/package.json +2 -2
- package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-hub/server.js +2843 -1632
- package/src/adapters/acp.ts +3 -2
- package/src/adapters/claude-print.ts +92 -0
- package/src/adapters/codex-review-isolation.ts +276 -0
- package/src/adapters/definitions/builtin-metadata.ts +20 -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 +24 -0
- package/src/adapters/definitions/native-policy-aux.ts +1 -1
- package/src/adapters/definitions/reviewer-variants.ts +32 -0
- package/src/adapters/jcode.ts +20 -5
- package/src/adapters/registry-types.ts +13 -3
- package/src/adapters/registry.ts +29 -5
- package/src/adapters/reviewer-print.ts +319 -0
- package/src/cli/help.ts +7 -0
- package/src/cli/identity.ts +2 -1
- package/src/cli/launch.ts +19 -6
- package/src/cli/main.ts +57 -4
- package/src/cli/memory.ts +13 -12
- 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 +61 -11
- package/src/hub/bus.ts +11 -5
- package/src/hub/claude-state.ts +37 -0
- package/src/hub/config-apply.ts +3 -2
- package/src/hub/control-client.ts +1 -1
- package/src/hub/cost-governance.ts +76 -0
- package/src/hub/daemon.ts +269 -87
- package/src/hub/envelope.ts +1 -1
- package/src/hub/host-executable.ts +1 -1
- package/src/hub/hub-tools.ts +25 -1
- package/src/hub/instances.ts +29 -5
- package/src/hub/memory-profile.ts +67 -11
- package/src/hub/planning.ts +2 -0
- package/src/hub/release-admission.ts +64 -0
- package/src/hub/review-brief.ts +175 -0
- package/src/hub/reviewer-pool-config.ts +11 -0
- package/src/hub/reviewer-pool.ts +70 -0
- package/src/hub/routing.ts +58 -4
- package/src/hub/sdlc-release.ts +113 -0
- 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 +788 -52
- package/src/hub/usage.ts +57 -20
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
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.37
|
|
6
|
+
|
|
7
|
+
Hub memory follows agent-mem 0.4.0 onto per-project shards (#534, #537), reviews rotate across fresh Claude and Codex sessions (#519), and `ahub jcode` starts again on current jcode releases (#529). Before running `amem migrate --shards`, upgrade every hub that shares the memory home to 0.12.37 or stop it.
|
|
8
|
+
|
|
9
|
+
- Reviews rotate across fresh Claude and Codex reviewer sessions (#519). A declared Codex reviewer instance opts in with `review_mode: "fresh"` and runs `codex exec` under a pinned isolation policy: plugins, hooks, ChatGPT Apps and web search disabled, a native read-only sandbox, denied credential roots, and only `hub_review` and `hub_task_show` from the hub. The start index follows the PR or task id, the implementer's family is skipped while another family can run (a separate fresh same-family instance is the last fallback, and a re-review keeps the family that requested changes), and an unavailable reviewer moves the walk on; it never counts as a pass. When no pool is configured and no fresh reviewer can run, the planner reviews as before; an explicitly required pool blocks for the person. Existing reviewer-role instances keep their native transport. Kimi is not offered yet, and agy is an implementer peer (#485), not a reviewer.
|
|
10
|
+
- Hub memory binds the alias-resolved project key that the `amem` CLI uses, so after `amem alias add` the daemon and `ahub memory` read and write the same shard and rows as standalone amem. A dangling or unreadable `shards.json` now counts as a split marker: new opens refuse with the resume remedy and an open unsplit binding latches unavailable. An alias added while a hub runs latches that hub's memory unavailable until it restarts (#237 review).
|
|
11
|
+
- Plan release trains through the board's release assignments, move missed work with a reason, and freeze approved scope through host resource admission and fresh peer quota checks. Hotfix causes and cut/shipment history are recorded; person upgrades retain original recovery readiness and train target compatibility (#481 S11).
|
|
12
|
+
- `ahub jcode` starts again: the adapter accepts any jcode release from 0.98.0 up to, not including, 1.0.0 (the assumed breaking version), so a jcode minor update no longer refuses the peer; releases before 0.98.0 are refused. The owned daemon's private socket is now named `j.sock`: jcode derives its debug socket by replacing `.sock` in the name, so the old name `s` made both binds one path and `serve` exited on EADDRINUSE with every jcode version. Every owned jcode process (the doctor version probe, `serve` and `acp`) now runs with `JCODE_NO_TELEMETRY=1`: jcode 0.98.1's telemetry policy can report the provider account's email. Verified live on 0.94.0 and 0.98.1 (#529).
|
|
13
|
+
- Hub memory opens a split memory home (agent-mem#15 T2, `amem migrate --shards`) through the project's own shard, with the user shard as its overlay. Before this, a hub pinned agent-mem 0.3.1, which does not detect a split and keeps using the old layout. An inactive split refuses with the `amem migrate --shards --resume` remedy, and an unsplit home opens exactly as before. The exact `@staix/agent-mem` pin rises to 0.4.0, the first release with T2 (agent-mem#15).
|
|
14
|
+
- CI: the macOS check gate gets a 960 s hang bound inside a 20-minute job ceiling; its test step had grown to 650-808 s (#538).
|
|
15
|
+
|
|
16
|
+
## 0.12.36
|
|
17
|
+
|
|
18
|
+
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.
|
|
19
|
+
|
|
20
|
+
- 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).
|
|
21
|
+
- 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).
|
|
22
|
+
- 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).
|
|
23
|
+
- 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).
|
|
24
|
+
- 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).
|
|
25
|
+
- `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).
|
|
26
|
+
- 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).
|
|
27
|
+
- 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).
|
|
28
|
+
- 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).
|
|
29
|
+
|
|
5
30
|
## 0.12.35
|
|
6
31
|
|
|
7
32
|
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.37, 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,22 +77,29 @@ 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.37; 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). Since 0.12.37 the hub accepts jcode
|
|
83
|
+
0.98.0 or later, below 1.0.0, and no longer 0.94.0 (#529).
|
|
82
84
|
Cancellation needs a daemon advertising `task_cancel`; controlled downgrades
|
|
83
85
|
refuse while cancelled rows exist. For local/Pi implementation, opt in to an
|
|
84
86
|
available `checks.runner = "container"`; issue-backed owners also need authenticated
|
|
85
87
|
spec access. PII work and designated reviewers retain their separate exemptions.
|
|
86
|
-
See [release verification](docs/verification/2026-10-
|
|
88
|
+
See [release verification](docs/verification/2026-10-19-0.12.37.md) for the live legs
|
|
87
89
|
still open, including #418 AC8, #438 AC7, the native console settings leg (#439 AC5),
|
|
88
|
-
|
|
89
|
-
|
|
90
|
+
real upgrades with reattach (#463) and with a managed terminal and cancelled tasks
|
|
91
|
+
(#478), a live headless Claude review (#223), a Claude session over 32 MiB (#517), a
|
|
92
|
+
live fresh Codex review (#519) and a live split of a real memory home (#536).
|
|
90
93
|
|
|
91
94
|
Before migrating a memory home with `amem`, upgrade every hub using that home to
|
|
92
95
|
0.12.30 or later, check `ahub memory profile-check`, then quiesce and close all memory
|
|
93
96
|
consumers. Shared v1 remains the default; installing this release does not migrate
|
|
94
|
-
data. The curated shard migration
|
|
95
|
-
|
|
97
|
+
data. The curated shard migration (`amem migrate --shards`, agent-mem 0.4.0) needs
|
|
98
|
+
every hub using that home to run 0.12.37 or later, which opens memory through shards
|
|
99
|
+
(#534). An older hub (0.12.36 or earlier, agent-mem 0.3.1) does not detect the
|
|
100
|
+
split: it keeps reading and writing the old layout, and its writes reach the shards
|
|
101
|
+
only through `amem migrate --shards --resume`. Upgrade or stop every such hub before
|
|
102
|
+
splitting. `ahub memory profile-check` is not split-aware yet (#536). See [memory profiles and rollout](docs/memory.md#selected-memory-profile).
|
|
96
103
|
|
|
97
104
|
## Documentation
|
|
98
105
|
|
|
@@ -33,7 +33,7 @@ Scope: the Codex, ACP and Pi adapters and the Pi extension (turn `generation` an
|
|
|
33
33
|
- Pi's bridge executes `/tool` only for the verified owner (`verifiedOwner()`: the claimed pid with its `processSignature`; a headless owner is pinned from the spawned process once `get_state` proved its session) with the current `sessionId` and turn `generation`, and admits `/budget` only for that owner and generation, checked again after the awaited admission. Refuse anything else before execution. `tool_abort` aborts that pending call, or fences its id when it arrives before the call (64 ids a turn, then every further call of that turn). A person's `!command` (`purpose: "idle_user_bash"`) is taken only in TUI mode from an idle owner on its one-use reservation, and it alone skips `piPermit`: never widen that to a model's bash call (#253).
|
|
34
34
|
- Pi and local approvals (#253): `permit` answers a `ToolApproval` (contract: `local-worker.md`) and `requestPermission` (`src/hub/daemon.ts`) tells the adapter's `ApprovalObserver` what settled it. An expiry counts toward the stop only as `source: "expired"` with `eligibleExpiry` not false (no person answered a request of that peer while it was pending); `person` resets the count, `automatic` (a mode grant, Pi's `always`) never does, and a new turn starts it over. The second one ends the turn, never the peer: local throws `ApprovalWaitStop`, Pi sends `abort_budget` with `cause: "approval"`, and both settle the delivery `completed` with the reason; a Pi whose extension did not announce `approvalTurnAbort` goes offline with the delivery held. `requestPermission` deletes a request from `permissions` before it runs an observer, resolves or reports, and an aborted signal closes it as `cancelled`: a late answer must find nothing to grant, and a failed event, notice or console push never strands the caller.
|
|
35
35
|
- `stop(reason?)` (`src/hub/peers.ts`): a reason marks a person's requested stop (`peer_stop`, rule: `daemon.md`). Fence before the first await (the adapter's stopping flag, or `offline`) so `deliver()` and buffered native callbacks do nothing more, hold the in-flight delivery as `needs_review` with that reason, and end `offline`. For ACP without a journal, return ordinary in-flight envelopes once through `onFailed` before a requested stop invalidates the turn; never return the watchdog-cancelled original prompt. A requested stop never completes, escalates or reports the turn it cut (`AcpPeer` bumps `turn`; `PiPeer.fail` skips the failure message and `onTurnFailure` while `requestedStopReason` is set). `PiPeer.stop()` waits for aborted tools only up to `stopGraceMs`: a tool past it never blocks the teardown, and the stop throws `PiToolSettlementFailure` once the owner is down.
|
|
36
|
-
- A terminal the hub opens for a person (`src/hub/terminal-open.ts`, #269) gets `personEnv`, which strips every agent marker; never `peerChildEnv`, which sets one. Adding a native peer means registering its `AdapterDefinition` in `ADAPTERS` and listing its id in `KNOWN_PEERS` or `OPTIONAL_PEERS` and `ROLE_ORDER`, all in `src/adapters/registry.ts` (`isSupportedPeer`, `isKnownPeer` and `defaultPeerRoles`
|
|
36
|
+
- A terminal the hub opens for a person (`src/hub/terminal-open.ts`, #269) gets `personEnv`, which strips every agent marker; never `peerChildEnv`, which sets one. Adding a native peer means registering its `AdapterDefinition` in `ADAPTERS` and listing its id in `KNOWN_PEERS` or `OPTIONAL_PEERS` and `ROLE_ORDER`, all in `src/adapters/registry.ts` (the registry's `isSupportedPeer` answers from `ADAPTERS`, `isKnownPeer` and `defaultPeerRoles` from `definitionOf`; `STARTABLE_PEERS` reuses `SUPPORTED_PEERS`); if it has both a TUI and a headless form, also add it to the registry's `START_MODE_PEERS`, with its fixed words in its definition's `cli.startWords`. Mirror `KNOWN_PEERS`, `OPTIONAL_PEERS` and `START_MODE_PEERS` in the literal cross-release copies in `src/hub/peer-catalog.ts` and `src/hub/start-mode.ts` (`ROLE_ORDER` has none). Adding a peer is the one change that updates their pinned counts in `test/registry-guard.test.ts` and the literal in `test/adapter-catalog-a2.test.ts`; say so in the PR as an expected test change.
|
|
37
37
|
- Spawn a native peer with `peerChildEnv(peer, source)` so its own `AGENTHUB_PEER_ID` replaces inherited agent markers. Sandbox tool environments carry their executing peer too. Preserve the native shell environment policy; Codex's `CODEX_THREAD_ID` supplies the fallback after filtering. Before changing marker detection or launcher environments, read `docs/verification/2026-10-09-agent-shell-t0.md` for pinned source and live-probe limits.
|
|
38
38
|
- Emit Pi's ceiling event at the pre-effect rejection boundary (`src/pi/ceiling.ts`), before `agent_end`. Accept only a valid current-session/current-generation signal during a running turn; retain the first signal and attach it to that turn's failure. Never classify a ceiling from free text.
|
|
39
39
|
|
|
@@ -49,7 +49,14 @@ 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`, `reviewerVariant` or `reviewer`. Only `definitionOf(id, instances)` picks a kind's `reviewerVariant`, for a declared extra with `start_mode: "headless"`, roles exactly `["reviewer"]`, and either a variant whose `reviewer.declaration` is `"automatic"` (Claude, agy) or `review_mode: "fresh"` (Codex, Kimi); without that opt-in a reviewer-only Codex or Kimi instance keeps its native adapter. A declaration-only definition (`AGY_DEFINITION`) is registered for `definitionOf` but stays out of `ADAPTERS`, `KNOWN_PEERS` and `OPTIONAL_PEERS`. 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
|
+
- Jcode (#529): accept the native version only through `jcodeVersionSupported` (from `JCODE_MIN_VERSION` up to, not including, `JCODE_BREAKING_VERSION`), in both the version probe and ACP `expectedAgent`; never pin the minor, and lower the breaking version when a release breaks the contract. Every owned jcode process (probe, serve, ACP) gets `JCODE_OWNED_ENV`, and the private socket name ends in `.sock`.
|
|
59
|
+
- `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.
|
|
60
|
+
- 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, `readOnly`, `strictMcp` and `ephemeral` true) before every child, and `launch.current()` is rechecked right before spawn; 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`. A placeholder (`reviewer.unavailable`: Kimi, agy) has no backend or launch code; `startPeerBody` refuses it before preflight.
|
|
61
|
+
- 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`.
|
|
62
|
+
- Codex reviewer isolation (`src/adapters/codex-review-isolation.ts`): accept only the pinned `codex-cli` version; prove model-free that Apps, plugins and hooks are disabled (`features list`), ambient MCP servers are replaced by disabled stubs, and the hub server alone is enabled with exactly `hub_review` and `hub_task_show`; run the native sandbox fixture probe before every launch; hash the binary once and fence binary and config files by metadata. Credential denies come only from the bounded `HOST_CREDENTIAL_PATHS` and `HOST_CREDENTIAL_ENV` lists, denied at the lexical and canonical path; refuse glob syntax or a root inside the checkout. Never pass the control token in a reviewer child's argv, env or MCP config: keep the hub state dir in its `privatePaths`.
|
|
@@ -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).
|
|
@@ -19,4 +19,5 @@ Scope: quota and native context readings, checkpoints, pause, handoff and resume
|
|
|
19
19
|
- Quota handoff context reads `QUOTA_HANDOFF_PLATFORMS` (`src/adapters/registry.ts`, each definition's `hooks.recallPlatform`: claude, codex, kimi, qwen, jcode). It is independent of `hooks.recallInjected`: a handoff platform never joins the injected-platform exclusion in recall (`test/adapter-handoff-platforms.test.ts`).
|
|
20
20
|
- `Budget.report` skips `user`, `hub` and a registered kind with no `budget` block or with `budget.quotaExempt` (only local); an external id keeps quota accounting. `budget.instrumented` (execution budget) never exempts quota.
|
|
21
21
|
|
|
22
|
-
- Key quota readings, pause records, overrides, checkpoints and handoffs by the canonical account (`hooks.account`, idempotent); telemetry keeps the reporter id and native context occupancy stays per instance. `instanceBudgetHooks` fans pause, resume, checkpoint and handoff out to every member, and `canHandOff` excludes the paused account's siblings. Startup normalization merges old alias pause rows without dropping a row or marking an unfinished handoff done. A manual pause stays per member.
|
|
22
|
+
- Key quota readings, pause records, overrides, checkpoints and handoffs by the canonical account (`hooks.account`, idempotent); telemetry keeps the reporter id and native context occupancy stays per instance. `instanceBudgetHooks` fans pause, resume, checkpoint and handoff out to every member, and `canHandOff` excludes the paused account's siblings. Startup normalization merges old alias pause rows without dropping a row or marking an unfinished handoff done. A manual pause stays per member. Reviewer variants keep their base kind's `budget.account`.
|
|
23
|
+
- Release-cut quota admission (`releaseQuotaSnapshot` in `src/hub/daemon.ts`, #481 S11): an account has a quota source only when one of its members' definitions has `budget.reader`, or `budget.resetSoftTokensOnResume` with `kimi_tokens_5h > 0`. Without a source a registered account is reported `no quota source` and never defers; with one, an unknown, stale or below-gate reading defers. Manual and budget-record pauses defer for every attached peer.
|
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).
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
- 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.
|
|
7
7
|
|
|
8
|
-
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` or the instance modules (`src/hub/instances.ts`, `src/hub/instance-config.ts`, `src/hub/instance-execution.ts`), or before adding a native peer.
|
|
8
|
+
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` or the instance modules (`src/hub/instances.ts`, `src/hub/instance-config.ts`, `src/hub/instance-execution.ts`), or before adding a native peer.
|
|
9
9
|
|
|
10
10
|
- 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).
|
|
11
11
|
- `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,15 +16,18 @@ Scope: the operator console, control WS and its protocol, state files, the dashb
|
|
|
16
16
|
- A copy of the git index keeps the original's mtime (`snapshot()`): git's racy-entry check compares entries with the index file's time in whole seconds, and a fresh copy makes a same-size edit look clean.
|
|
17
17
|
- State files that clients read (`status.json`, `control-token`) are written after the port is bound, and `status.json` via temp file + rename.
|
|
18
18
|
- Open native memory through the shared profile-aware helper with an explicit current selector binding; never fall back to shared v1 after invalid/missing generation metadata. Check that binding at hub call-site entry and before candidate/counter writes, including callbacks retained across awaits; do not intercept store methods with proxies/private hooks or claim atomic in-flight cutover. Quiesce and close consumers before migration/selection changes, then reopen through the supported lifecycle. Keep the sidecar at the independent memory root, not inside the selected generation. Report memory availability separately from bus health; never hot-rebind or migrate as a startup repair (`docs/memory.md` §Selected memory profile).
|
|
19
|
+
- Take the memory project only from `memoryProjectFor` (the alias-resolved `memoryProjectKey`) in the daemon, `ahub memory` and `ahub goal`; never use a raw `memoryProjectKey` as the project of a store, screen or raw command. The daemon resolves it once at start (falling back to the raw key, so the memory home never blocks startup) and passes both `project` and the raw `projectKey`, so the binding latches unavailable once the alias log maps the key elsewhere; a restart binds the new key.
|
|
20
|
+
- Detect a curated split by `lstat` of `shards.json` (`splitMarked`), never `existsSync`. An inactive, malformed, dangling or unreadable marker throws `MEMORY_SPLIT_INCOMPLETE` before any shard read, and callers show that message instead of the generic profile refusal. On a split home open only through `openShardStore` with the canonical memory dir and a project key; never a plain `MemoryStore`, and refuse without a key.
|
|
19
21
|
- The channel's reconnect loop stops on closes a retry cannot fix (`TERMINAL_CLOSES` in `claude-channel.ts`); a new daemon close code that means "do not come back" belongs there, or two clients fight over it forever.
|
|
20
22
|
- Close 4000 is the one close that ends on its own, so it is not in `TERMINAL_CLOSES`: the session stands by and reconnects only once `status.json` reports the peer offline and not `claiming`. One exception (#205, `peerHeld` in `claude-channel.ts`): a session with channel pushes also reconnects when the holder is reported `toolsOnly` and not `claiming`, and takes the peer; the daemon refuses the reverse with 4000. Reconnecting blind would evict whoever holds the id now, and the two would trade it forever. `claim()` happens at hello and `attach()` only after the preface, so the peer reads as offline in between: the daemon writes the status file at the claim and the flag covers that window.
|
|
21
23
|
- Adding a native peer requires testing the command emitted by the actual recovery driver, not only its inspection or launch helpers. Never let a generic non-Codex branch treat a new peer as Claude.
|
|
22
24
|
- Cross-version recovery must use the authenticated source protocol for prepare/commit/abort and the target protocol after startup. Prove the transition against a real prior release before claiming compatibility.
|
|
23
25
|
- Preserve `task_cancel` target-feature preflight before source holds, capture or commit. Reuse stored target features only after verifying the same retained root and digest; legacy receipts still capable of commit must obtain feature proof. Do not apply pre-commit compatibility checks to sources already past commit. Every prepare, including the lease renewal before a terminal close and after a resume, sends the operation's stored `targetFeatures` (#478).
|
|
26
|
+
- A recovery target-feature floor (`task_cancel`, `peer_instances`, `release_trains` while a configured train is active or frozen, `reviewer_pool` while a fresh reviewer or `review.pool` is configured) lives in `incompatibleRecoveryTarget`, which every existing check site calls, and its name in `DAEMON_FEATURES`; never check a floor at one site only.
|
|
24
27
|
- 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
28
|
- 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
29
|
- 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.
|
|
30
|
+
- 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
31
|
- 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
32
|
- 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
33
|
- 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 +42,7 @@ Scope: the operator console, control WS and its protocol, state files, the dashb
|
|
|
39
42
|
- 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
43
|
- `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
44
|
- 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.
|
|
45
|
+
- `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
46
|
|
|
44
47
|
- 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
48
|
- 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.
|
|
@@ -59,5 +62,10 @@ Scope: the operator console, control WS and its protocol, state files, the dashb
|
|
|
59
62
|
- Keep full memory home, generation ID, selector revision and malformed-row totals in authenticated person status/CLI receipts, never disk status.json or agent CLI output; low-privilege receipts use a generation prefix and omit home, selector revision and malformed-row totals. Emit typed unbound disabled/unavailable receipts with no profile when no memory handle opened; profile-check may treat an authenticated same-home generation-capable unbound daemon as memory-quiesced, never a stale bound handle. Scope profile-check readiness to its named registry inventory; old, unknown or unreadable receipts refuse readiness, and other custom registries need separate checks.
|
|
60
63
|
|
|
61
64
|
- 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)`.
|
|
65
|
+
- `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`.
|
|
62
66
|
- 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
67
|
- 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.
|
|
68
|
+
|
|
69
|
+
- 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).
|
|
70
|
+
- 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.
|
|
71
|
+
- 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`
|
|
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` or `src/hub/release-admission.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,16 @@ 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. A definition with `reviewer.unavailable` is refused as reviewer there too (code `policy`); unavailable never means pass.
|
|
50
|
+
- 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; 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.
|
|
51
|
+
- `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.
|
|
52
|
+
- 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"]`.
|
|
53
|
+
- `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,12 +13,16 @@ 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
|
-
- 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.
|
|
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. The check job's `timeout-minutes` must leave room above that value for setup: raise them together.
|
|
20
20
|
- A seed's named test must run on Linux, where the seeded job runs: a darwin-only test reports no result there and fails the job (#382). Cover a macOS-only protection with its regression test instead.
|
|
21
21
|
|
|
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, and fresh Codex reviewer tests run `test/fakes/fake-codex-reviewer.ts` through `codex_bin`; a test that reaches the daemon's reviewer start must never run the real `claude` or `codex`. The fake's sandbox markers test host admission only, never native isolation.
|
package/docs/memory.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Hub memory integration
|
|
2
2
|
|
|
3
|
-
Agent-hub consumes the exact-pinned first-party [`@staix/agent-mem`](https://github.com/STAIxBWLB/agent-mem) library (current pin `0.
|
|
3
|
+
Agent-hub consumes the exact-pinned first-party [`@staix/agent-mem`](https://github.com/STAIxBWLB/agent-mem) library (current pin `0.4.0`, initial pin `0.1.1`). The package owns portable storage, tiers, graph, goals, sync, import and raw commands. Agent-hub owns caller authority, current project privacy policy, task lifecycle integration and presentation. Optional claude-mem remains a separate capture and session-recall source.
|
|
4
4
|
|
|
5
5
|
## Portable core and data directory
|
|
6
6
|
|
|
@@ -12,6 +12,8 @@ The [agent-mem repository](https://github.com/STAIxBWLB/agent-mem) is the refere
|
|
|
12
12
|
|
|
13
13
|
The memory root `M` is independent of the project state directory. The package's `M/profile.json` selects shared v1 or an explicit generation `G`; an absent selector means shared v1. The hub opens the selected profile with its current privacy screen and configured TTL. A generation uses `M/generations/G`, reads legacy prefixes through the package's readonly bridge, and writes its own rows and sync history there. The bridge does not migrate legacy bytes or use the legacy index. Exact-entry prune remains the package's person-only operation; it adds no shared-v1 operation and gives hub tools no prune authority.
|
|
14
14
|
|
|
15
|
+
A curated split (agent-mem#15 T2, `amem migrate --shards`) writes `M/shards.json`. Once it is active, the hub opens memory through the package's `openShardStore` for the project's key: `memoryProjectKey` of the hub's project, resolved through the person-reviewed alias log of the memory home, the same key the `amem` CLI binds. The daemon and `ahub memory`/`ahub goal` read and write under that key, split or not. A running hub resolves the key once at start; when `amem alias add` later maps that key elsewhere, its binding latches unavailable at the next memory entry, and a restart binds the new key. That store is this project's shard, with the user shard as its overlay. Other projects' private notes are not in it. A project first seen after the split gets its own shard on first open. The binding compares the shard identity (key, sealed generation, selector revision) at every entry. A split marker that is not active (captured, malformed, or a dangling or unreadable `shards.json`) refuses with `amem migrate --shards --resume` as the remedy and is never read as an empty or legacy store. A marker that appears under an open unsplit binding latches that binding unavailable. Without a marker, the hub opens memory exactly as before. `ahub memory profile-check` is not split-aware yet: on a split home it compares running hubs against the legacy selector's profile and reports a mismatch for every correctly bound hub (#536).
|
|
16
|
+
|
|
15
17
|
All hub consumers share that binding: Tasks notes and lifecycle operations, leases, suggestions, briefs, recall, peer read tools, dashboard, ask and automatic sync. Standalone `amem` and `ahub memory`/`ahub goal` must resolve the same root and profile to see one another's writes. An invalid selector or missing/corrupt generation disables native memory without taking down the bus or task board; it never silently opens shared v1. The daemon reports its package/profile binding and availability separately from ordinary hub health.
|
|
16
18
|
|
|
17
19
|
Person-facing status and memory output show the profile and nonzero malformed-row count. Allowed agent-shell reads retain current row screening and receive only a short profile label; full generation IDs, the memory home and home-wide skipped totals are available through authenticated person status and person CLI receipts, never the shell-readable status.json file or agent CLI output. Domain JSON shapes are unchanged; the additive `memoryProfile` receipt belongs to status, profile-check and the person-only decay object. Profile-check reports the registry scope it inspected; inventory separate custom registries too before treating zero live claims as quiescence.
|
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.37 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
|
|
@@ -965,7 +971,7 @@ ahub memory forget 1a2b3c4d # a person's; also promote, demote, sup
|
|
|
965
971
|
|
|
966
972
|
`"memory": { "native": false }` in `.agenthub/config.json` turns it off for a project.
|
|
967
973
|
|
|
968
|
-
The exact-pinned `@staix/agent-mem` 0.
|
|
974
|
+
The exact-pinned `@staix/agent-mem` 0.4.0 resolves `AMEM_HOME`, then `$AGENTHUB_HOME/memory`, then `~/.agenthub/memory`. An absent `M/profile.json` selects shared v1; an explicit generation selects `M/generations/G` with the package's readonly legacy bridge. All daemon memory consumers and the hub memory/goal CLI use that selected profile. Invalid metadata or a missing selected generation makes native memory unavailable while the bus and board continue; there is no fallback to legacy writes. Inspect the daemon's memory binding/availability before interpreting missing memory as an empty store.
|
|
969
975
|
|
|
970
976
|
The daemon checks its selector at hub operation entry and before candidate/counter writes. It does not hot-switch handles or provide an atomic fence against selection changes during an in-flight core call. Quiesce consumers and close their handles before migration or selection changes; reopen through the supported session-preserving lifecycle afterward. Candidate/counter storage remains `M/candidates.db`, independent of `G`; `amem review` on a generation is still refused. Every running hub using this home needs an authenticated receipt: a matching ready binding, or a generation-capable unbound receipt (`disabled` when native memory is off, `unavailable` when opening failed) proving it holds no memory handle. An unbound daemon and zero running hubs are quiesced for memory; a stale bound handle, unreadable registry or unknown runtime is not ready. Existing JSON memory/goal results remain unchanged; the additive binding receipt belongs to daemon status, profile-check and the decay object. This source integration does not authorize installing a new runtime, migrating real memory, restarting sessions or claiming real-machine sync/capture receipts. Migration/deactivation and their limits belong to [agent-mem](https://github.com/STAIxBWLB/agent-mem); see [the hub profile contract](memory.md#selected-memory-profile).
|
|
971
977
|
|
|
@@ -973,6 +979,8 @@ Run `ahub memory profile-check [--json]` from the person's terminal for the read
|
|
|
973
979
|
|
|
974
980
|
Before real migration, first upgrade every hub sharing the home to 0.12.30 or later. Obtain positive ready or unbound/quiesced receipts in every relevant registry; then quiesce consumers and close bound handles before changing the selector. A matching ready handle proves compatibility but is still an open handle. Verify synthetic cross-writes before the separately approved real migration, then reopen through the supported session-preserving lifecycle afterward. Merely installing this release does not authorize migration or provide live capture/device-sync evidence.
|
|
975
981
|
|
|
982
|
+
A curated split (`amem migrate --shards`) writes `M/shards.json`; from 0.12.37 the hub then opens this project's shard, with the user shard as its overlay, under the alias-resolved project key the `amem` CLI binds (#534, #537). Before splitting, upgrade or stop every hub using the home: a hub on 0.12.36 or earlier does not detect the split and keeps writing the old layout, which reaches the shards only through `amem migrate --shards --resume`. A split that is not active (captured, malformed, or a dangling or unreadable `shards.json`) makes native memory unavailable with that resume command as the remedy. An `amem alias add` that remaps a running hub's key latches its memory unavailable until a restart. `ahub memory profile-check` is not split-aware yet: on a split home it reports a mismatch for every correctly bound hub (#536). See [the shard binding](memory.md#selected-memory-profile).
|
|
983
|
+
|
|
976
984
|
Managed agents read through `hub_memory_search`, `hub_memory_show` and `hub_memory_graph`, with the server's project identity, the `memory_read` capability and current privacy screening. They cannot supply another project; private turns are refused. The tools bound UTF-8 input/output, screen before counts and truncation, and exclude their arguments/results from hub-managed capture. `hub_memory_correct` has its own `memory_correct` capability and records a screened correction candidate, not a note change. See [the complete memory contract](memory.md) for caps and refusal semantics.
|
|
977
985
|
|
|
978
986
|
Review candidates and rendered/delivered/cited counters stay in machine-local `M/candidates.db`, outside sync, import, recall, briefs, ask and graph evidence. Public changes-requested reviews with unmet items may propose a fail candidate; missing cause/retry details stay unknown. Shared-v1 acceptance is a person's `amem review` action with its TTY/policy checks; selected generations still refuse that command. Read-time `(stale?)` hints and plan-fail advice never change stored rows or task authority. Structured fail notes are validated against 4000 UTF-8 bytes before screening or writes, with no silent truncation.
|
|
@@ -1785,6 +1793,7 @@ A Claude session started with `ahub claude` receives pushes when it is idle. Whi
|
|
|
1785
1793
|
- `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
1794
|
- 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
1795
|
- 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.
|
|
1796
|
+
- 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
1797
|
|
|
1789
1798
|
## Graceful shutdown
|
|
1790
1799
|
|
|
@@ -1971,21 +1980,21 @@ section is what they run.
|
|
|
1971
1980
|
|
|
1972
1981
|
Upgrade running projects with the target release's own coordinator. It accepts
|
|
1973
1982
|
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.
|
|
1983
|
+
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.37), and only
|
|
1975
1984
|
a target on its own protocol, so the target's coordinator fits every supported
|
|
1976
1985
|
source and carries every recovery fix released up to it. Protocol 8 and older
|
|
1977
1986
|
(0.5.x and earlier) are refused as `manual-bootstrap-required`. Run from the
|
|
1978
1987
|
project directory, without replacing the global CLI first:
|
|
1979
1988
|
|
|
1980
1989
|
```bash
|
|
1981
|
-
bunx --package @staix/agent-hub@0.12.
|
|
1982
|
-
bunx --package @staix/agent-hub@0.12.
|
|
1990
|
+
bunx --package @staix/agent-hub@0.12.37 ahub upgrade --to 0.12.37 --dry-run
|
|
1991
|
+
bunx --package @staix/agent-hub@0.12.37 ahub upgrade --to 0.12.37 --yes
|
|
1983
1992
|
```
|
|
1984
1993
|
|
|
1985
1994
|
| Running now | Coordinator to use |
|
|
1986
1995
|
| --- | --- |
|
|
1987
1996
|
| 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.
|
|
1997
|
+
| 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.37 (protocol 16) | the target's, through `bunx` as above |
|
|
1989
1998
|
| any supported source, with the installed CLI already at the target | `ahub upgrade` below, which is the same coordinator |
|
|
1990
1999
|
| 0.5.x or earlier (protocol 8 and older) | not supported: bootstrap by hand with the matching CLI |
|
|
1991
2000
|
|
|
@@ -2017,14 +2026,14 @@ projects first:
|
|
|
2017
2026
|
|
|
2018
2027
|
```bash
|
|
2019
2028
|
ahub restart --dry-run
|
|
2020
|
-
ahub upgrade --to 0.12.
|
|
2029
|
+
ahub upgrade --to 0.12.37 --dry-run
|
|
2021
2030
|
```
|
|
2022
2031
|
|
|
2023
2032
|
Apply only after reviewing the plan:
|
|
2024
2033
|
|
|
2025
2034
|
```bash
|
|
2026
2035
|
ahub restart --yes
|
|
2027
|
-
ahub upgrade --to 0.12.
|
|
2036
|
+
ahub upgrade --to 0.12.37 --yes
|
|
2028
2037
|
ahub recovery status [<operation-id>]
|
|
2029
2038
|
ahub recovery resume [<operation-id>]
|
|
2030
2039
|
ahub recovery abort [<operation-id>]
|
|
@@ -3056,7 +3065,7 @@ that the file is untracked; a dropped value is reported by the settings result.
|
|
|
3056
3065
|
|
|
3057
3066
|
## SDLC artifact receipts and gates
|
|
3058
3067
|
|
|
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.
|
|
3068
|
+
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)). S2-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.
|
|
3060
3069
|
## Optional Qwen Code peer
|
|
3061
3070
|
|
|
3062
3071
|
Select Qwen per project with `ahub roster add qwen`; it defaults off and is absent
|
|
@@ -3084,12 +3093,12 @@ Qwen controlled recovery and automatic crash resume load the recorded conversati
|
|
|
3084
3093
|
|
|
3085
3094
|
## Optional Jcode peer
|
|
3086
3095
|
|
|
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.
|
|
3096
|
+
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
3097
|
|
|
3089
3098
|
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
3099
|
|
|
3091
3100
|
|
|
3092
|
-
Native policy is unmanaged and has NO hub sandbox; no permission_modes.jcode or runtime mode mapping is invented. Quota and consumption remain unknown.
|
|
3101
|
+
Native policy is unmanaged and has NO hub sandbox; no permission_modes.jcode or runtime mode mapping is invented. Quota and consumption remain unknown. Supported versions run from 0.98.0 up to, not including, 1.0.0 (#529). Every owned jcode process (version probe, daemon, ACP) runs with `JCODE_NO_TELEMETRY=1`. Native session/load (verified on 0.94.0 and 0.98.1) cannot prove identity, so attached Jcode blocks controlled recovery before effects and crash recovery asks for deliberate native reopening. A requested load without an echoed identity refuses; no fresh session is presented as restored. Provider/tool/native teardown receipts remain unverified in the pinned contract report.
|
|
3093
3102
|
|
|
3094
3103
|
# Headless ACP watch (#286)
|
|
3095
3104
|
|
|
@@ -3267,10 +3276,12 @@ The person uses `ahub backlog pull-ready <id> <revision> <reason>` or Pull withi
|
|
|
3267
3276
|
WIP on a card. Explicit manual pull/assignment retain their existing authority.
|
|
3268
3277
|
|
|
3269
3278
|
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.
|
|
3279
|
+
uses the existing machine-local registry path; integers 0..32 apply live (the next
|
|
3280
|
+
admission reads the current value), with 0 disabling planner admission for that
|
|
3281
|
+
peer. Status and the agent legend count
|
|
3282
|
+
owned unfinished work plus unowned planner-pull reservations. The same limit gates
|
|
3283
|
+
acceptance and self-claim in every lifecycle mode, so 0 refuses new acceptances; a
|
|
3284
|
+
current owner's redo of sent-back work keeps its slot (#481 S9).
|
|
3274
3285
|
|
|
3275
3286
|
Pending unowned, unstarted pull can be undone at its latest owned revision via
|
|
3276
3287
|
`hub_backlog_undo` or the existing planning undo control, restoring its earlier
|
|
@@ -3430,7 +3441,7 @@ reference markers remain protected. Split-part plan schemas follow the selected
|
|
|
3430
3441
|
project profile. Terminal tables preserve the visible read for stages and use
|
|
3431
3442
|
a fresh peer status read on the same authenticated connection.
|
|
3432
3443
|
|
|
3433
|
-
Integer WIP editors
|
|
3444
|
+
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
3445
|
|
|
3435
3446
|
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
3447
|
|
|
@@ -3519,10 +3530,49 @@ restart the hub. For example, a reviewer can use:
|
|
|
3519
3530
|
Start them with `ahub codex --as codex-review` and
|
|
3520
3531
|
`ahub kimi --as kimi-review`. Codex keeps its native TUI and its own hub-owned
|
|
3521
3532
|
app-server proxy; extra Kimi, Qwen, Pi and local instances use headless starts.
|
|
3522
|
-
Extra
|
|
3533
|
+
Extra Jcode instances are unavailable. An extra Claude instance is a reviewer or
|
|
3534
|
+
planner you start yourself: declare it with `roles` only (for example
|
|
3535
|
+
`"claude-review": { "kind": "claude", "roles": ["reviewer"] }`, no `start_mode` or
|
|
3536
|
+
`worktree`) and run `ahub claude --as claude-review` in a terminal. It keeps its own
|
|
3537
|
+
`claude-*.claude-review.json` state files.
|
|
3538
|
+
|
|
3539
|
+
A headless Claude reviewer is run by the hub instead (#223): declare
|
|
3540
|
+
`"claude-review": { "kind": "claude", "roles": ["reviewer"], "start_mode": "headless" }`
|
|
3541
|
+
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
|
|
3542
|
+
review request runs one new `claude -p` session (never resumed) that may only read
|
|
3543
|
+
the checkout and call `hub_review` and `hub_task_show`, with no user settings,
|
|
3544
|
+
hooks or plugins. It gets the review facts and a bounded diff of the head, never
|
|
3545
|
+
the builder's summary or agent chat, and the board records its verdicts as
|
|
3546
|
+
`reviewContext: fresh`. Review requests go to it first; while it is not started,
|
|
3547
|
+
the planner reviews and the task's assignment note says `reviewer=planner (fallback)`.
|
|
3548
|
+
A missing
|
|
3523
3549
|
`peer_instances` feature on an old daemon or upgrade target requires a supporting
|
|
3524
3550
|
version before launching or restoring extras.
|
|
3525
3551
|
|
|
3552
|
+
### Fresh reviewer pool (#519)
|
|
3553
|
+
|
|
3554
|
+
From 0.12.37 reviews can rotate across fresh headless Claude and Codex reviewers. Declare reviewer-only instances with explicit headless mode and the Codex fresh opt-in in `.agenthub/config.local.json`:
|
|
3555
|
+
|
|
3556
|
+
```json
|
|
3557
|
+
{
|
|
3558
|
+
"instances": {
|
|
3559
|
+
"claude-review": { "kind": "claude", "roles": ["reviewer"], "start_mode": "headless" },
|
|
3560
|
+
"codex-review": { "kind": "codex", "roles": ["reviewer"], "start_mode": "headless", "review_mode": "fresh" }
|
|
3561
|
+
},
|
|
3562
|
+
"review": { "pool": ["claude", "codex"], "rerotate": false }
|
|
3563
|
+
}
|
|
3564
|
+
```
|
|
3565
|
+
|
|
3566
|
+
Start them with `ahub claude --as claude-review` and `ahub codex --as codex-review`; stop with `ahub stop <id>`. Fresh variants share the project checkout and 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 metadata placeholder remains automatically unavailable. 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 as fixed configured kind slots; undeclared/unavailable slots are skipped after modulo without renumbering; `review.rerotate` defaults to false. Pool selection starts at PR number modulo the fixed configured kind count, 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.
|
|
3567
|
+
|
|
3568
|
+
Codex requires an installed 0.146.0 executable and a passing bounded runtime receipt before its model child starts. Use the existing `codex_bin` setting to select a supported host executable when PATH resolves another version. PATH 0.163.0 has no receipt in this slice and remains unavailable. The producer checks native binary/config stability, configured MCP inventory with only the two hub tools, effective Apps/plugins/hooks disablement 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.
|
|
3569
|
+
|
|
3570
|
+
Kimi and agy are metadata-only unavailable candidates, outside the default pool. agy will not become a reviewer (person decision, 2026-10-19); it is planned as an implementer peer under [#485](https://github.com/STAIxBWLB/agent-hub/issues/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 the unavailable Kimi/agy placeholders (a Kimi fresh declaration also needs `review_mode: "fresh"`); their reason appears as an unavailable skip and they never run or count as approval. Kimi launch/isolation work is deferred to [#533](https://github.com/STAIxBWLB/agent-hub/issues/533), hub task #234; agy code belongs in `src/adapters/agy/`. Do not substitute plan mode for isolation.
|
|
3571
|
+
|
|
3572
|
+
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.
|
|
3573
|
+
|
|
3574
|
+
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.
|
|
3575
|
+
|
|
3526
3576
|
An additional implementer must have an existing registered Git worktree and
|
|
3527
3577
|
attached branch at `.worktrees/<instance-id>`, named explicitly by `worktree` in
|
|
3528
3578
|
its declaration. The hub validates it and does not create it in this phase.
|
|
@@ -3545,3 +3595,9 @@ fields as unknown and cannot restore them into the main checkout. The supporting
|
|
|
3545
3595
|
CLI checks the saved workspace against the current declaration again before any
|
|
3546
3596
|
context change or file restoration. Removing the declaration intentionally
|
|
3547
3597
|
blocks that undo until the identity and workspace can be reviewed.
|
|
3598
|
+
|
|
3599
|
+
## Planner release scheduling
|
|
3600
|
+
|
|
3601
|
+
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.
|
|
3602
|
+
|
|
3603
|
+
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.
|