@staix/agent-hub 0.16.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -0
- package/README.md +2 -2
- package/docs/agent-notes/README.md +5 -0
- package/docs/agent-notes/adapters.md +20 -5
- package/docs/agent-notes/benchmarks.md +1 -0
- package/docs/agent-notes/daemon.md +5 -3
- package/docs/agent-notes/local-worker.md +1 -0
- package/docs/agent-notes/tasks.md +9 -3
- package/docs/agent-notes/tests.md +6 -3
- package/docs/dogfood.md +21 -4
- package/docs/memory.md +1 -1
- package/docs/operations.md +150 -34
- package/docs/profiles.md +36 -0
- package/docs/quickstart.md +14 -4
- package/docs/roles.md +1 -0
- package/docs/sdlc.md +39 -13
- package/docs/security.md +41 -9
- package/docs/smoke.md +51 -3
- package/docs/specs/2026-09-19-agent-hub-design.md +56 -10
- package/docs/specs/2026-09-20-upgrade-recovery-design.md +3 -1
- package/docs/verification/2026-10-25-0.17.0.md +74 -0
- package/docs/verification/2026-10-25-0.18.0.md +76 -0
- package/docs/verification/2026-10-25-codex-0164-reviewer-tool-call.md +52 -0
- package/docs/verification/2026-10-25-codex-review-live-verdict.md +35 -0
- package/docs/verification/2026-10-25-kimi-231-reviewer-compat.md +48 -0
- package/docs/verification/2026-10-25-kimi-usage-ac11.md +48 -0
- package/docs/verification/2026-10-25-qwen-dual-output-hub-e2e.md +23 -0
- package/docs/verification/2026-10-25-qwen-dual-output-t0.md +20 -0
- package/docs/verification/2026-10-25-remote-docker-transport.md +46 -0
- package/docs/verification/2026-10-25-reviewer-no-verdict-diagnostics.md +35 -0
- package/docs/verification/profiles/YYYY-MM-DD-general.md +46 -0
- package/docs/verification/profiles/YYYY-MM-DD-research.md +61 -0
- package/docs/verification/profiles/YYYY-MM-DD-writing.md +46 -0
- package/docs/verified.json +28 -19
- package/package.json +2 -2
- package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-hub/server.js +1115 -192
- package/src/adapters/codex-review-isolation.ts +10 -1
- package/src/adapters/compat.ts +7 -2
- package/src/adapters/conformance/enrollment.ts +18 -4
- package/src/adapters/conformance/factories.ts +4 -1
- package/src/adapters/conformance/kit.ts +11 -0
- package/src/adapters/definitions/builtin-metadata.ts +1 -1
- package/src/adapters/definitions/native-cli-entry-qwen.ts +32 -4
- package/src/adapters/definitions/native-cli-preview-qwen.ts +9 -1
- package/src/adapters/definitions/native-factories.ts +8 -0
- package/src/adapters/definitions/native-recovery.ts +8 -0
- package/src/adapters/definitions/qwen.ts +7 -4
- package/src/adapters/local-worker.ts +5 -3
- package/src/adapters/qwen-dual.ts +578 -0
- package/src/adapters/registry-types.ts +5 -1
- package/src/adapters/registry.ts +9 -1
- package/src/adapters/reviewer-mcp.ts +12 -6
- package/src/adapters/reviewer-print.ts +100 -17
- package/src/cli/console-state.ts +9 -2
- package/src/cli/help.ts +29 -28
- package/src/cli/identity.ts +87 -6
- package/src/cli/main.ts +190 -69
- package/src/cli/output.ts +7 -2
- package/src/cli/sdlc-output.ts +242 -0
- package/src/cli/task-output.ts +212 -0
- package/src/cli/upgrade-output.ts +57 -0
- package/src/cli/upgrade.ts +4 -3
- package/src/hub/board.ts +17 -0
- package/src/hub/check-runner.ts +252 -22
- package/src/hub/conductor.ts +5 -0
- package/src/hub/config-apply.ts +16 -7
- package/src/hub/config-edit-host.ts +1 -1
- package/src/hub/config-tier.ts +1 -1
- package/src/hub/daemon.ts +487 -94
- package/src/hub/dogfood-profile.ts +248 -0
- package/src/hub/dogfood-read.ts +77 -14
- package/src/hub/dogfood.ts +5 -1
- package/src/hub/events.ts +1 -1
- package/src/hub/host-executable.ts +2 -1
- package/src/hub/hub-tools.ts +2 -2
- package/src/hub/memory-plan-guard.ts +8 -33
- package/src/hub/operational-authority.ts +60 -0
- package/src/hub/operational-view.ts +141 -0
- package/src/hub/orphan-process.ts +69 -0
- package/src/hub/permission-mode.ts +1 -1
- package/src/hub/read-only-shell-proof.ts +80 -0
- package/src/hub/read-only-shell.ts +162 -0
- package/src/hub/reviewer-pool-config.ts +3 -2
- package/src/hub/reviewer-pool.ts +3 -0
- package/src/hub/routing.ts +23 -6
- package/src/hub/sdlc-merge.ts +299 -0
- package/src/hub/sdlc-mirror.ts +129 -0
- package/src/hub/sdlc.ts +58 -12
- package/src/hub/settings.ts +11 -5
- package/src/hub/snapshots.ts +60 -0
- package/src/hub/task-repository.ts +31 -5
- package/src/hub/tasks.ts +266 -28
- package/src/local/tools.ts +17 -4
- package/src/memory/brief.ts +45 -5
- package/src/ui/index.html +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,40 @@ Issue and pull request numbers in the entries for 0.7.7 and earlier refer to the
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.18.0
|
|
8
|
+
|
|
9
|
+
- Readable output by default (#814): `ahub sdlc status|explain|queue|release|metrics|costs|authorize-release` (#831), `task show|cancel`, `backlog groom|return|plan|pin|split|undo|pull-ready` and `release list|show|new|plan|activate|freeze|ship|drop` (#834), and `restart` and `upgrade` off a terminal (a pipe or `--dry-run`), `recovery status` and the results of `recovery reattach|resume|abort|dispose`, `models setup|start`, `queue resolve` and `budget execution configure|disable` (#833) print for a person. Scripts must pass `--json`, which prints exactly the previous document (the hub tool's text for the task, backlog and release commands). `--json` and `--color=` are flags only at the start or end of a command that takes free text, and an invalid or repeated `--color` is refused before any request is sent. Guard tests fail when a CLI path prints JSON outside `--json` or a named data format, or prints a hub tool's text that is not behind `--json` or a named exception (#837).
|
|
10
|
+
- Guarded merge (#786, #815, #825): `sdlc.toml` gains `auto_merge` (`off`, `dry-run`, `on`) and per-repository `required_checks` and `auto_merge`. An approving `hub_review` at the current head, or `ahub sdlc merge <task> [--dry-run]`, re-reads the PR and the merge queue and runs the one guarded `gh pr merge --squash --match-head-commit` or names every failing gate; never `--admin`. Confirmed merges are recorded and post-merge remote cleanup is guarded.
|
|
11
|
+
- Each hub verdict is mirrored as one GitHub COMMENT review, never APPROVE or REQUEST_CHANGES, on the head it was given for, once per task, head and verdict, and nothing at a stale or closed PR (#786, #823). It follows `auto_merge`.
|
|
12
|
+
- The review request carries the hub's exact-head CI evidence (a "Hub-verified at <head>" block, #824, #826) and the current planner rulings, bounded to five and 1000 characters each, with the builder summary still withheld from a review-only reviewer (#835, #836).
|
|
13
|
+
- `review.same_family = "never"` keeps a reviewer of the owner's family from being assigned; an exhausted automatic choice goes to the person (#820, #828).
|
|
14
|
+
- Agents can run read-only `ahub` commands (`doctor`, `projects`, `models status`, `recovery status`, dry-run previews) and public read controls from ordinary agent shells; a form the daemon refuses for a tools connection stays a console command, and conductor operations are authorized per scope (#812, #818, #827, #832).
|
|
15
|
+
- A proven read-only shell call is granted without asking: a bounded literal parser and a proof that screens Git signature, diff and custom-format helpers (#816, #829).
|
|
16
|
+
- The planner reads any task through the public view and cancels its own untouched declined task by the work-evidence test instead of "ever accepted" (#822).
|
|
17
|
+
- `ahub status`, the console Peers detail and the dashboard legend show planner WIP as active, review-wait and reserved per peer (#784, #819).
|
|
18
|
+
- Isolated remote Docker check runner with snapshot extraction and scoped cleanup (#793, #806, #807), and turn facts detect dirty submodules (#798, #808).
|
|
19
|
+
- The dogfood scorecard reports non-dev profile evidence and research profile measurements, rejects malformed profile evidence, and keeps dev rows known when a plan is cut or malformed (#799, #810, #817, #830).
|
|
20
|
+
- Pin @staix/agent-mem 0.8.0 (native-memory import, Korean recall set, consolidation and sync fixes) (#821).
|
|
21
|
+
- Tests and docs: the fake Claude call log is read only as complete lines (#525, #813), an extra implementer without a worktree is refused at start (#809), and the live codex-review verdict receipt on 0.17.0 is recorded (#811).
|
|
22
|
+
|
|
23
|
+
## 0.17.0
|
|
24
|
+
|
|
25
|
+
- Every open live leg in `docs/smoke.md` carries one class (`agent-executable`, `agent-evidence / person-verdict`, `person-only`, `external-blocked`), the SDLC stage table names the class per stage, and this repository's AGENTS.md has the reviewer that approves a PR at its head merge it under the existing gates and clean up; the shipped AGENTS block, catalog roles and flow role contracts are unchanged (#784).
|
|
26
|
+
- A follow-up task whose review records an earlier task's finding only as fixed no longer counts as a recurrence of that finding, so it no longer files a false "Review policy amendment" backlog card. The finding counts on a task once it was recorded open or accepted there (#328).
|
|
27
|
+
- The hub no longer logs `hub memory sync failed: no generation sync remote configured` every sync interval: agent-mem's generation sync words a missing remote this way, and no remote is not a failure (#401).
|
|
28
|
+
- Add `ahub qwen --mode tui` (#286 Part 3): Qwen Code's own TUI in your terminal, driven by the hub through its dual-output files (`--input-file`, `--json-file`), gated by a new `qwen-dual` compat entry verified on 0.26.1 only (the headless `qwen` entry is not widened). A delivery becomes a `submit`, the last assistant message of the turn is shared, console approvals round-trip as `confirmation_response`, hub tools are approved by exact `mcp__agent-hub__<tool>` name, and an answer given in the Qwen terminal withdraws the hub's pending approval. Two stated limits: a hub deny ends the turn without an answer (Qwen emits nothing for it), and the watchdog never cancels, it reports a stalled turn ("press Esc in the Qwen terminal") because no input or signal stops one. A Qwen write or command that names the hub state dir is refused (the input file is a prompt channel); an unreadable event file reads as detached, never idle; restart resumes from the saved offset or `qwen -r <session>` without double processing. The TUI is explicit only: Qwen stays a headless-form peer.
|
|
29
|
+
- The Codex reviewer approves exactly `hub_review` and `hub_task_show`, per tool (#783, #796). Codex 0.164.0 `exec` refused every MCP tool call ("requires approval, but approval policy is never"), so `codex-review` finished its reviews without recording a verdict. A reviewer backend now needs one model-backed hub tool call in its receipt, not only a model-free inventory.
|
|
30
|
+
- A reviewer turn that ends without a verdict leaves one bounded, screened `hub.log` line (its exit code and the hub tools it called, never tool arguments or model text), and its delivery completes only once the review is confirmed re-offered; an unconfirmed re-offer stays `needs_review`. A manually named reviewer's no-verdict, and one with no declared pool, goes to the person (#453, #804).
|
|
31
|
+
- The Qwen TUI, run against the real `qwen` 0.26.1 for the first time, had three defects the fake missed (#286, #805): the owner is now bound at the root of Qwen's process tree (a wrapper, a node launcher and `cli.js` all name the input file, so no owner was ever bound); a hub deny completes the delivery instead of `needs_review`, which held every later message to Qwen; and `ahub qwen --mode tui` execs Qwen in the person's own environment (PATH, HOME, provider key) with the hub's peer marker over it, dropping every other `AGENTHUB_*` variable but `AGENTHUB_HOME` and other agents' vendor markers.
|
|
32
|
+
- A stale review request that the hub refuses at admission no longer marks the reviewer unavailable as an isolation failure: a request whose binding changed is skipped, and one whose policy changed is rerouted as no verdict (#781, #789).
|
|
33
|
+
- An unavailable reviewer slot no longer fences an agy task read (#794).
|
|
34
|
+
- The Kimi 2.3.1 fresh reviewer is verified model-free (#797).
|
|
35
|
+
- Stop-and-archive keeps the error that blocked the upgrade as the audit entry's `cause`, so an abandoned upgrade can be diagnosed afterwards (#776, #777).
|
|
36
|
+
- Hub briefs and plan guards come from agent-mem's intent brief; the hub keeps its privacy policy, redaction and caller authority, and plan-guard fails stay at the front of the brief (#779).
|
|
37
|
+
- A committed ratchet of the remaining native-branch calls in the daemon (`test/daemon-native-branches.json`) and a fixed-builtin trust gate for proof-adjacent ports (#625 slice 2a, #790). The public conformance kit now requires a `<kind>:restrictive-ports` leg for every ACP definition.
|
|
38
|
+
- Shared dev-only AST helpers (`scripts/lib/ts-ast.ts`). A seed-rot error names the anchor's symbol at the base commit and where it is now; seeded CI fetches full history and passes the base with `--base` (#684 phase 5, #778).
|
|
39
|
+
- The Kimi usage counting receipt is recorded (#285 AC11, #782).
|
|
40
|
+
|
|
7
41
|
## 0.16.0
|
|
8
42
|
|
|
9
43
|
- The project daemon stops through its normal stop on SIGHUP and SIGQUIT too, not only SIGINT and SIGTERM, so memory sync git (detached since agent-mem 0.7.0) never outlives the hub; SIGQUIT no longer reaches its default core dump (#438).
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ Code, Codex, Kimi Code, Pi and a hub-owned local worker (plus Qwen Code, Jcode a
|
|
|
5
5
|
Antigravity if you want them) share a message bus, a task board and a memory, each
|
|
6
6
|
connected through its own native interface.
|
|
7
7
|
|
|
8
|
-
Status: 0.
|
|
8
|
+
Status: 0.18.0, control protocol 16. Bun + TypeScript, one loopback daemon per
|
|
9
9
|
project. Site: [agent-hub.staix.net](https://agent-hub.staix.net).
|
|
10
10
|
|
|
11
11
|
## Quick start
|
|
@@ -94,7 +94,7 @@ uses the home must run a version that understands the new layout.
|
|
|
94
94
|
[#258](https://github.com/STAIxBWLB/agent-hub/issues/258)).
|
|
95
95
|
|
|
96
96
|
Live checks that still need a real account or a person are listed in the
|
|
97
|
-
[release verification report](docs/verification/2026-10-25-0.
|
|
97
|
+
[release verification report](docs/verification/2026-10-25-0.18.0.md) and the
|
|
98
98
|
[smoke checklist](docs/smoke.md).
|
|
99
99
|
|
|
100
100
|
## Documentation
|
|
@@ -43,3 +43,8 @@ Background moved out of the area notes for 0.15.0:
|
|
|
43
43
|
Background moved out of the area notes for 0.16.0:
|
|
44
44
|
|
|
45
45
|
- Closing-refs negation list (#760, tests.md): agent-mem PR #36 said "Does not close #15", GitHub read `close #15` as a closing keyword, and the design issue closed with T3 open. The list errs toward flagging: a false positive costs a rewrite of the PR text, a false negative closes a design issue, and a body that really means a qualified close is the author's to reword. A keyword typed into a comment or the merge dialog is out of the check's reach. At 0.16.0 `closing-refs` is not a required status check (branch rules require `check (ubuntu-latest)`, `check (macos-latest)` and `seeded guards`).
|
|
46
|
+
|
|
47
|
+
Background behind the 0.18.0 rules:
|
|
48
|
+
|
|
49
|
+
- CLI print guards (#814, tests.md): a command that printed JSON by default made scripts the only reader; each converted command keeps `--json` byte-identical to the old output, and the guard scans for both a bare `JSON.stringify` print and a bare print of a hub tool's text so a new unlisted command cannot slip back.
|
|
50
|
+
- Agent shell authority (#812, adapters.md): reading public state is allowed for every agent, the conductor gets a closed operational family, and every other form stays the person's. An allowed read form is added only when the daemon admits it for an ordinary tools connection too.
|
|
@@ -8,14 +8,16 @@ Scope: the Codex, ACP and Pi adapters and the Pi extension (turn `generation` an
|
|
|
8
8
|
|
|
9
9
|
- Keep per-kind native policy in `src/adapters/definitions/` and consume it through the registry. Private CLI implementations accept only fixed host-bound ports; keep identity, audit, lifecycle admission and effect ordering in the host. Except for the verified person-owned A4 startup-module contract, do not add config-selected callbacks or runtime imports from registry leaves into eagerly initialized hub/CLI modules; use type-only ports for existing host planners.
|
|
10
10
|
- 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`, `OPTIONAL_PEERS` and `START_MODE_PEERS`, which `test/registry-guard.test.ts` holds equal to the derived lists. `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`).
|
|
11
|
-
- 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` or `claude-implementer` daemon port, `identity.currentChild`, `routing`, `projection`, `reviewerVariant`, `headlessVariant`, `reviewer`, `hooks.factsHook`, `cli.entry`, `cli.preview` or `cli.usage`. 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. It picks a kind's `headlessVariant` (Claude, #525-4) for a declared headless extra whose roles include `implementer` and hold only `implementer` and `verifier`. Metadata-only reviewer variants never establish native peer support; `isSupportedPeer` shares the registry runtime/validated-instance predicate with the catalog and daemon. 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`).
|
|
11
|
+
- 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`, `session.nativeRevert`, a `pi-rpc` or `claude-implementer` daemon port, `identity.currentChild`, `routing`, `projection`, `reviewerVariant`, `headlessVariant`, `reviewer`, `hooks.factsHook`, `cli.entry`, `cli.preview` or `cli.usage`. 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. It picks a kind's `headlessVariant` (Claude, #525-4) for a declared headless extra whose roles include `implementer` and hold only `implementer` and `verifier`. Metadata-only reviewer variants never establish native peer support; `isSupportedPeer` shares the registry runtime/validated-instance predicate with the catalog and daemon. 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`).
|
|
12
|
+
- Native branch ratchet (#625): `test/daemon-native-branches.test.ts` counts every call of a native class predicate (an `is*Peer` imported from an adapter module other than `registry.ts`) per enclosing function in `src/hub/daemon.ts` and `src/hub/claude-launch-proof.ts`, and the counts must equal `test/daemon-native-branches.json` (`predicate`, `function`, `count`, `reason`). A PR that adds, moves or removes such a call edits its row by hand with a concrete reason; never regenerate the table from a test. Bind a predicate alias with `const` only and never call one through a computed namespace key: the scan refuses both.
|
|
13
|
+
- Native proof state (Codex proxy URL, attachment and permission policy, `statusNotice`, `session.nativeRevert`, the Claude launch proof and terminal execution root) is read in the daemon only after `isFixedBuiltinDefinition(definitionOf(peer))`: a same-name foreign definition gets none of it. A product of a definition that is not a fixed builtin may not have a `frontendProxy`, `statusNotice` or `revertTurn` member at all, even undefined (`assertNativeProofPorts`, run by `createFromDefinition` and the conformance kit).
|
|
12
14
|
- Adding a native peer: declare its `AdapterDefinition` under `src/adapters/definitions/` (or its own directory, as agy) and list it in `ADAPTERS` (`src/adapters/registry.ts`). `KNOWN_PEERS`, `OPTIONAL_PEERS`, `SUPPORTED_PEERS`, `START_MODE_PEERS` (a kind with both a TUI and a headless form), `ROLE_ORDER`, `HELP_ORDER` and `PLANNER_PEERS` are derived from it by `deriveAdapterLists`: set `optional`, `projection` (`roleRank`, `startRank`, `helpRank`, `plannerEligible`; a configured or external definition cannot) and, for an `ahub <kind>` launcher, `cli.entry`, `cli.usage`, `cli.preview` and `cli.startWords`, never a list edit (`builtinCliEntries(definitions)` projects the entries). The literal cross-release copies `KNOWN_PEERS` and `OPTIONAL_PEERS` in `src/hub/peer-catalog.ts` and `START_MODE_PEERS` in `src/hub/start-mode.ts` stay hand-kept for older coordinators: `test/registry-guard.test.ts` fails when one differs from the derived list. Adding a peer is the one change that updates their probe counts and `VENDORS` in `test/helpers/registry-inventory.ts` (`REGISTRY_COMPATIBILITY_PROBES`), the probe literal in `test/adapter-registry-a2.test.ts`, the `ADAPTERS` order in `test/adapter-registry.test.ts` and the literal in `test/adapter-catalog-a2.test.ts`; say so in the PR as an expected test change. `isSupportedPeer` answers from `ADAPTERS`, and for a declared instance from its kind's runtime definition unless the instance resolves to an unavailable reviewer variant; `isKnownPeer` and `defaultPeerRoles` answer from `definitionOf`; `STARTABLE_PEERS` reuses `SUPPORTED_PEERS`.
|
|
13
15
|
- When an adapter lands, add its project config directories to `AGENT_CONFIG_SEGMENTS` (`src/hub/permission-mode.ts`); scoped automatic writes must ask there, while explicit approval and `never-ask` keep their existing behavior.
|
|
14
16
|
- Load external modules only at daemon startup from trusted untracked local declarations: verify the self-contained bundle and content pin before importing captured bytes. Keep CLI/reload paths metadata-only, modules restart-bound and never settings-editable, builtin capabilities restricted, and external-kind instances refused in A4 (follow-up #287). Export conformance registrations and fake assets without starting them on import.
|
|
15
17
|
- Bind scanned external runtime SDK imports to this daemon's own `/adapter` entry; never resolve them against project packages. Refuse `/conformance`, `bun:ffi`, `bun:test` and CommonJS loading at runtime. Isolate module refusals from hub startup and retain unknown module roster rows as inert when metadata is unavailable.
|
|
16
18
|
- Require the immutable captured project catalog at instance, roster, execution and permission resolver boundaries; choose a builtin catalog explicitly only for builtin-only callers; accept `adapters` only as wholly machine-local trusted data and require restart for changes. Refuse missing/mismatched pins, non-echoed load sessions, leader/attach argv and unknown owned-group evidence; require an explicit standalone `--no-leader` before argv termination for Grok (kind, executable or pinned native name); signal only the original verified group. Keep unmapped policy unmanaged, absent budgets exempt and read-only instances non-raisable through native acknowledgement plus host/MCP mutation gates. Claim native support only with its isolated T0 receipt.
|
|
17
19
|
- Consume quota/context through the resolved definition's declared readers; absence stays unknown. Use explicit `attach.externalHello` and `instances` policy for admission, never `identity.mcp`; keep runtime owner/root and reviewer isolation proof in the host. Never let configured `roles` include `conductor`. Keep native stderr, prompt-failure, permission-mode and transport error text out of configured-kind logs and turn-failure reasons. A configured CLI start (`src/cli/configured-acp-entry.ts`) accepts only `--mode headless`, `--as`, `--session-id` and one preview flag, and its preview never shows argv or session values.
|
|
18
|
-
- Validate coherence and conformance enrollment for reviewer and headless variants too. Construct driven conformance peers through `start.create`; name dedicated coverage and concrete reasons for unsupported legs. Do not restore removed placeholder launch, lifecycle, init or permission metadata.
|
|
20
|
+
- Validate coherence and conformance enrollment for reviewer and headless variants too. Construct driven conformance peers through `start.create`; name dedicated coverage and concrete reasons for unsupported legs. Every fixed builtin identity, variants included, has a concrete `<id>:native-proof-ports` reason in `UNSUPPORTED_LEGS`, and only a fixed builtin may have one; every ACP kind keeps `<kind>:restrictive-ports` with exactly `ports added in 2b` until slice 2b supplies those ports (`assertEnrollment`). Do not restore removed placeholder launch, lifecycle, init or permission metadata.
|
|
19
21
|
|
|
20
22
|
## Permissions, modes and approvals
|
|
21
23
|
|
|
@@ -23,6 +25,8 @@ Scope: the Codex, ACP and Pi adapters and the Pi extension (turn `generation` an
|
|
|
23
25
|
- An ACP permission request may carry no `rawInput` (Kimi 2.0.1): the arguments were on the earlier `tool_call` update, which `acp.ts` remembers by `toolCallId`. Kimi 2.1.1 sends no `rawInput` before the answer at all: the argument JSON streams as `content` text on `tool_call_update`, and only complete JSON counts. A payload that still cannot be resolved, or is too long to show whole (marked `[cut, N chars]`), withholds its `allow_always` option - never render a bare tool name as if it described the call.
|
|
24
26
|
- Auto-approval covers the hub's own tools by exact `mcp__agent-hub__<name>` title and only ever picks `allow_once`. Identity comes from the permission request title or, when an agent titles the request with the argument JSON (Qwen), from the announced `tool_call` title bound to the call id and resolved against the session's configured MCP servers, never from payload text, a prefix or an unrelated display title. Never widen it to a prefix or replace it with an agent-selected session mode. Configured ACP kinds (`nativePolicy` or `requireOwnedGroup` set) never auto-approve from a request or tool-call title, even an exact hub tool name; under a locked read-only mode, reject any request whose ACP `kind` is not read, search or think, and never offer `allow_always`. Set Kimi `yolo`/`auto` only for its verified native identity and an operator mode; config never-ask waits for a console y, and other ACP agents stay unmanaged on fresh/resumed starts without setting/resetting modes or failing over their reported mode; refuse runtime changes until their map exists; await the advertised mode change after session new/load and before a prompt, and fail startup on refusal. Use the shared `KIMI_MODE_IDS` mapping (`src/adapters/definitions/kimi-native.ts`, re-exported by `src/hub/permission-mode.ts`).
|
|
25
27
|
- Permission modes (#242) are a person's and live in the daemon's memory only: `permission` and `permission_default` are console-role control messages, `ahub permission` is `console` in `classifyPeerCommand`, and no hub tool or conductor tool sets one. The dashboard and `ahub settings` set one only through `settingsSet` (`src/hub/daemon.ts`, #269), which calls `changePermission` inside `permissionChange` as the console does: never add another path that writes a runtime choice to `permissionModes`; a reloaded project default (#439) is applied only by `applyPendingPermissionDefault`, and only while that peer is absent or offline. Never write a runtime choice to a restart snapshot.
|
|
28
|
+
- Proven read-only shell grants (#816, `src/hub/read-only-shell.ts`, `read-only-shell-proof.ts`) apply only to Pi and the local worker under `ask-when-needed`, at the host permit boundary: parsing grants nothing, and a grant needs the closed program and flag table, trusted executable lookup and the path guard. Between the grant and the spawn the host calls `shellExecutionCheck` synchronously; `ask` re-asks once with `forceAsk` and a second unstable proof refuses without running, and an aborted or superseded turn (`executionGeneration`) never spawns. Never widen the table or skip the recheck to make a command pass.
|
|
29
|
+
- Agent shell authority (#812, `classifyPeerCommand` in `src/cli/identity.ts`, `src/hub/operational-authority.ts`): the Allowed read forms live in `peerReadForms` and are admitted only when the daemon also admits the same form for an ordinary tools connection; the conductor family is the closed `conductorOperation` list, each admitted by `admitOperational` (role recheck, closed audit label, no argument text). A new CLI form is `console` until it is added to one of those tables with a test in `test/identity.test.ts`.
|
|
26
30
|
- A headless ACP Kimi or Codex whose mode lives in the native agent joins the bus only through `reconcileNativeStart` (`src/hub/daemon.ts`): after `start()` the daemon's current mode is applied, then `bus.add` (it can drain at once), then the state `start()` reached is replayed. Every mode change runs inside `permissionChange`, one per peer at a time.
|
|
27
31
|
- Generic permission and generation dispatch in the daemon reads the optional `PeerAdapter` ports (`src/hub/peers.ts`, #667): `permissionModeState`, `setPermissionMode` (resolves only after native acknowledgement; changes approval frequency, never the sandbox), `sessionGeneration` and `checkpointUsesSessionGeneration`; never add a native-class predicate (`isAcpPeer`, `isCodexPeer`) for it. An absent `sessionGeneration` keeps the recovery-session/owner-token fallback; a present but undefined one is unknown and refuses. `BasePeer` installs no default generation, and only an explicit `checkpointUsesSessionGeneration = false` (Pi) skips the checkpoint generation fence. A `permissionModeState` of `unknown` or `unmanaged` refuses a change.
|
|
28
32
|
- Codex takes a mode as an `approvalPolicy` override on `turn/start`, and the override persists on the thread: pass the hub's and the TUI's `turn/start` alike through `applyPermissionPolicy`, and return to `ask` only by sending the captured native policy once. With none captured, refuse while the TUI is attached; never just stop sending the override. Keep the captured native policy and the restoration debt per thread (`nativeApprovalPolicies`, `overriddenApprovals`) across detach and `thread/start` or `thread/resume`; clear a debt only on a successful response to its restoring `turn/start` while the mode is `ask` (`settlePermissionRestore`), so a rejected turn keeps it. While a thread owes a restoration with a known baseline, in any mode, never replace that baseline: a `thread/resume` response and the TUI's own `turn/start` can both echo the sticky override, and the restoring turn sends the kept baseline whatever the TUI supplied. That protection is one turn: after the restoration succeeded, a policy the TUI sends is taken as the person's choice, captured and passed through, because the proxy cannot tell a repeated echo from one. A detached proxy's `clearPermissionMode` is never refused; if the native policy is unknown, return a CLI-visible note that the thread keeps its last policy. Attached refusal directs the person to close the TUI, then run `ahub permission codex ask`.
|
|
@@ -71,6 +75,17 @@ Scope: the Codex, ACP and Pi adapters and the Pi extension (turn `generation` an
|
|
|
71
75
|
- `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`).
|
|
72
76
|
- Pi terminal ownership reads only the existing monitor's current pid/signature/session/file/token observation, expiring after 1 s; unknown verification withdraws proof. Keep fresh shared process-signature checks at tool/effect admission and teardown, and never revive terminal proof across detach, offline, stop or a changed owner/session (#576).
|
|
73
77
|
|
|
78
|
+
## Qwen's dual-output TUI (#286 Part 3)
|
|
79
|
+
|
|
80
|
+
- `src/adapters/qwen-dual.ts` drives Qwen's own TUI through `--input-file` and `--json-file`; it is started only by `ahub qwen --mode tui`, through the definition's `start.createTui` and the `qwen-dual` daemon port, both fixed-builtin capabilities (`assertNativeCapabilityScope`). Qwen's `start.forms` stays `["headless"]`: never add `tui` to it (that would make Qwen a start-mode peer the hub opens by itself, and would change `START_MODE_PEERS`, a cross-release probe). The launcher needs a terminal and the base `qwen` id; the daemon refuses a TUI start for a declared instance, off roster or while a headless Qwen runs.
|
|
81
|
+
- Judge a Qwen TUI by `holdsTerminal()` (its bound owner verified alive, or a session seen with no owner bound), never by peer state: a detached TUI reads offline while Qwen keeps running. A headless start over a held terminal is refused and a TUI start answers `already`.
|
|
82
|
+
- Gate the TUI on `COMPAT["qwen-dual"]` (verified 0.26.1, no `line`), both before the start (`qwenDualVersionAsync`) and from the TUI's own `session_start` (version, `protocol_version` 2, the events read). Never widen the headless `qwen` entry for it, or the other way round: their receipts are separate.
|
|
83
|
+
- `deliver()` sets `busy` synchronously and appends one `submit`; a prompt that is not the hub's own (matched by its text) is the person typing: the peer stays busy, nothing is shared, no approval is asked of the console. A submit that starts with `/`, `!`, `@` or `?` gets an `[agent-hub]` first line: Qwen reads a submit as typed input (slash command, shell line, file include). A turn ends at an `assistant` whose `stop_reason` is not `tool_use` followed by `message_stop` (or a `result`); a `tool_use` message never ends it.
|
|
84
|
+
- Limit 1: Qwen emits nothing for a deny, so after `allowed: false` the adapter ends the turn itself (the delivery `completed`, since a deny is a known outcome and `needs_review` holds the peer's whole queue: measured against the real Qwen; no answer; a notice). Only an unknown outcome (Qwen exited, Esc then a new prompt, an unreadable event file) settles `needs_review`. Limit 2: the watchdog never cancels. `onWatchdog` marks the turn stalled and keeps the peer busy; the next event clears it. Never type into the terminal, send a signal to stop a turn, or add an input type: the protocol has only `submit` and `confirmation_response`. A pending approval is a person deciding, not silence.
|
|
85
|
+
- Only an exact hub tool name is auto-approved (`autoApprove`, the daemon's `HUB_TOOL_TITLES`), and an always-ask class call never is. A `control_response` for a request the hub has not answered means the person answered in the TUI: abort the hub's pending request so a late console answer finds nothing. A request other than a hub tool that names the hub state dir (`qwenDualNamesStateDir` against `guardDir`) is refused before anyone is asked: the input file is a prompt channel (`docs/security.md`).
|
|
86
|
+
- The event file is read from the saved offset in `state.json`. Qwen opens an existing event file at byte 0 without truncating it (measured on 0.26.1), so a fresh or resumed launch starts from empty files (the old event file is kept as `events.prev.jsonl`) and a hub restart adopts a still-running Qwen from the saved offset instead. The owner is bound from the host's process table (the root of the tree of processes whose argv names this adapter's input file; two separate roots is no owner), never from anything the launcher sends. An unreadable event file is `detached` (offline, the `qwen-detached` status notice), never idle, and reattaches when it reads again; a stall shows `qwen-stalled`. An event line over 1 MiB is dropped through its newline and the saved offset moves past it.
|
|
87
|
+
- Recovery never restores this terminal as a headless worker (`QWEN_RECOVERY_SESSION.recoveryRefusal`), and `stopPeer` refuses a Qwen TUI: the hub never signals a person's Qwen.
|
|
88
|
+
|
|
74
89
|
## TUI launchers, native facts and Antigravity
|
|
75
90
|
|
|
76
91
|
- Kimi TUI is a pull-only WsPeer under the one kimi id, with no hub busy/watchdog/permission/quota machinery. Only the person's launcher merges or removes its project MCP entry; a daemon headless start refuses a stale entry and never edits the file. Scrub mode/channel markers from the native child; managed AGENTS instructions and tool descriptions carry the pull rules, not MCP initialize instructions.
|
|
@@ -90,15 +105,15 @@ Scope: the Codex, ACP and Pi adapters and the Pi extension (turn `generation` an
|
|
|
90
105
|
## Reviewers
|
|
91
106
|
|
|
92
107
|
- Keep the default reviewer kind order Claude, Codex, Kimi (selection rules: `tasks.md`).
|
|
93
|
-
- Keep the retained agy reviewer slot unavailable with exact reason `agy is an implementer peer (#485)`, including explicit reviewer-pool requests. Do not add agy reviewer launch/admission or personal configuration writes to #234; implementer work belongs to #485.
|
|
108
|
+
- Keep the retained agy reviewer slot unavailable with exact reason `agy is an implementer peer (#485)`, including explicit reviewer-pool requests. The slot is data only: the running-review fence (`runningReviewAuthority` in `src/hub/daemon.ts`) binds only a definition with `reviewer.backend`, so it never fences agy's own task reads (#792). Do not add agy reviewer launch/admission or personal configuration writes to #234; implementer work belongs to #485.
|
|
94
109
|
- For fresh Kimi reviewers and task-bound MCP (#533), retain the common per-child task/head/review-revision/generation verdict fences, privacy screening and cleanup-before-settlement. Expose only a host in-process two-tool MCP capability for `hub_review` and `hub_task_show`; never give the native child the full hub control token. Supply source evidence only through the screened review brief/read result and host facts; exclude native Read/file/shell tools. Admit only compat-covered Kimi versions when current runtime preparation passes; otherwise keep it unavailable with the refusal reason. Keep actual interactive-TUI/usage ceilings separate from completed live/fake receipts; do not reuse historical Seatbelt/dummy-provider probes as revised-contract passes.
|
|
95
110
|
- Start fresh Kimi reviewers without Seatbelt, with private `KIMI_CODE_HOME`, managed-provider-only configuration, clean temporary cwd, explicit empty skills directory and exactly `mcp__agent-hub__hub_review` plus `mcp__agent-hub__hub_task_show` in its agent tools. Use directory symlinks for the person's native `credentials/` and `oauth/`, and link `device_id` to the native identity; do not copy token files, select static-API-key mode or set `KIMI_DISABLE_OAUTH_LOCK`. Let native OAuth share refresh/atomic rename/`proper-lockfile` coordination and the existing Kimi account budget. Revoke verdict authority before shutdown; allow bounded TERM grace while the OAuth lock is held, then use verified owned-group teardown. Report a forced-kill/re-login requirement without attempting host auth repair.
|
|
96
111
|
- Bind every running print review to its immutable envelope head and review revision. Recheck `Tasks.reviewBindingCurrent` for task-MCP operations, legacy print reads and the verdict authority after awaits; an old child never gains a replacement request. A committed verdict acknowledgement does not authorize subsequent reads or verdicts.
|
|
97
112
|
- `ReviewerPrintPeer` (`src/adapters/reviewer-print.ts`, #223, #519; `ClaudePrintPeer` is its Claude wrapper) runs one fresh child per review and clears `reviewing` at the leader's exit and first in `kill()`. A stop or watchdog settles `needs_review` at once, before the kill; `completed`, `idle` and `offline` come only after the whole process group is confirmed gone, and a failed cleanup keeps the child handle and stays `busy`. The daemon's `reviewAuthority` compares `reviewToken`: never widen it.
|
|
98
113
|
- Every print child (reviewer and Claude implementer) is spawned, bounded and stopped only through `OwnedPrintChild` (`src/adapters/claude-print-child.ts`): its `done` settles only after the whole group is confirmed gone, and a failed stop rejects with `PrintChildCleanupError` while the owning peer keeps the handle. Never add a second spawn path to an adapter.
|
|
99
114
|
- A reviewer backend is a fixed builtin `ReviewerBackend` in a definition's `reviewer`, never config. Its `prepare` must return a complete isolation receipt (matching backend id, binary, version, profile, evidence and `readOnly` true, plus `strictMcp` and `ephemeral` true, or for a `nativePolicy` backend `enforcement: "native-policy"`, `freshSession` and `hubToolsOnly` true) before every child. A `taskMcp` backend refuses without the host's `taskMcp` port and gets only that task's MCP config, never the hub-wide one. `admit(env)` and `launch.current()` are rechecked right before spawn, and a `watchCurrent` launch is polled while the review runs: a change aborts it as `review policy changed`; a missing or failed proof refuses, never a weaker launch or a continuing native session. The daemon puts the reviewer on the bus only after `start()` proved isolation; a failed start or a `start_failed` report marks it in `reviewerUnavailable`, which routing reads as `failing`. An unavailable reviewer variant has no reviewer backend or launch code; `startPeerBody` refuses it before preflight, independently of any native TUI launcher.
|
|
100
|
-
- 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`.
|
|
101
|
-
- Codex reviewer isolation (`src/adapters/codex-review-isolation.ts`): accept only a `codex-cli` version that compat `codex-review` admits (verified or the person's trust, #691), and still run every proof below after trust; pass `-c notify=[]` (legacy notify off) on every launch; prove model-free that Apps, plugins and hooks are disabled (`features list`), ambient MCP servers are replaced by disabled stubs, and the hub server alone is enabled with exactly `hub_review` and `hub_task_show`; run the native sandbox fixture probe before every launch; hash the binary once and fence binary and config files by metadata. Credential denies come only from the bounded `HOST_CREDENTIAL_PATHS` and `HOST_CREDENTIAL_ENV` lists, denied at the lexical and canonical path; refuse glob syntax or a root inside the checkout. A root that is dangling or unreadable refuses too; the reason names only its path class (`~/.docker`, `$DOCKER_CONFIG`), never a path or contents. Never pass the control token in a reviewer child's argv, env or MCP config: keep the hub state dir in its `privatePaths`. A refused `prepare` carries a `causeClass` from the closed `REVIEWER_CAUSE_CLASSES` (`version`, `binary`, `mcp_inventory`, `plugins_hooks_apps`, `sandbox_proof`, `credential_root`, `timeout`, `unknown`; `reviewer-print.ts`, #722): `prepare` tracks the proof leg it is on, and `timeout` means a metadata command ran out of its 2 s or the 12 s budget, never an abort. The host builds the reason only through `reviewerUnavailableReason(cause, detail)`: a fixed sentence per class, plus a credential-root path class (`~/.docker: a dangling link`) as the only detail. The one exception is a failed `start()` whose error is a version refusal (`VERSION_REFUSAL`) or comes from a `nativePolicy` backend: the daemon shows that sanitized message, capped at 1000 characters (#691). `ReviewerPrintPeer`
|
|
115
|
+
- 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. Complete a normal no-verdict delivery only after the host confirms a current re-offer; an uncertain or failed fallback stays `needs_review`, with its `REVIEWER_FALLBACK_UNCONFIRMED` reason excluded from automatic moved-review settlement; ordinary superseded holds keep their existing rule. Log one bounded no-verdict line from exit code and allowlisted hub-tool metadata only, with full refusal privacy/secret screening before sanitization and truncation; retain the first full host refusal across native echoes, and withhold differing later host refusals rather than replacing or accumulating them; stale/private context is withheld. Record only allowlisted tool names and refusal text; never retain argument objects, task/diff fields or model-message events as trace data. `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`.
|
|
116
|
+
- Codex reviewer isolation (`src/adapters/codex-review-isolation.ts`): accept only a `codex-cli` version that compat `codex-review` admits (verified or the person's trust, #691), and still run every proof below after trust; pass `-c notify=[]` (legacy notify off) on every launch; prove model-free that Apps, plugins and hooks are disabled (`features list`), ambient MCP servers are replaced by disabled stubs, and the hub server alone is enabled with exactly `hub_review` and `hub_task_show`; approve exactly those two MCP tools per tool, never with a server-wide approval default; before marking a reviewer backend/version verified, record a successful model-backed hub tool call as well as the model-free inventory (inventory alone is not verdict-delivery proof); run the native sandbox fixture probe before every launch; hash the binary once and fence binary and config files by metadata. Credential denies come only from the bounded `HOST_CREDENTIAL_PATHS` and `HOST_CREDENTIAL_ENV` lists, denied at the lexical and canonical path; refuse glob syntax or a root inside the checkout. A root that is dangling or unreadable refuses too; the reason names only its path class (`~/.docker`, `$DOCKER_CONFIG`), never a path or contents. Never pass the control token in a reviewer child's argv, env or MCP config: keep the hub state dir in its `privatePaths`. A refused `prepare` carries a `causeClass` from the closed `REVIEWER_CAUSE_CLASSES` (`version`, `binary`, `mcp_inventory`, `plugins_hooks_apps`, `sandbox_proof`, `credential_root`, `timeout`, `unknown`; `reviewer-print.ts`, #722): `prepare` tracks the proof leg it is on, and `timeout` means a metadata command ran out of its 2 s or the 12 s budget, never an abort. The host builds the reason only through `reviewerUnavailableReason(cause, detail)`: a fixed sentence per class, plus a credential-root path class (`~/.docker: a dangling link`) as the only detail. The one exception is a failed `start()` whose error is a version refusal (`VERSION_REFUSAL`) or comes from a `nativePolicy` backend: the daemon shows that sanitized message, capped at 1000 characters (#691). `ReviewerPrintPeer` classes a missing or invalid receipt as `unknown`, a `launch.current()` change before spawn as `binary`, and any other start error as `unknown`. Keep admission refusals separate from start failures: skip only a stale-bound request with one task-id log and no reroute; reroute a still-current request whose policy changed as `no_verdict`, without marking the reviewer unavailable. The daemon writes that reason to the `reviewerUnavailable` map (read by routing and shown as the `reviewer` line of `ahub status`), and a `hub.log` line names the class. Never add native CLI text, a path, a version output or a token to the class, the reason or the log.
|
|
102
117
|
|
|
103
118
|
## Headless Claude implementer (#525-4)
|
|
104
119
|
|
|
@@ -13,3 +13,4 @@ Scope: the CooperBench runners, their fixtures, teardown and recovery. Read befo
|
|
|
13
13
|
|
|
14
14
|
- Treat an earlier journal-less ACP attempt as possibly affected by cancellation overlap (#304) when its recorded peer `state` goes idle without an answer near `watchdogMs` after the last recorded activity, followed by rapid `failure` entries and `qwen_log` prompt-failed lines, then `active_end` as `peer-failure` with that reason. The driver's tap does not record `undeliverable`, main logged no watchdog cancel, and Qwen's busy-refusal wording is still unmeasured: these are candidate signs, not proof, and absence does not clear an attempt. Keep historical results unchanged and the native measurement owed in `docs/smoke.md`.
|
|
15
15
|
- After #304, an unreported cancelled prompt leaves a cancellation-deadline line in `qwen_log`, a recorded offline state about 30 s after the watchdog, and `active_end` as `native-failure` on the next driver tick. The driver starts each peer once and cannot restart it within the attempt; its in-memory queue ends with the process. The end cause of affected attempts is not comparable across this fix (formerly `peer-failure` or the wall limit); keep that distinction in later collections, per issue #304's Decision, without changing historical scores or results.
|
|
16
|
+
- The benchmark local-worker tool surface is `src/local/tools.ts`: its `bash` permit now carries a host shell recheck (#816, see local-worker.md). A benchmark driver that stubs `ctx.permit` needs no change, since an absent `shellExecutionCheck` means `valid`.
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
- Console attention navigation selects a delivery only by an exact visible row id. A missing id or row prints read-only guidance; never select by peer or fall back to the first Queue row.
|
|
10
10
|
|
|
11
|
-
Scope: the operator console, control WS and its protocol, state files, the dashboard listener and settings, the Claude channel's reconnect, `ahub setup`, git snapshots, upgrade and recovery. Read before editing `src/adapters/definitions/native-recovery*.ts`, `src/adapters/definitions/native-policy-aux.ts`, `src/adapters/definitions/native-config-*.ts`, `src/adapters/definitions/native-cli-*.ts`, `src/cli/console.ts`, `src/cli/console-state.ts`, `src/cli/attention.ts`, `src/ui/needs.ts`, `src/hub/daemon.ts`, `src/hub/ui.ts`, `src/hub/settings.ts`, `src/hub/control-client.ts`, `src/adapters/claude-channel.ts`, `src/hub/restart.ts`, `src/hub/recovery-store.ts`, `src/hub/snapshots.ts`, `src/hub/manager.ts`, `src/hub/lifecycle.ts`, `src/hub/crash.ts`, `src/hub/start-mode.ts`, `src/hub/terminal-open.ts`, `src/hub/recovery-error.ts`, `src/hub/db-timeout.ts`, `src/hub/config-reload.ts`, `src/hub/config-apply.ts`, `src/hub/config-tier.ts`, `src/hub/config-edit.ts`, `src/hub/config-edit-host.ts`, `src/hub/claude-launch-proof.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`, the memory code modules (`src/hub/memory-symbols.ts`, `src/hub/memory-code-overlay.ts`, `src/hub/memory-code-head.ts`, `src/hub/memory-stale-anchors.ts`, `src/cli/memory.ts`), `src/hub/native-hook-launch.ts`, `src/cli/output.ts`, `src/ui/index.html`, `src/hub/task-code-group.ts` (dashboard Code group) or the instance modules (`src/hub/instances.ts`, `src/hub/instance-config.ts`, `src/hub/instance-execution.ts`), or before adding a native peer.
|
|
11
|
+
Scope: the operator console, control WS and its protocol, state files, the dashboard listener and settings, the Claude channel's reconnect, `ahub setup`, git snapshots, upgrade and recovery. Read before editing `src/adapters/definitions/native-recovery*.ts`, `src/adapters/definitions/native-policy-aux.ts`, `src/adapters/definitions/native-config-*.ts`, `src/adapters/definitions/native-cli-*.ts`, `src/cli/console.ts`, `src/cli/console-state.ts`, `src/cli/attention.ts`, `src/ui/needs.ts`, `src/hub/daemon.ts`, `src/hub/ui.ts`, `src/hub/settings.ts`, `src/hub/control-client.ts`, `src/adapters/claude-channel.ts`, `src/hub/restart.ts`, `src/hub/recovery-store.ts`, `src/hub/snapshots.ts`, `src/hub/manager.ts`, `src/hub/lifecycle.ts`, `src/hub/crash.ts`, `src/hub/start-mode.ts`, `src/hub/terminal-open.ts`, `src/hub/recovery-error.ts`, `src/hub/db-timeout.ts`, `src/hub/config-reload.ts`, `src/hub/config-apply.ts`, `src/hub/config-tier.ts`, `src/hub/config-edit.ts`, `src/hub/config-edit-host.ts`, `src/hub/claude-launch-proof.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`, the memory code modules (`src/hub/memory-symbols.ts`, `src/hub/memory-code-overlay.ts`, `src/hub/memory-code-head.ts`, `src/hub/memory-stale-anchors.ts`, `src/cli/memory.ts`), the task brief host (`src/memory/brief.ts`, `src/hub/memory-plan-guard.ts`), `src/hub/native-hook-launch.ts`, `src/cli/output.ts`, `src/ui/index.html`, `src/hub/task-code-group.ts` (dashboard Code group) or the instance modules (`src/hub/instances.ts`, `src/hub/instance-config.ts`, `src/hub/instance-execution.ts`), or before adding a native peer.
|
|
12
12
|
|
|
13
13
|
- The plugin bundle is installed apart from the daemon. Breaking a control WS message contract bumps `PROTOCOL` in `control-client.ts`; a new request answered as unknown or an optional field preserves it, as does additive optional public task metadata in `ui_snapshot` that older clients ignore (such as `deps` in #246).
|
|
14
14
|
- `peer_stop` is console-only and stops only a hub-owned headless ACP, Pi, local, print-reviewer or Claude implementer 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.
|
|
@@ -21,6 +21,7 @@ Scope: the operator console, control WS and its protocol, state files, the dashb
|
|
|
21
21
|
- Dashboard Code group (#684 2c): `codeGroup` (`src/hub/task-code-group.ts`) is the only dashboard classifier of changed symbols against `plan.symbols`, called in `uiSnapshot` with the public `codeSymbols`, `tasks.publicMetadataText` and `tasks.isPii`; the page (`codeGroupSection`) draws it and never matches names. A private task gets no `codeGroup`. A stored receipt (one with `repositoryKey`), a missing or an unknown one is `state: "unknown"`. A malformed or screened-out plan name is counted in `planWithheld` and marks the group incomplete, never dropped. Lists stop at `CODE_GROUP_MAX` (40) with the rest in `more`, and `counts` are totals. The memory view reads a `node` of up to 512 characters only when it starts with `symbol:` (200 otherwise).
|
|
22
22
|
- SDLC band timer (#481 S7): the daemon's band interval (`bandsEveryMs`, default 5 min) is the only caller of `tasks.proposeFromBands`. It skips while stopping or in recovery, when `tasks.bandsState()` is not ready or `bandsHeld` holds, and when `board.statusRevision()` with the band signature is unchanged since the last pass that ran; it records that key after the write. A failure is one sanitized `hub.log` line. Writer rules: `tasks.md`.
|
|
23
23
|
- `ahub board` (#724): `renderBoard` prints one line per task only with a terminal width and without `--full`; `--full`, `ahub task show` and output without a width keep the full table and SDLC advice. `STAGE_CODES` (`src/cli/output.ts`) keys on blocker-text prefixes from `src/hub/sdlc.ts`: rewording a blocker there updates its code in the same PR. TITLE is cut, never wrapped; STAGE is never dropped (it falls back to the meter and the code); a PII row keeps its `(ahub task show N)` hint while 8 title columns remain.
|
|
24
|
+
- CLI output default (#814): a command prints for a person unless `--json` is given. The recovery and upgrade commands follow it too (`src/cli/upgrade-output.ts` composes `planLines`, `renderPlan`, `renderOperation`, `renderBlocked` and `choiceInstruction`, so the non-interactive text never says what the screens would not): a pipe and `--dry-run` print the plan with blockers, reconnect-only and fresh-start peers in stdout, `--json` keeps the document and the stderr lines (#206), and `recoveryDisplay` takes a pipe's width from `COLUMNS`. Hub tool text (`task show`, the backlog and release commands) is parsed on the CLI side by `showToolText` and rendered by `src/cli/task-output.ts`; `--json` prints the text byte for byte, unparseable text prints as it came, and a private task is rendered as the stub the hub sent, never asked for more. A command that takes free text reads `--json`, `--full` and `--color=` through `splitPresentation` (`edges`): only a leading or trailing run is a flag, and a value flag's value never is, so a reason keeps its words. `test/cli-json-guard.test.ts` also scans every file in `src/cli/` for a print of a hub tool's text (`console.log(await taskOp(<op>))`, or `console.log(x)` where `x` is bound from one) and passes only when the print is inside a `--json` branch, the op is in the named `SENTENCE_OPS` (ops whose daemon case returns prose, each checked against the daemon), or it is named debt with a reason (`TEXT_DEBT` for JSON ops a later task converts, `CONDUCTOR_DEBT` for the conductor-role commands, `REFUSAL_FALLBACK` for the refusal sentence printed after `--json` and the table were handled); a new JSON-returning op printed bare fails it, and an entry that matches no print fails too. Convert a command by rendering it through `showToolText`, never by adding it to a list. A renderer is a pure function in `src/cli/` (`sdlc-output.ts` for the `sdlc` read commands) and the call site keeps its own visible `if (options.json) console.log(JSON.stringify(...))`, so `test/cli-json-guard.test.ts` can see the branch. Never print JSON by default: add the command's `--json` branch, or name the site in the guard's `DATA_FORMAT` (JSON by purpose: `export`, `sdlc export`, `bench export`, the PreToolUse hook reply) with its reason. The guard accepts a `--json` branch only when the condition PROVES the flag (`selectsJson`): `X.json` on an option holder (`options`, `display`, `present`), `args|flags.includes("--json")`, a known alias, `&&` when either side does, `||` only when both do, `=== true`. Anything else (`!x`, `=== false`, `|| other`, `?? x`, a comparison, an unknown name) is not a guard, so a new shape fails until it is written one of these ways. A renderer table never exceeds the terminal: `grid` (`sdlc-output.ts`) wraps cells, then falls back to one labelled block per row, and keeps every identifier whole. `NOT_YET_CONVERTED` is debt the later #814 tasks delete; an entry that matches no site fails the test, so the list only shrinks. `sdlc` drops `--json`, `--full` and `--color=` from its operands only for its read subcommands (`sdlcOperands`): the others keep their argv exactly. A hub tool's JSON text that does not parse prints as it came, never as an empty report.
|
|
24
25
|
- Settings (#269): the dashboard's `setting`, `setting_undo`, `setting_preview` and `setting_reload` actions and the console-role `settings_get|set|undo|preview|reload` requests go through `settingsSet`, `settingsUndo`, `settingsPreview` (`settingsUndoPreview` for `undo: true`) and `reloadConfig` (`src/hub/daemon.ts`) and the registry in `src/hub/settings.ts`. A new editable setting is a registry entry, never a new write path; a key that chooses what the hub runs or where secrets or task text go joins `NEVER_EDITABLE` instead. Writes go only to `.agenthub/config.local.json` and `.agenthub/routing.local.toml` through `store()`: the loader's own validation first, temp file and rename, never a file git tracks. Authority comes from the dashboard session (`DashboardSession`, `src/hub/ui.ts`) or the console role, never from the request body; `ui_action` from the manager is an ordinary session. Never return a setting's value in an event or notice except through `recordSetting`.
|
|
25
26
|
- Structural config edits (#689): `src/hub/config-tier.ts` is the one tier rule (editor, dashboard refusal and `structuralLeaf` in `authorizeConfigLeaf`), `src/hub/config-edit.ts` plans and writes (loaders first, revision checked twice, bounded backups, atomic), `src/hub/config-edit-host.ts` owns authority, the typed tier 3 confirmation, the `config_edit` event and the scoped publish. The daemon only supplies ports (`createConfigEditor` in `daemon.ts`); never validate, write or classify in `daemon.ts`. A new editable structural key is an entry in `config-tier.ts` and nothing else; a key that chooses what the hub runs or where secrets go is tier 3 and is never offered to the dashboard (the hub refuses it, the page also hides it). The audit event and every notice carry key paths and value hashes, never a value. `structural` reaches `authorizeConfigLeaf` only through `structuralScopes` for the one scoped reload. `sdlc.toml` is read once at start, so it joins `pendingRestart` through `allPendingRestart()`, never through `reloadConfig`. A new ahub settings subcommand that writes is classified `console` by default; keep it that way.
|
|
26
27
|
- Live reload (#439): `reloadConfig` is the only publication path. Settings writes and undo, `ahub settings reload`, the dashboard reload and the debounced watcher of the four `CONFIG_SOURCE_FILES` (`src/hub/config-reload.ts`) all reach it; `prepareConfiguration` validates the complete candidate before anything is published, and recovery or shutdown holds it. A write or undo passes its own key as `scope` and publishes only that path, and `acknowledgeConfiguration` records only the accepted paths, so other edits stay pending. Every changed key, inside the registry or not, is decided by `authorizeConfigLeaf` (`src/hub/config-apply.ts`): a refused key keeps its accepted value and is reported by name while the others apply, `roster.*` applies only through its own scoped roster write, and a change of the settled planner or conductor holders (`roleHolders` over `resolveSettledRoles`) is guarded even when a role list shrinks. Outside a named write, only the console's `settings_reload` (authority `terminal`) adopts guarded file edits, by sending back the `confirmation` token bound to the candidate `fingerprint` and `configRevision`; changed bytes or accepted state refuse it, and a watcher, dashboard or settings session never confirms. An undo that names `key` and `undoDigest` writes only while `pendingUndoIdentity` still matches both. Keep config-source snapshots plain and cloneable; classify dynamic ids with validated running/source identity scopes, never suffix discovery or a builtin fallback. Compare restart-bound declarations with the running config on every reload, never a pending desired value accepted by an earlier reload; derived defaults stay bound to that running declaration. A `restart` key (`configApply`) keeps its running value and is listed in `pendingRestart`; reload notices and events carry key names and reasons, never values. The console settings editor (the Settings panel, or the Peers panel's `w` WIP shortcut, which opens only an existing `tasks.wip_limits.<peer>` row for a rostered peer listed in status `taskWip`) stays bound to the panel that opened it (`SettingsEdit.panel`) and arms a write only from a preview reply that matches that panel and its open edit's token, key and value; pasted text never confirms. Console Tasks backlog actions (`p`, `a`, `e`, `R`, `c`) only prefill person CLI commands (`backlog pull-ready|plan|return`, `task cancel`) with the row's immutable id and its planning or flow revision; `validateLockedRow` rechecks the row's state and revision at Enter and again at `y`, and no console path writes the board directly (#532).
|
|
@@ -52,7 +53,7 @@ Scope: the operator console, control WS and its protocol, state files, the dashb
|
|
|
52
53
|
- 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".
|
|
53
54
|
- A target that stopped after it started is restarted only through `recordRestart` (`src/cli/upgrade.ts`), recorded before the new one starts and at most once per project per resume: each done or waiting `restored:<peer>` binding becomes `retired:<peer>`, whose terminal is closed (`closedRetired:<peer>`) or found absent before that peer is relaunched, and is never revalidated against the new instance. Keep repaired-session provenance across every target restart, disabling automatic fresh/zero-turn replacement. A person may explicitly accept loss of a repaired Codex/Claude conversation only on typed positive missing transcript/rollout evidence and a disposal-time recheck; refuse live-test repaired overrides after read-only receipt classification and before runner/lock or coordinator mutation, fail closed on unclassifiable scope, and repeat in core before lock and after under-claim read, preserving ordinary #408 delegation; record the one-use grant and freshOverride evidence, and consume the grant on exact attachment. Pi stays excluded: positively missing accepted Pi files get stop-and-archive plus the normal project-scoped ahub pi command; bookmark retirement never falls back fresh in the same start. Clear the repair fence only for a done or waiting binding retired from that target; an uncertain creation without a durable binding keeps its fence. Launcher evidence is read for every instance in `restarts`, and a session attached without an id is unknown, never gone (#225).
|
|
54
55
|
- After a managed terminal is created, persist its binding before waiting up to `RECONNECT_WAIT_MS` for authenticated attachment to the expected target instance, operation and conversation (#414). Resume waits on the saved binding without creating another terminal. Count only attached, non-offline session ids; when the chosen terminal has no current launch record, a confirmed-ended row for another handle or an id-less attachment retains the bounded wait after older-launcher checks. A late current record must match the saved terminal; unknown or live replacement evidence still blocks. Timeout keeps the receipt blocked and records only terminal/hub metadata; a last native connect attempt that was never recorded is unknown, never inferred from terminal readiness or offline state. Final identity and preservation verification stays strict. `createTerminal` re-reads the launch record and Orca binding of an idle replacement every `MAP_POLL_MS` until it maps to the original peer and session, a known identity or session contradicts it, or the deadline passes; only then does it report `ambiguous-create`. Missing identity alone is never a mapping (#599).
|
|
55
|
-
- An operation receipt stays `schema: 1`. Only `abandoned()` writes 2, on an operation with a recorded `disposition` whose coordinator predates #215: in the same write as the disposition, or, for a disposition 0.12.20 recorded at 1, by the next command that holds the runner claim. `validateReceipt` accepts 2 only with a disposition (#227). Never write 2 on an operation that can still be resumed.
|
|
56
|
+
- An operation receipt stays `schema: 1`. Only `abandoned()` writes 2, on an operation with a recorded `disposition` whose coordinator predates #215: in the same write as the disposition, or, for a disposition 0.12.20 recorded at 1, by the next command that holds the runner claim. `validateReceipt` accepts 2 only with a disposition (#227). Never write 2 on an operation that can still be resumed. Disposal clears `error` and `failure`, so each `stop-and-archive` audit entry keeps the operation's `error` at that moment as `cause` (#776): only text the receipt already held, never task or message text.
|
|
56
57
|
- Four checks read another release's source text for an exact line: `coordinatorCurrent` (`export async function disposeRecovery` in `src/cli/upgrade.ts`), `targetReadsWaivers` (`export function readRecoveryWaivers` in `src/hub/restart.ts`), `coordinatorRestarts` (`export function recordRestart` in `src/cli/upgrade.ts`) and `supportsAction` (`export async function reattachRecovery` in `src/cli/upgrade.ts`). Renaming, moving or un-exporting one makes this release read as an older one: keep those lines until a capability list replaces the checks. The reattach CLI dispatch must use the same `supportsAction` check as its printed command; never redirect a fallback command back to a coordinator without that action. Recovery also reads a target's literal `OPTIONAL_PEERS` (`src/hub/peer-catalog.ts`, through `legacyTargetSupport`) and `START_MODE_PEERS` (`src/hub/start-mode.ts`, through `legacyHeadlessArgv`), declared in `src/adapters/definitions/native-recovery.ts`; keep both literal declarations until a published capability replaces them.
|
|
57
58
|
- Publish terminal ownership only after the current native owner is proven through `terminal_owner` (#482); capture and start acknowledgement are pending evidence only. Preserve unrelated and invalid rows, scope current evidence to project/instance/peer, and recheck the same native generation after asynchronous Orca reads. Keep Claude's hook record pre-spawn. Compatibility and retention ceilings are described in Operations, controlled recovery.
|
|
58
59
|
- `reattachRecovery` (`src/cli/upgrade.ts`, #463) relaunches only the peer a blocked receipt's `blockedReattachment` names, once per its `reattachments` marker, after the lock, the runner claim and a re-read, and only against the same held target instance and version. The terminal proof is `inspectSavedTerminal` (`src/cli/terminal-recovery.ts`): only an `orphaned` pane without positive connected evidence or `missing` (a typed stale handle and a complete, untruncated inventory) counts as gone; `unknown`, `changed`, an orphaned-but-connected pane or a disconnected-only reading never authorizes a terminal. It counts as reattached only on a `done` restore receipt with the exact recorded session id, session file and project root, then leaves the operation blocked for `recovery resume`. It is person-only: `classifyPeerCommand` and `liveTestRefusal` refuse it from an agent shell.
|
|
@@ -91,6 +92,7 @@ Scope: the operator console, control WS and its protocol, state files, the dashb
|
|
|
91
92
|
- The memory client (#752) is the in-process amem `MemoryClient` built once in `startDaemon` from the alias-resolved `memoryProject`, the bound store and candidates, `screenMemory` and the capture binding; its `check` rechecks that binding and the project before each call. `DaemonOptions.memoryClient` is a host-only seam, never read from configuration or a tool; `memory.worker_url` is ignored legacy configuration, never a destination. Local capture takes `memoryProject`, never the project chain. Shutdown aborts `syncStop`, awaits `syncRun`, then closes `memory`, the candidates and the store, in that order.
|
|
92
93
|
- Symbol anchors (#769, #770): resolve `memorySymbols` only in the `prepareReviewBrief` pass that fills `reviewSymbols`, under the same key and eviction, through `resolveMemorySymbolNodes` (a positive, unambiguous `exists` at the brief's head, at most `MEMORY_SYMBOL_LIMIT` candidates within `MEMORY_SYMBOL_START_BUDGET_MS`); a filled key is reused by the completion pass, a PII task is never resolved, and one that turns PII or changes owner meanwhile resolves unknown. `memorySymbolNodes` answers only for a non-PII task whose `codeSymbols.head` is its `refs.commit`. Raw and normalized symbol forms are query compatibility only (`taskMemoryQueryNodes`, `planGuardNodes`): never rewrite a stored note or id. `ahub doctor --stale-anchors` (`memoryStaleAnchorsCommand`, `src/cli/memory.ts`) is read-only: it never creates a missing profile, machine or shard, never edits, deletes or promotes a note, and reports unavailable, private or ambiguous evidence as unknown, never stale.
|
|
93
94
|
- Read-time code neighbours (#771): derive them only through `memoryCode` and publish only through `currentMemoryCode` (`src/hub/daemon.ts`), which rechecks the memory binding, the overlay head against its `readMemoryCodeHead` proof and every node's canonical id, and screens each attached note again; never store derived `calls` edges or neighbours in amem. Neighbour queries run through `codeFacts.neighbours` with `priority: "optional"`, so review and completion queries win. Only `symbol:` starts query: the dashboard only through `uiMemorySnapshot` for the `memory` view with `codeNeighbours: true` and a node of at most 512 bytes (`ui_snapshot` answers a failure with `memory view unavailable`), and `hub_memory_graph` only after `memory_read`, rechecking the caller's connection generation, private turn and PII context after the await. Console task detail reads `taskMemoryResult(task, true)`, which never starts a query; its `relatedMemory` is a count or `unknown`. A PII task or turn never queries. Keep the cache ceilings (32 entries, 30 s known, 5 s unknown, 5 s between derive starts), and keep direct path/title/symbol notes in `taskMemoryResult` independent of code availability: an optional code failure never drops them.
|
|
95
|
+
- Task briefs (#779): with a memory store, `Briefs.forTask` (`src/memory/brief.ts`) takes relevance, ordering and line text from agent-mem's `buildIntentBrief` through `taskIntentBrief` (`src/hub/daemon.ts`); the hub keeps only policy and binding. Screen rows through `screenMemory` and `publicMetadataText` with the local deny paths, and recheck `allowed`/`assertCurrent` (same task revision and owner, config revision, not stopping, no PII task, private turn or PII context) after every await. An `unknown` result or a changed context gives no brief, never a partial one. Host plan-guard fails (`priority`) keep the front of the brief even when the library's cap left them out, and the per-peer dedupe and `brief_items` cap apply to whole library rows. Derived code neighbours only add `symbol:` selectors and a failed derive never removes direct intent memory. `src/hub/memory-plan-guard.ts` only adapts the library's `planGuardNodes` and `matchingPlanFails` to the task shape: never add matching logic there.
|
|
94
96
|
|
|
95
97
|
- Bind adapter and instance declarations once at startup through the daemon-local `adapterCatalog` (`createProjectAdapterCatalog(loadedConfig.adapters, externalDefinitions)`, the definitions `loadExternalAdapterDefinitions` imported at startup; a reload passes the same `externalDefinitions` to `loadConfig` and imports nothing), its `definitionOf` closure and the `configuredIds`/`extraIds` catalogs; never publish them to the global registry or take them from a request. `adapters` and `instances` are restart-class, and `adapters` is `NEVER_EDITABLE` and in `ALL_MACHINE_LOCAL`. Settings rows come only from `projectSettings(declarations, adapterCatalog)`; a `locked` row (a read-only instance's permission) is refused by `checkSettingValue` and `settingRefusal` for every authority, and the console and dashboard never arm an edit or undo for it.
|
|
96
98
|
- Configured ACP starts (`startPeerBody`): before effects refuse native launch overrides (model, route, backend, unattended, provider profile, session file, fresh) and an invalid session selector; answer `already` only for the same requested native session and never pass `qwen_cmd`; after a failed start keep the owner with `pendingOwnedProcess` on the bus, let `peer_stop` admit that offline owner and refuse starts until it is confirmed gone, reporting only a withheld-detail reason. In `taskOp`, refuse a read-only instance's op outside `READ_ONLY_HUB_TOOLS` before effects. Publish `configuredAdapters`, `startablePeers` and `readOnlyPeers` in status as bounded ids only. Keep controlled recovery refused (`session.recoveryRefusal`); crash resume passes only a bounded, control-free recorded `sessionId`, never a recorded model.
|
|
@@ -109,4 +111,4 @@ Scope: the operator console, control WS and its protocol, state files, the dashb
|
|
|
109
111
|
- Share one monotonic 250 ms snapshot Git budget until the next event-loop I/O turn. Repository/HEAD probes, capture and end diffs consume the same remaining deadline; exhaustion or unreadable diff evidence records unknown trees and no attribution/undo, with one diagnostic per burst. Never substitute a stale baseline or apply a late result (#576). `DaemonOptions.snapshotClock` is host-only (tests hold it, rule: `tests.md`): never read it from configuration or a tool, production leaves it unset (`performance.now`), and the share stays 250 ms with `snapshotBurstMs` still clamped to it. Every deadline comparison goes through `budgetNow(budget)`, never `performance.now()` against `budget.deadline`.
|
|
110
112
|
|
|
111
113
|
- Channel tool-result hints (#493, #221): `withPendingHint` (`src/adapters/claude-channel.ts`) appends fresh `delivery_holds` counts, and drops the "call hub_inbox" advice whenever the same reply carries a held-delivery hint, because `bus.pull` refuses under that hold and the advice would send the session into a refused read. A `hub_inbox` result that already says how many messages remain unread adds only the important count, never a second queued count. Do not add a Claude client-version gate on steer without a probe of the older client and an optional hello field that carries the version (background: README).
|
|
112
|
-
- Memory sync (#438): stop an in-flight `syncMemory` only through `syncStop` (`src/hub/daemon.ts`), and await `syncRun` before the memory store closes, as `stopOnce` does. agent-mem runs the sync's git in its own process group, so a process that ends without `daemon.stop()` leaves that git and its transport child holding the memory repository. A signal the hub exits on goes through lifecycle stop (`STOP_SIGNALS` in `src/hub/lifecycle.ts`: SIGINT, SIGTERM, SIGHUP, SIGQUIT), never the default action; SIGQUIT in particular must not reach its default core dump (Bun honours it: agent-mem #435 measured a 5.9 GB core file and a delayed exit). The forced exit after the shutdown deadline stays `process.exit`.
|
|
114
|
+
- Memory sync (#438): stop an in-flight `syncMemory` only through `syncStop` (`src/hub/daemon.ts`), and await `syncRun` before the memory store closes, as `stopOnce` does. agent-mem runs the sync's git in its own process group, so a process that ends without `daemon.stop()` leaves that git and its transport child holding the memory repository. A signal the hub exits on goes through lifecycle stop (`STOP_SIGNALS` in `src/hub/lifecycle.ts`: SIGINT, SIGTERM, SIGHUP, SIGQUIT), never the default action; SIGQUIT in particular must not reach its default core dump (Bun honours it: agent-mem #435 measured a 5.9 GB core file and a delayed exit). The forced exit after the shutdown deadline stays `process.exit`. A sync result is logged as a failure only when `quietSyncError` is false: a missing remote in either agent-mem wording (`no sync remote`, `no generation sync remote configured`) and a stop are not failures (#401); a new agent-mem wording for them extends that one predicate.
|
|
@@ -21,3 +21,4 @@ Scope: the hub-native local worker, its tools, path guard, denylist and seatbelt
|
|
|
21
21
|
- Honor the live `protect_instruction_files` policy in scoped grants for the three instruction names; keep reads, explicit approval and never-ask unchanged. Only the settings registry writes this machine-local policy (#323).
|
|
22
22
|
|
|
23
23
|
- For sandboxed task checks, use the opt-in hub-owned bounded container runner; never widen Seatbelt, expose credentials, mount host paths or fall back to a host command. Keep spec/check arguments and results out of capture.
|
|
24
|
+
- The `bash` tool asks `ctx.permit` with the frozen input, then `ctx.shellExecutionCheck` right before the sandbox spawns (#816): `valid` runs, `ask` repeats the permit once with `{ forceAsk: true }`, `aborted` or a cancelled signal returns "turn cancelled before shell execution". `LocalWorker` binds the check to its turn generation, so a stale turn cannot spawn. A fake `ctx` in a test supplies `shellExecutionCheck` or accepts the `valid` default; never make it return `valid` for an aborted turn.
|
|
@@ -12,7 +12,7 @@ Scope: the task flow, assignment, the state machine and the hub's MCP tools. Rea
|
|
|
12
12
|
- A named owner that `assign()` refuses is never a silent no-op (#601): `assignTo` passes `refuseNamed` so `assignOwner` throws with the routing refusal reason before any write (pre-effect for tool callers, a plain `Error` for the console), and a proposal's unmet named owner is reported in the propose result through `namedOwnerNote`, read from the stall the proposal recorded. The task stays where it was; never deliver to an offline peer to make the name stick.
|
|
13
13
|
- Write-class owners (#735): `ownerRoleOfClass` (`src/hub/routing.ts`) is the one derivation of the role that owns a class (`plan` planner, `review` reviewer, everything else implementer); the role ceiling and the owner-admission gate both use it, so never test a fixed list of class names; `explain` and `assign` stay the same function.
|
|
14
14
|
- The state machine allows `in_progress -> approved` only for classes without a reviewer; `review()` checks `in_review` itself.
|
|
15
|
-
- Treat `cancelled` as closed through `isClosedTaskState`, but keep dependency release restricted to approval. Cancellation goes through `Tasks.cancel`, preserves the row and roles, clears its reservation/cohort, sends revision-bound stop notices and writes no outcome memory; never map it to approval or cancel a native turn or worktree. Planner cancellation stays limited to its own
|
|
15
|
+
- Treat `cancelled` as closed through `isClosedTaskState`, but keep dependency release restricted to approval. Cancellation goes through `Tasks.cancel`, preserves the row and roles, clears its reservation/cohort, sends revision-bound stop notices and writes no outcome memory; never map it to approval or cancel a native turn or worktree. Planner cancellation stays limited to its own proposed/backlog work with no execution evidence (`taskHasWorkEvidence`, the `hub_backlog_return_untouched` test, #812 AC5): an accepted-then-declined untouched task is cancellable, anything with a branch, commit, PR, local check, done, verdict or reopen is person-only and keeps the refusal text. The configured planner may `hub_task_show` any non-backlog task through the conductor's public view (`ConductorHooks.isPlanner`); a staged backlog row stays hidden from agent task tools (#263) and a peer holding both roles takes the audited conductor path. That read grants no hold or assignment.
|
|
16
16
|
- Capture `TaskOffer.pullOnly` and the exact offer envelope id at publication. Keep unread-offer attention independent of attachment and restart until acceptance, decline, supersession or `offerRead` evidence; never replace that evidence with a generic inbox timestamp or completion state.
|
|
17
17
|
- Use the daemon's settled role map (`resolveSettledRoles`, `src/hub/settled-roles.ts`, the same resolver reload compares holders with) for planner authority and notice recipients. An explicit planner designation is exclusive; automatic fallback remains runtime-only. Keep ready-unassigned sweep admission consistent with person-unassignment suppression, planner-pull reservation/WIP limits and ordinary routing guards; persist each ladder step before its notice.
|
|
18
18
|
- Readiness never puts a reserved owner over its planner WIP cap (#655). `offerReady` asks `reservedHold` (the same `wip()` plus `pendingClaims` against `wipLimit()` that `acceptanceCapacity` refuses at) before the `ready` write is offered; a hold records `ready` and one quiet `ready held` entry (in `QUIET_EVENTS`, never a stall, never an owner) and keeps the reservation. Offer dependents and held work through `readyOrder` (priority, rank, id), and free a slot through `releaseDependents` (approval), `releaseHeld` (cancel, untouched return) or the release timer; never offer a held task past the cap, and never let the idle sweep's auto-reassign override `reservedHold`. `offerReady` records its slot in `readyInFlight` in the same synchronous step as the check and frees it in `finally`, so an await in `assignOwner` never lets two offers share the last slot; `releaseReady` treats `isHeld` work as eligible even after a `planned` entry. Planner-pulled work and an excluded reservation are not held. The hold text carries peer ids and counts only.
|
|
@@ -62,14 +62,15 @@ Scope: the task flow, assignment, the state machine and the hub's MCP tools. Rea
|
|
|
62
62
|
- 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.
|
|
63
63
|
|
|
64
64
|
- `Tasks.review` captures `reviewAuthority` once and rechecks it in `valid()` after every await, right before each write; a headless reviewer's verdict, and its `hub_task_show` (checked before and after the read), holds only while the same child runs for that task at its bound envelope head and review revision (`Tasks.reviewBindingCurrent`, host-only: `in_review`, same reviewer, non-PII, matching `reviewRequestRevision` and review head). Never cache the answer (#223, #533).
|
|
65
|
-
- Keep structured review findings in verdict history, bound to the reviewed head. Validate bounded reports and person-only accepted dispositions before effects; recheck authority, privacy and head identity after awaits. A supplied `head` that differs from the task's resolved head (`findingHead`) is refused pre-effect, and the resolved head is part of the identity rechecked after the screen (#657). Key findings by `id` alone, never by pass (`findingsForHead`): a nit or open reclassification never discharges an Important, only an explicit disposition does. Omitted IDs never settle findings; carry open Important across heads until explicitly dispositioned, while nits stay head-local. Use the shared valid-head resolver for projections. Project text through publicView, and deduplicate recurring public pass/id pairs into backlog proposals through durable `Review finding key:` markers (a pair recurs only across separate work: tasks sharing a PR, a branch in one repository or a supersession count once, `workUnits`, #736) read only from hub-proposed `plan` cards (first history entry by `hub`), without editing instruction files.
|
|
65
|
+
- Keep structured review findings in verdict history, bound to the reviewed head. Validate bounded reports and person-only accepted dispositions before effects; recheck authority, privacy and head identity after awaits. A supplied `head` that differs from the task's resolved head (`findingHead`) is refused pre-effect, and the resolved head is part of the identity rechecked after the screen (#657). Key findings by `id` alone, never by pass (`findingsForHead`): a nit or open reclassification never discharges an Important, only an explicit disposition does. Omitted IDs never settle findings; carry open Important across heads until explicitly dispositioned, while nits stay head-local. Use the shared valid-head resolver for projections. Project text through publicView, and deduplicate recurring public pass/id pairs into backlog proposals through durable `Review finding key:` markers (a pair recurs only across separate work: tasks sharing a PR, a branch in one repository or a supersession count once, `workUnits`, #736; a task counts only where a review that is not withheld recorded the finding's id, whatever its pass, as open or accepted, never where it is only recorded fixed, #328) read only from hub-proposed `plan` cards (first history entry by `hub`), without editing instruction files.
|
|
66
66
|
- Plan conformance (#481 S2, `src/hub/plan-conformance.ts`): compare only literal repository-relative `plan.paths` entries (file or directory prefix, no glob) with complete PR file metadata; `completePlanInventory` requires a full head, `pathsComplete` and `typesComplete` (false on any rename or copy). Root, absolute-unverified or unsafe entries, a missing plan, a truncated inventory and a failed read are `unknown`, never covered; never read patches or the filesystem. An untouched planned path is advice; a changed path outside the plan is a departure, and `planConformanceBlockers` blocks enforce approval on `unknown` and on an unaccepted departure. Plan `proof` and `risks` (32 strings of 512 characters) cover no file. `Board` records `planRevision` only while `trackPlanRevisions` (mode not off), separate from backlog `planningRevision`.
|
|
67
67
|
- Departure consent is written only by `Tasks.acceptPlanDeparture` for the person (`ahub sdlc accept-departure <task> <head> <reason...>`; no peer tool) on a currently known departure at the current PR head, never on `unknown`, and never for a PII task. It binds repo, PR, head, plan revision and fingerprint, the inventory, `planScopeIdentity` and this host's `departureGeneration`, so a restart or a change to any of them invalidates it (`validPlanDepartures`). Status and merge-queue reads never write the board: they only note observations and invalidations in memory (`observedPlans`, `invalidatedPlanDepartures`), which `planObservation()` attaches to the next owner or reviewer transition write. Off captures no observation or revision; a plan or refs change still invalidates an existing acceptance.
|
|
68
68
|
- 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.
|
|
69
69
|
- 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.
|
|
70
70
|
- A definition's `routing.allowedRoles` is a fixed role ceiling (#543): `assign()` refuses a peer whose required role (reviewer for the reviewer role or the `review` class, planner for `plan`, otherwise implementer) is outside it, with code `policy`, and `loadConductorPolicy` drops project `roles` entries outside it with a config notice. `planner` also needs `projection.plannerEligible` (#525-1): `loadConductorPolicy` drops it from a kind's entry and `effectiveInstanceRoles` from an instance-id entry. Never widen either from project config.
|
|
71
71
|
- Reviewer pool (#519): `selectReviewerPool` is pure and called only inside `assign()`, which builds the candidates (declared reviewer-only `routing.reviewOnly` instances) and their blocked reasons; Tasks passes only `reviewPool` (`pool`, `rerotate`, `failed`, `required`). With no fresh reviewer declared and no configured `review.pool`, routing stays the legacy path. The seed is the linked PR number, else the task id; compute its modulo over configured kinds with declared candidates, including blocked/offline/paused/quota/failing candidates, and do not renumber by availability; append undeclared-kind trace entries (`no declared reviewer`) after the declared walk without assigning them modulo shares or changing a selected reason; the implementer's family is skipped while another can run, and a delta review keeps the last `changes_requested` family unless `rerotate`. `required` (an explicit `review.pool`) exhaustion goes to the person with `reviewBlocked`; only the optional default pool may fall back to an eligible planner, other family first. Record the choice through `reviewMetadata` (`reviewerFamily`, `reviewerReason`, including `same-family fallback`), never in note text alone. PII tasks, `review`-class tasks and a reviewer a person named (`reviewer changed`, reason `manual`) stay outside the pool.
|
|
72
|
-
- `
|
|
72
|
+
- `review.same_family = "never"` (#820) is read only inside `assign()` through `reviewPool.sameFamily`, which `Tasks.health()` and `reviewPoolOptions` pass from `d.review.same_family` (absent for the default `"fallback"`, so default routing stays byte-identical). It removes every same-family reviewer: the selector's last-resort loop (`selectReviewerPool` `sameFamily`), the legacy `automatic` fallback, a named same-family reviewer (refused with code `family`, `same family as <owner>; review.same_family is never`) and the planner escape unless the planner holds the reviewer role and is of another family. Nobody left means `reviewer = "user"` with `reviewBlocked`, never `sameFamilyFallback`, and that holds for any automatic exhaustion (all offline, no candidate, no declared instance), never `reviewer: none, done will approve directly`; the person fallback is keyed on the setting, not on an eligible same-family candidate. Keep it out of call sites: `Tasks` never tests the family itself.
|
|
73
|
+
- `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 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. A manually named reviewer's `no_verdict` and a no-verdict with no declared pool go only to the person; other manually named failures keep explicit person control. Confirm fallback only after accepted reviewer publication or a person offer and a fresh binding/generation check; failed publication or notification never confirms settlement. 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.
|
|
73
74
|
- 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"]`.
|
|
74
75
|
- Release authorization (#481 S6, `src/hub/sdlc-release-authorization.ts`): only the person's console `sdlc authorize-release` creates one, bound to the current HEAD commit, its committed package version, the origin identity and a fresh, error-free docs verdict, all reread after the docs check. It lives in daemon memory only: a restart revokes it and the event is audit, never restored. `release-guard` and `release-guard-hook` are refused in a private turn; a tag push needs a matching receipt, and npm publish also a clean tracked worktree and the same docs digest. Never accept authorization from a tool, a task receipt or a file.
|
|
75
76
|
- `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).
|
|
@@ -93,3 +94,8 @@ Scope: the task flow, assignment, the state machine and the hub's MCP tools. Rea
|
|
|
93
94
|
|
|
94
95
|
- Backlog issue planning (#759): allow only `fields.sdlc.repo`/`issue` on unstarted ownerless backlog through `Tasks.planBacklog`, scoped to the effective planner/person; a PII card's identity is person-only and a public one must pass `publicMetadataText`, both pre-effect. Keep arbitrary lifecycle receipts refused on backlog. Reuse lifecycle identity invalidation, keep the planning write atomic and recheck after awaits. Store identity-only undo values; never revive old spec acceptance. Omitted/empty planning fields with a bounded reason record a note-only revision and retain normal pins/frozen/stale guards.
|
|
95
96
|
- Grooming duplicate candidates (#759): suppress only pairs related by directed dependency reachability, either direction, using bounded public ID/dependency context. Include approved intermediate phases when available; a walk that hits its bound marks the digest truncated. Keep overlap findings, unrelated same-issue duplicates and privacy filtering; never infer follow-ups from prose or supersession text.
|
|
97
|
+
|
|
98
|
+
- Planner WIP breakdown (#784 AC3, `Board.plannerWipBreakdown`, `Tasks.wipBreakdown`): a read-only split of `plannerWip` into `active`, `reviewWait` and `reserved` for status, the console Peers detail and the dashboard legend. Read it from ONE grouped query for every peer (the status snapshot builds on a hot path); never loop `wip()` per peer or per task there. Admission, `reservedHold`, `acceptanceCapacity` and the planner pull keep reading `wip()` / `plannerWip` unchanged, and the three parts must add up to it: change the two queries together. The new `taskWip` fields are additive; renderers keep printing the plain count when an older hub sends none.
|
|
99
|
+
- Review request CI evidence (#824, `Tasks.hubVerifiedBlock`, `ReviewReadiness.checks`): `sendReview` renders one `Hub-verified at <head>` block from the `admission` it already carries and never makes a `gh` call of its own (the AC5 test pins the admission path to its three reads: a fourth call fails it). Its words come only from the hub's readiness read and recorded `check passed` events, never the builder's summary or a `hub_sdlc_record` claim; a PII task and a request with no admission get no block. A producer of `ReviewReadiness` (`readReviewReadiness`, `reviewReadinessFromEvidence`) must fill `checks` or the block names no checks. The checklist's `{check}` line drops "run the affected tests yourself" only for a ready admission with no `check passed` event; keep the #223 R2 withheld summary.
|
|
100
|
+
- Verdict mirror (#786 T3, `src/hub/sdlc-mirror.ts`, `Tasks.mirrorVerdict`): one `COMMENT` review per (task, head, verdict), called after the approved and changes_requested writes in `Tasks.review`, never awaited and never throwing (a failure is a number-only notice). The only review write is `gh api repos/<repo>/pulls/<pr>/reviews -f event=COMMENT -f commit_id=<head> -f body=<text>`, pinned by `assertMirrorArgv`: never `APPROVE`, `REQUEST_CHANGES`, another endpoint or a method flag, and keep the `test/sdlc-mirror.test.ts` argv test. Idempotency lives on GitHub (list the PR's reviews for the headline at that head), not on the task, so it adds no board or receipt field and survives a restart; an unknown read posts nothing. The note goes out only when public (`!isPii`, not withheld, not a private turn, `publicMetadataText`); a PII task mirrors the headline alone. `mirrorVerdict` reads the PR head and state again AFTER the review listing and judges on that last read with nothing awaited before the post (a head checked before an await is not trusted after it; the same invariant as the guarded merge's dispatch): a new read in that module means a new re-read after it and a fake-gh test that moves GitHub during it. An approval's mirror settles before `autoMerge` starts (`autoMerge(task, after)`), so keep that order. It follows `auto_merge` through `mergeModeFor`.
|
|
101
|
+
- Guarded merge (#786 T1, `src/hub/sdlc-merge.ts`, `Tasks.guardedMerge`): the hub's only merge method is `gh pr merge <pr> --repo <repo> --squash --match-head-commit <head>`, built by `mergeArgv` and checked by `assertMergeArgv` right before the call; never add `--admin`, `--auto` (until the repository allows it), another merge method or a ref update used to merge, and keep `test/sdlc-merge.test.ts` AC5 over the recorded argv. `decideMerge` names every failing gate and treats unknown evidence as a failure, and judges the non-head gates (checks including the `[repos]` table's, review requests, threads, draft, change request) on the first read AND a last read taken after the queue (`evidenceGates`), because `--match-head-commit` pins only the head, and compares every board-side input again in one synchronous dispatch segment (`dispatchGates` on `MergeInput.fresh`, then the `run` call, with no await between them: state, repo/PR/reviewed head, privacy, dependency edges and states, open Important findings, release stage, merge mode, required checks, authority and recovery/storage hold). The invariant: a gate checked before an await is never trusted after it, so a new input to the decision is a new field in `FreshBoard`, a new line in `dispatchGates` and a new entry in the `flips` table of `test/sdlc-merge.test.ts`; a new gate is a new refusal string plus a test line. The approval trigger (`autoMerge` after `releaseDependents` in `review()`) is fire-and-forget through `Promise.resolve().then(...).catch(...)`: it never awaits, never throws and never changes a verdict. Notices and results carry the task number and gates only (no `publicTitle`), and a PII task is refused before any read. Success is GitHub reporting `MERGED` on a readback, not the exit code. The control op (`sdlc` / `merge`) answers every request and admits the console user or the one configured conductor; post-run result recording and cleanup go through `Tasks`. After verified MERGED plus merge commit, the only permitted ref write is the same-repository/nondefault/unprotected PR head CAS deletion in #786 AC5; never delete a local worktree or branch.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Tests
|
|
2
2
|
|
|
3
|
-
Scope: tests, fakes and the test gate. Read before adding or changing a test or fake under `test/` or `src/adapters/conformance/`, or editing `scripts/check.sh`, `scripts/hang-watch.sh`, `scripts/seeded-check.ts`
|
|
3
|
+
Scope: tests, fakes and the test gate. Read before adding or changing a test or fake under `test/` or `src/adapters/conformance/`, or editing `scripts/check.sh`, `scripts/hang-watch.sh`, `scripts/seeded-check.ts`, `scripts/seeds.json`, `scripts/lib/ts-ast.ts`, `scripts/changed-tests.mjs`, `scripts/check-closing-refs.mjs` or `.github/workflows/closing-refs.yml`.
|
|
4
4
|
|
|
5
5
|
- A fake's gated action that never sees its gate skips the action; never fall through to a late, unsynchronized action. Owned-group tests inject `AcpOwnedGroupPorts`; a test never signals a process group it did not spawn.
|
|
6
6
|
- When extracting production closures into a VM fixture, bind every referenced host port and actual imported policy helper. Keep native identity checks on real unstarted instances; never replace a policy predicate with an always-true stub or weaken existing assertions to accommodate a moved dependency.
|
|
@@ -16,7 +16,9 @@ Scope: tests, fakes and the test gate. Read before adding or changing a test or
|
|
|
16
16
|
- A bounded polling wait must assert its condition after the last attempt, or document why giving up is intentional. Append-only event histories wait for a correlated closing event, not removal of the opening event. Before changing a slow wait, identify the unreached state and repair its cause (#296).
|
|
17
17
|
- A test of turn or notice behavior that is not about the snapshot budget yields an I/O turn (`await new Promise(r => setImmediate(r))`) between captures: captures in one synchronous burst share the 250 ms budget and may correctly record unknown evidence. A daemon test whose assertion needs a turn's snapshot or end evidence passes `snapshotClock: () => 0` (`DaemonOptions.snapshotClock`, #661) so a slow runner cannot spend that budget; never lengthen the budget instead. A wait on concurrent-edit evidence correlates the expected turn ids and paths (#617).
|
|
18
18
|
- A request that a test expects a stop or shutdown to cut gets its rejection handler where the request is created, never after the awaited teardown: a late `catch` races the reset and the run fails on an unhandled rejection.
|
|
19
|
-
- Run `bun scripts/seeded-check.ts` before reporting the seeded-guard gate complete. After changing seeded guard logic, run its affected seeds even when search-anchor counts are unchanged. Put asynchronous calls inside `resolves`/`rejects` expectations so a mutated refusal fails a named assertion. Keep the `scripts/seeds.json` cases sequential; require the same named test green before its seeded assertion fails. Seed rot, survivors, setup/compiler failures, timeout/watchdog termination and process leaks fail the gate. A change that moves or rewrites a line a `scripts/seeds.json` case searches for updates that case's search and replacement in the same PR (seed rot fails the gate), keeping its mutant effect and named test. Never run the full seed runner from a unit test or mutate the source checkout; inspect preserved failed fixtures before removing them. CI runs it as four sequential Linux shards (`--shard i/4`) beside the ordinary check, separately from `scripts/check.sh`; the required `seeded guards` job passes only when every shard passed, or when the decision job reused a verified identical-tree full gate (`admission == 'reuse'`). Keep that job name: branch protection and `scripts/ci-gates.mjs` read it (#457).
|
|
19
|
+
- Run `bun scripts/seeded-check.ts` before reporting the seeded-guard gate complete. After changing seeded guard logic, run its affected seeds even when search-anchor counts are unchanged. Put asynchronous calls inside `resolves`/`rejects` expectations so a mutated refusal fails a named assertion. Keep the `scripts/seeds.json` cases sequential; require the same named test green before its seeded assertion fails. Seed rot, survivors, setup/compiler failures, timeout/watchdog termination and process leaks fail the gate. A change that moves or rewrites a line a `scripts/seeds.json` case searches for updates that case's search and replacement in the same PR (seed rot fails the gate), keeping its mutant effect and named test. Never run the full seed runner from a unit test or mutate the source checkout; inspect preserved failed fixtures before removing them. CI runs it as four sequential Linux shards (`--shard i/4`) beside the ordinary check, separately from `scripts/check.sh`; the required `seeded guards` job passes only when every shard passed, or when the decision job reused a verified identical-tree full gate (`admission == 'reuse'`). Keep that job name: branch protection and `scripts/ci-gates.mjs` read it (#457). On seed rot the checker names the anchor's symbol at a base commit and where it is now (`seedRotContext`): `--base <commit>` sets that base (CI passes the PR base from a `fetch-depth: 0` checkout; keep both), and without it the base is `merge-base origin/main HEAD`. The context is evidence only: it never relaxes the one exact match, and an unavailable base says so instead of guessing.
|
|
20
|
+
- `scripts/lib/ts-ast.ts` is the one TypeScript AST helper for scripts and source-scanning tests (`parseSource`, `callSites`, `symbolSpans`, `enclosingSymbol`, `findSymbol`; inline HTML scripts keep their offsets). It gives lexical locations only, never resolved call edges, and stays dev tooling: never import it from `src/`.
|
|
21
|
+
- The native branch ratchet (`test/daemon-native-branches.json`) is reviewed data, never regenerated by a test (rule: `adapters.md`). `test/daemon-native-proof-gates.test.ts` runs daemon code cut out of `src/hub/daemon.ts` by its text, from ` function claudePermissionLaunch(` to ` async function changePermission(` to ` async function confirmPermissionDefault(`, and the ` const isSupportedPeer =` and ` const readOnlyPeer =` bindings: renaming, reordering or reindenting them updates the test in the same PR.
|
|
20
22
|
|
|
21
23
|
- `scripts/check.sh` pins the test environment (#610): `GIT_CONFIG_GLOBAL=/dev/null`, `NO_COLOR` unset, `TERM=xterm-256color`, temp root under the OS per-user temp dir, agent markers cleared. A test that needs a person's git config or color settings sets them itself. `--changed` selects tests with `scripts/changed-tests.mjs`: changed tests (nested suites included) plus every test that names a changed file directly; docs need none; any other changed file no test names, and any gate, package or shared-fixture change, runs everything.
|
|
22
24
|
- `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.
|
|
@@ -33,5 +35,6 @@ Scope: tests, fakes and the test gate. Read before adding or changing a test or
|
|
|
33
35
|
|
|
34
36
|
- Keep the CI admission lock around the whole controller workflow, not just its decision job, in its per-PR (`check-pr-<number>`, `queue: single`: a newer pending run replaces the older) or main (`check-main`, `queue: max`) group, never with `cancel-in-progress: true` beside `queue: max` (GitHub refuses it) (#609); two groups admitting one tree at once is an accepted duplicate run, never a reason to fail closed on another tree. 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. In `readFullGateAdmission`, lost successful proof (404, malformed proof, a per-read timeout) only means a fresh gate runs; unknown active coverage and the global read ceilings (`GateReadLimitError`) stay unknown and refuse. CI checks out the PR head (`pull_request.head.sha`), not a merge with main: after a fix lands on main, merge main into the branch before reading CI as proof. CI reuse tests use fixed metadata fixtures and never dispatch real duplicate workflows.
|
|
35
37
|
|
|
36
|
-
- Headless-reviewer tests run `test/fakes/fake-claude-print.ts` and headless Claude implementer tests run `test/fakes/fake-claude-implementer.ts` through `command` or a PATH stand-in (the implementer with `HOME` and `CLAUDE_CONFIG_DIR` in a temp dir), and fresh Codex reviewer tests run `test/fakes/fake-codex-reviewer.ts` through `codex_bin`; a test that reaches the daemon's reviewer or implementer start must never run the real `claude` or `codex`. The fake's sandbox markers test host admission only, never native isolation. Fresh Kimi reviewer tests set and restore `KIMI_CODE_HOME` to a fixture native home (the backend links that home's `credentials/`, `oauth/` and `device_id`, else the person's `~/.kimi-code`) and run `test/fakes/fake-kimi-reviewer.ts` through `command` or a PATH stand-in, never the real `kimi`. agy tests put a fake `agy` first on PATH and set `HOME` to a temp dir before any transcript readback (`readAgyTranscript` reads `$HOME/.gemini`). Code-facts tests build `new CodeFacts({ env })` with a PATH whose first `ripwire` is a wrapper around `test/fakes/fake-ripwire.ts` (`FAKE_RIPWIRE_MODE` selects its failures), or inject Tasks' `prepareReviewBrief`; never rely on an installed ripwire, which CI does not have.
|
|
38
|
+
- Headless-reviewer tests run `test/fakes/fake-claude-print.ts` and headless Claude implementer tests run `test/fakes/fake-claude-implementer.ts` through `command` or a PATH stand-in (the implementer with `HOME` and `CLAUDE_CONFIG_DIR` in a temp dir), and fresh Codex reviewer tests run `test/fakes/fake-codex-reviewer.ts` through `codex_bin`; a test that reaches the daemon's reviewer or implementer start must never run the real `claude` or `codex`. The fake's sandbox markers test host admission only, never native isolation. Fresh Kimi reviewer tests set and restore `KIMI_CODE_HOME` to a fixture native home (the backend links that home's `credentials/`, `oauth/` and `device_id`, else the person's `~/.kimi-code`) and run `test/fakes/fake-kimi-reviewer.ts` through `command` or a PATH stand-in, never the real `kimi`. Qwen TUI tests run `test/fakes/qwen-dual.ts` (scripted by submit text) as the launched Qwen or the daemon's `qwen_cmd` (an adapter unit test also passes its own `table`), never the real `qwen`. agy tests put a fake `agy` first on PATH and set `HOME` to a temp dir before any transcript readback (`readAgyTranscript` reads `$HOME/.gemini`). Code-facts tests build `new CodeFacts({ env })` with a PATH whose first `ripwire` is a wrapper around `test/fakes/fake-ripwire.ts` (`FAKE_RIPWIRE_MODE` selects its failures), or inject Tasks' `prepareReviewBrief`; never rely on an installed ripwire, which CI does not have.
|
|
37
39
|
- Production `MemoryClient` (`src/memory/client.ts`) is the in-process amem store and never opens `memory.worker_url` (#752). A test that needs the legacy worker shape injects `FakeMemoryClient` (`test/fakes/mem-worker.ts`) as `DaemonOptions.memoryClient` or in place of the client; never add an HTTP path to the production client for a test. An amem-backed test sets its own temp `AMEM_HOME` and reads only its own fixture memory directory.
|
|
40
|
+
- CLI print guards (#814, `test/cli-json-guard.test.ts`): two scans over every file in `src/cli`. One finds `console.log(JSON.stringify(...))` and passes only behind a `--json` branch (`selectsJson`), a named `DATA_FORMAT` or `NOT_YET_CONVERTED` entry. The other finds a print of a hub tool's text (`console.log(await taskOp(<op>))`, or a variable bound from one) and passes only behind `--json`, for a named `SENTENCE_OPS` op (checked against the daemon's case), or named debt (`TEXT_DEBT`, `CONDUCTOR_DEBT`, `REFUSAL_FALLBACK`). A list entry with no matching site fails, so the lists only shrink. Add a command's `--json` branch or renderer; never add a list entry to make a new print pass. Per-command output tests (`test/task-output.test.ts`, `test/sdlc-output.test.ts`) run the real CLI against a fake control server and assert default not JSON, `--json` byte equality, 80 and 200 columns and `--color=never`.
|
package/docs/dogfood.md
CHANGED
|
@@ -13,10 +13,17 @@ agent-hub is developed with agent-hub. This page lists every feature, whether it
|
|
|
13
13
|
| Message bus, digests, priorities | on | `ahub report`: messages, digests, overflow, undeliverable; delivery latency from events |
|
|
14
14
|
| Compact board (#724) | on | `ahub board` at 80, 120 and 200 columns: one line per task (test/board-compact.test.ts) |
|
|
15
15
|
| Backlog list table (#399) | on | `ahub backlog list` at a terminal width: one line per card by rank with the reserved owner and dependencies, and the count by owner (test/backlog-list-output.test.ts); `--json` for scripts |
|
|
16
|
+
| Native branch ratchet and fixed builtin proof gate (#625 slice 2a) | active in source/CI after merge; running daemon gate takes effect after release and upgrade | reviewed predicate/function/count/reason rows equal source; foreign native proof products and nativeRevert declarations refused; reserved ACP ports leg remains unverified until 2b |
|
|
17
|
+
| Remote isolated check transport (#793) | off here; fake checks and a bounded local Docker transport receipt, dependency preparation and person opt-in/SSH/live receipt follow separately | remote tests capability unavailable on failed SSH/Docker/image/transfer probe and while dependencies are unprepared; exact Git receipt, non-root hardened argv, zero Mac dependency copies and confirmed Docker cleanup before lease release; Darwin tar ignores PATH shims, probe warnings do not pollute JSON, bounded probes do not become unknown task-run orphans |
|
|
16
18
|
| Task board, routing, roles | on (claude planner, reviewer and implementer; codex, jcode, agy implementers) | board history: lead time from propose to approve, review rounds, escalations |
|
|
19
|
+
| Readable `sdlc` output by default (#814 T1) | on after the release: `ahub sdlc status\|explain\|queue\|release\|metrics\|costs\|authorize-release` print tables and labelled fields here; scripts pass `--json` | the person reading `ahub sdlc status` and `sdlc metrics` without `--json`; `test/cli-json-guard.test.ts` lists the CLI paths that still print JSON by default (target: only named data formats) |
|
|
20
|
+
| Readable restart, upgrade, recovery and model output by default (#814 T3) | on after the release: `ahub restart --dry-run`, `upgrade --dry-run`, `recovery status`, `models start`, `queue resolve` and `budget execution configure\|disable` print text here; scripts pass `--json` | the person reading a dry-run plan and a recovery status without `--json`; `test/cli-json-guard.test.ts` lists the CLI paths that still print JSON by default (target: only named data formats) |
|
|
21
|
+
| Readable task, backlog and release output by default (#814 T2) | on after the release: `ahub task show`, `backlog plan\|split\|groom` and `release list\|show` print blocks here; scripts pass `--json` | the person reading `ahub task show <id>` and `ahub release list` without `--json`; a reason that mentions `--json` keeps its words (`test/task-output.test.ts`) |
|
|
17
22
|
| Planner walk and reviewer independence (#525-3) | on after the release: `ahub route explain <id>` prints the planner walk here, and with `claude` the only planner holder it names `claude` for every task (rotation among one holder); the rotation among several holders and a named planner are not exercised here, task bindings are tracked separately below (525-2). Reviewer independence (I-2) is on: a peer that accepted or finished a task never reviews it | the `planner: claude (rotation)` line in `ahub route explain` for a task; I-2 shown as `reviewer user` with the trace line `every eligible reviewer implemented this task: the person reviews` on a task `claude` implemented (such as #399), and as `skipped, implemented this task` on the other candidates; a former implementer that is offline or paused is refused for that reason and leaves the outcome unchanged (the trace still ends `reviewer: none, done will approve directly` where no other reviewer exists) |
|
|
23
|
+
| Same-family reviewer ban, `review.same_family` (#820) | `never` in this repository's `config.local.json` after the release and a hub restart: a Codex-owned task is never assigned to `codex-review`; the planner (claude) reviews, else the person | board history: tasks whose reviewer family equals the owner's (target 0), `ahub route explain <id>` showing the rule, reviews that went to the person because no planner was eligible |
|
|
18
24
|
| Per-task and release planners, I-4 (#525-2) | off until this change is released and the running hub is upgraded; this PR enables no new project planner configuration | task/release planner bindings and selection reasons; wrong-planner pre-effect refusals; independent plan-review requests and `selfReview` receipts; queued notices rerouted after a planner change; `task_planners` recovery refusals |
|
|
19
25
|
| Acceptance criteria ids in review (#507b) | on after the release: a task with a recorded `criteria` checklist lists its ids in the review request and `hub_review` unmet items that start with an id are recorded as `unmetCriteria`; tasks here rarely record a checklist, so most reviews are unchanged | the `Acceptance criteria` block in a review request and `unmetCriteria` in `ahub task show` history for tasks with a checklist; the share of changes-requested verdicts whose unmet items name an id; unmet items that name an id the checklist does not hold (a typo) |
|
|
26
|
+
| Planner rulings in review (#835) | source/fixture coverage; active after release and hub upgrade | post-ownership planner notes in review requests and review-only views, 5 x 1000 bounds, withheld/PII exclusions, builder summary still withheld and departure accepted:false |
|
|
20
27
|
| Deliverable records (#509a) | on after the release: `deliverables` on `hub_task_done` and `hub_sdlc_record`, hashed by the hub in this repository's folder; most tasks here are code with a PR and name none, so only document-shaped work (a report, a spec draft) uses it | the `Deliverables` block in a review request and `deliverables` in `ahub task show`; approvals refused with `deliverable changed since it was delivered`; records with `state: unknown` (a file the hub could not vouch for); claimed-hash refusals as a measure of agents trying to supply a hash |
|
|
21
28
|
| `ahub deliverables` and the dashboard badge (#509b) | on after the release: `ahub deliverables [--check]` on this repository's board; only tasks that named deliverables appear, so it lists document-shaped work only | the person runs `ahub deliverables --check` after a document task is approved and the next time its file is edited: a `changed since review` row names the edit; `Deliverables n` chips on cards; `not measured now` in the sheet means nobody ran `--check` since the daemon started |
|
|
22
29
|
| Roles per kind and form (#525-1, #525-3, #525-4, #525-5) | on: claude holds the implementer role in the project checkout; isolated terminal and headless Claude implementer support is on, but neither form is declared here | tasks by owner; verified owner isolation and execution-root refusals; headless task-session resumes, inbox task-session refusals, fresh notices and person approval outcomes; missing or mismatched root must never admit a write owner |
|
|
@@ -25,7 +32,7 @@ agent-hub is developed with agent-hub. This page lists every feature, whether it
|
|
|
25
32
|
| Symbol preparation and historical recall (#684 phase 3 follow-ups) | off until release | exists calls per no-reviewer completion; raw-signature fail recall; indexed-definition/unknown labels; unchanged note/candidate bytes and neutral cleanup failures |
|
|
26
33
|
| Overlap notices | on; plan symbols match by normalized name (arguments stripped, a bare name matches the last segment, two qualified names must be equal; #682 phase 1b) | `ahub report`: overlap warnings, task pairs; false-overlap rate, and symbol-only matches on bare common names (#682) |
|
|
27
34
|
| Backlog phase grooming and issue planning (#759) | off until released and the running hub supports the new planning fields; relinks for cards #138/#287 remain a post-landing planner action | related-phase duplicate count; unrelated same-issue duplicate controls; planning revisions with `fields: ["sdlc"]` or `[]`; old spec acceptance absent after relink/undo |
|
|
28
|
-
| Planner WIP caps and backlog pull (#655, #718) | on (`tasks.wip_limits.codex = 2`); reserved-pull handoff on after release | board history/offer readback: reserved `backlog pulled` -> `assigned` -> `accepted` for the same peer; stale or unowned offers, capacity refusals and held-ready entries |
|
|
35
|
+
| Planner WIP caps and backlog pull (#655, #718) | on (`tasks.wip_limits.codex = 2`); reserved-pull handoff on after release; the active / review-wait / reserved split (#784) shows in `ahub status`, the console Peers detail and the dashboard legend after release | `ahub status` split per peer (a full cap held by review-wait shows as such); board history/offer readback: reserved `backlog pulled` -> `assigned` -> `accepted` for the same peer; stale or unowned offers, capacity refusals and held-ready entries |
|
|
29
36
|
| Task sweep (stalls, idle owners) | on | stall and sweep events per week |
|
|
30
37
|
| Turn-free coordination | off: experimental; needs a separate measured trial | coordination events, conflict notices |
|
|
31
38
|
| Pull-only peers (agy) | on; `hub_inbox` drains to 64 KiB (#713) | unread offers, empty-inbox polling (#681); queued count and oldest queued age per pull-only peer in `ahub status`; reads that said `N more waiting` |
|
|
@@ -49,9 +56,10 @@ agent-hub is developed with agent-hub. This page lists every feature, whether it
|
|
|
49
56
|
| Plan conformance (#481 S2) | advise | PRs with files outside the plan |
|
|
50
57
|
| Test integrity for bug fixes (#481 S4) | advise | bug PRs without a red-first receipt, modified pre-existing tests |
|
|
51
58
|
| Required checks | advise (`check (ubuntu-latest)`, `check (macos-latest)`, `seeded guards`) | first-pass CI rate, same-head reruns (`hub_sdlc_costs`) |
|
|
59
|
+
| Seed anchor diagnostics (#684) | on in the seeded checker; exact-match failure remains mandatory | missing anchors report the base commit, enclosing symbol and current file/line or explicit unknown; `test/seeded-check.test.ts` |
|
|
52
60
|
| Release authorization (#481 S6) | pending (#199) | releases with a receipt |
|
|
53
61
|
| SDLC metrics and control bands (#481 S7) | `ahub sdlc metrics` on; bands off until `[bands]` is set in `.agenthub/sdlc.toml`, and they write cards only in advise or enforce (in off the report still shows tiers). Completion checks are off here, so `first_pass_check` and the PR-based figures read unknown | its own report; breach episodes per week by tier, and band cards the planner accepted rather than dropped |
|
|
54
|
-
| Fresh headless reviewers (#223, #519) |
|
|
62
|
+
| Fresh headless reviewers (#223, #519) | configured here (`codex-review`); per-tool approval correction and no-verdict diagnostics/re-offer (#783) take effect after release and upgrade | reviews by fresh sessions, findings they add; a per-review start failure names a closed cause class (#722): `ahub status` reviewer line and hub.log `[cause: ...]`, with the share of failures that are `unknown`; stale admission skips and current-policy `no_verdict` reroutes leave the reviewer available (#781); a real `hub_review` verdict per verified backend/version, beyond inventory-only proof; bounded refusal diagnostics, confirmed re-offers without queue holds, and uncertain fallback holds |
|
|
55
63
|
| Completion checks per class | off: the implementer already runs `scripts/check.sh`; a hub-run copy would double the heavy-slot use | time to verdict when on |
|
|
56
64
|
|
|
57
65
|
## Memory and knowledge
|
|
@@ -59,7 +67,7 @@ agent-hub is developed with agent-hub. This page lists every feature, whether it
|
|
|
59
67
|
| Feature | State here | Measured by |
|
|
60
68
|
| --- | --- | --- |
|
|
61
69
|
| amem runtime integration (agent-mem#13 P4) | on after release; no claude-mem worker dependency | no-worker tripwire requests (must be 0), screened capture rows, profile refusals, all-author short-term recall, bounded-view truncation notices and curated-only ask citations |
|
|
62
|
-
| Shared memory through amem, briefs, recall preface | on | notes saved per week, brief hits, fails that prevented a retry |
|
|
70
|
+
| Shared memory through amem, briefs, recall preface | on; library intent briefs and plan guard (#436) take effect after release and hub upgrade; legacy index/timeline reads and worker notices retire in intent mode | notes saved per week, brief hits, fails that prevented a retry; same-input hub/library brief equality, host privacy refusals and front-of-brief plan fails despite the library byte cap or standalone screen |
|
|
63
71
|
| `ahub ask` | on | answers with cited ids |
|
|
64
72
|
| Code structure in the memory graph (#684) | pending | stale anchors, briefs with related notes |
|
|
65
73
|
|
|
@@ -72,6 +80,7 @@ agent-hub is developed with agent-hub. This page lists every feature, whether it
|
|
|
72
80
|
| Context gate | off: `context.gate = 0` | context-pressure events |
|
|
73
81
|
| Always-ask action classes (#636) | on after release for managed Claude forms, hub-owned ACP in ask mode, local/Pi tools and received Codex command approvals; Codex native bypass stays not enforceable | classed permission requests (a `gh api graphql` query is a read, a mutation, `gh run` or `gh workflow` write and every command with a shell expansion in a classed position (`git push $B`, `rm -rf "$DIR"`) is asked about: #756, #762; `test/action-class.test.ts` holds the table), full local/Pi command visibility and denied effects for incomplete native views; actionPolicyInfo current-child enforceability, remaining class budgets and weakening attention; Codex native no-request and Kimi non-ask ceilings are explicit |
|
|
74
82
|
| Permission modes | on, person-chosen | approval prompts and their wait times |
|
|
83
|
+
| Pi/local read-only shell class (#816 T1) | source/fixture coverage; person setting and live Pi receipt (AC4) remain unverified | automatic read-only class versus approval requests, parser/allowlist/proof revalidation tests, unchanged Seatbelt/path-guard byte checks |
|
|
75
84
|
| Pi and the local worker on DGX | off: DGX unreachable recently; Pi is off the roster | turns served on-prem, relay errors |
|
|
76
85
|
| PII routing | on | PII tasks and where they ran |
|
|
77
86
|
| Multiple Jcode instances (#685) | pending | turns per instance |
|
|
@@ -81,6 +90,13 @@ agent-hub is developed with agent-hub. This page lists every feature, whether it
|
|
|
81
90
|
| Feature | State here | Measured by |
|
|
82
91
|
| --- | --- | --- |
|
|
83
92
|
| Dogfood scorecard (#687) | on after release; read-only weekly receipt | `ahub report --dogfood --since 7d --json`; bounded evidence and explicit unknowns |
|
|
93
|
+
| Profile scorecard (#799) | source/fixture coverage; live writing/general/research pilot remains unverified | ahub report --dogfood profile rows; pure and reader dev byte goldens, private-text exclusion, research declarations/unknown verdicts and per-source unknown tests; anonymous weekly receipt templates |
|
|
94
|
+
| Agent-shell read-only commands (#812 T1) | source/fixture coverage; active after release | the kept Allowed forms succeed from an agent shell and the demoted forms are refused with the console line (identity tests plus the live table in the PR body) |
|
|
95
|
+
| Public agent CLI reads (#812 T1b) | source/fixture coverage; active after release | ordinary tools and agent-shell read success, private/secret projection tests, mutating-neighbour and planner-only refusals |
|
|
96
|
+
| Conductor operational CLI (#812 T2) | source/fixture coverage; active after release | peer-attributed agent_cli admissions, conductor/non-conductor command matrix, public projection and authority-revocation checks |
|
|
97
|
+
| Review request CI evidence (#824) | on after release: each review request carries one `Hub-verified at <head>` line from the admission it already read | review requests to a review-only reviewer; the count of reviews that still list test execution or CI evidence as unmet (should fall); no new gh reads |
|
|
98
|
+
| Verdict mirror (#786 T3) | `auto_merge = "dry-run"` first (same setting as the guarded merge): the dry-run notice names the one comment review it would post; `on` after one dry-run notice matches a verdict | one `agent-hub verdict:` comment review per (task, head, verdict) on each PR; none at a stale head; the headline alone for a private task |
|
|
99
|
+
| Guarded merge and result recording (#786 T1/T2) | `auto_merge = "dry-run"` first in this repository's `.agenthub/sdlc.toml`; `on` after one dry-run report matches a manual merge | `ahub sdlc merge <task> --dry-run` reports the decision and the argv it would run; merges the hub made against merges a person or reviewer made by hand; host merge/cleanup records and owner-notice status; fake-gh ordering/idempotency proof, no live cleanup claim |
|
|
84
100
|
| Console and dashboard | on; #719 pending-history reads and bounded native launch retries take effect after release | control-request latency, connection timeouts and launch retry notices; daemon CPU/RSS samples, measured separately from lifetime CPU |
|
|
85
101
|
| Usage view (#683) | CLI (`ahub usage`, U2), console Usage tab `7` (U3) and dashboard Usage view (U4) shipped | its own panels |
|
|
86
102
|
| Kimi usage from its wire log (#285 phase 2) | on after the release when `budget.kimi_tokens_5h` is set; this repository sets it | `tokens` and `usage` (`kimi_wire`) events per Kimi turn, the `tokens` window in `ahub budget`, and the count of `kimi usage unknown` notices; the live check against Kimi's `/usage` (AC11) stays opens |
|
|
@@ -88,7 +104,8 @@ agent-hub is developed with agent-hub. This page lists every feature, whether it
|
|
|
88
104
|
| Settings reload | on | reloads applied, pending restarts |
|
|
89
105
|
| Config editing from the console and dashboard (#689) | on after release; `ahub settings edit` and console key `c` run here, the dashboard Structure group needs `ahub ui --settings`; executable keys stay console only | `config_edit` events (edits applied by tier and source), refusals shown with the loader reason, restarts left pending |
|
|
90
106
|
| Upgrade and recovery | on, per release | upgrade receipts in `docs/verification/` |
|
|
91
|
-
| Agent CLI version policy and trust (#691) | on after the release: Codex 0.163.0 verified by the [2026-10-22 receipt](verification/2026-10-22-codex-reviewer-isolation.md) and 0.164.0 by the [2026-10-24 receipt](verification/2026-10-24-codex-0164-reviewer-isolation.md); Kimi 2.3.0 and Qwen 0.26.0 verified by the [model-free receipt](verification/2026-10-22-kimi-qwen-compat.md), with account/provider live legs unverified; agy 1.4.4 in-line | model-free isolation receipts, `ahub agents` verdicts, versions trusted per machine, lead time from an `agent-compat` issue to a receipt |
|
|
107
|
+
| Agent CLI version policy and trust (#691) | on after the release: Codex 0.163.0 verified by the [2026-10-22 receipt](verification/2026-10-22-codex-reviewer-isolation.md) and 0.164.0 by the [2026-10-24 receipt](verification/2026-10-24-codex-0164-reviewer-isolation.md); Kimi 2.3.0 and Qwen 0.26.0 verified by the [model-free receipt](verification/2026-10-22-kimi-qwen-compat.md), Kimi 2.3.1 by the [2026-10-25 model-free receipt](verification/2026-10-25-kimi-231-reviewer-compat.md), with account/provider live legs unverified; agy 1.4.4 in-line | model-free isolation receipts, `ahub agents` verdicts, versions trusted per machine, lead time from an `agent-compat` issue to a receipt |
|
|
108
|
+
| Qwen's own TUI over dual output (#286 Part 3) | off here: this repository's hub runs no Qwen peer (roster.qwen is off), and the limits (a hub deny ends the turn without an answer; a stalled turn is stopped only with Esc in the Qwen terminal) are for the person at that terminal to accept. Verified on Qwen 0.26.1 under an isolated HOME by the [2026-10-25 receipt](verification/2026-10-25-qwen-dual-output-t0.md); the live account leg is the person's | `test/qwen-dual.test.ts` against a fake dual-output Qwen; in real use `ahub status` stalled/detached notices per peer and the `needs_review` count of Qwen deliveries (an Esc or an exited terminal ends a turn that way; a deny completes) |
|
|
92
109
|
| Benchmarks (#251, #637) | off: `bench.enabled = false`; routing gate pending | bench receipts |
|
|
93
110
|
|
|
94
111
|
## Decisions
|
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.8.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. Claude-mem is an optional, explicit import source only; runtime recall and capture make no worker HTTP requests.
|
|
4
4
|
|
|
5
5
|
## Portable core and data directory
|
|
6
6
|
|