@staix/agent-hub 0.17.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 +16 -0
- package/README.md +2 -2
- package/docs/agent-notes/README.md +5 -0
- package/docs/agent-notes/adapters.md +2 -0
- package/docs/agent-notes/benchmarks.md +1 -0
- package/docs/agent-notes/daemon.md +1 -0
- package/docs/agent-notes/local-worker.md +1 -0
- package/docs/agent-notes/tasks.md +7 -1
- package/docs/agent-notes/tests.md +1 -0
- package/docs/dogfood.md +15 -1
- package/docs/memory.md +1 -1
- package/docs/operations.md +105 -16
- package/docs/profiles.md +36 -0
- package/docs/quickstart.md +9 -3
- package/docs/sdlc.md +29 -3
- package/docs/security.md +26 -4
- package/docs/specs/2026-09-19-agent-hub-design.md +16 -6
- package/docs/verification/2026-10-25-0.18.0.md +76 -0
- package/docs/verification/2026-10-25-codex-review-live-verdict.md +35 -0
- package/docs/verification/2026-10-25-remote-docker-transport.md +46 -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 +24 -18
- package/package.json +2 -2
- package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-hub/server.js +94 -33
- package/src/adapters/definitions/builtin-metadata.ts +1 -1
- package/src/adapters/local-worker.ts +5 -3
- 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/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 +353 -72
- 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/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 +241 -16
- package/src/local/tools.ts +17 -4
- package/src/ui/index.html +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,22 @@ 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
|
+
|
|
7
23
|
## 0.17.0
|
|
8
24
|
|
|
9
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).
|
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.
|
|
@@ -25,6 +25,8 @@ Scope: the Codex, ACP and Pi adapters and the Pi extension (turn `generation` an
|
|
|
25
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.
|
|
26
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`).
|
|
27
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`.
|
|
28
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.
|
|
29
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.
|
|
30
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`.
|
|
@@ -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`.
|
|
@@ -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).
|
|
@@ -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.
|
|
@@ -69,6 +69,7 @@ Scope: the task flow, assignment, the state machine and the hub's MCP tools. Rea
|
|
|
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
|
+
- `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.
|
|
72
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.
|
|
@@ -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.
|
|
@@ -37,3 +37,4 @@ Scope: tests, fakes and the test gate. Read before adding or changing a test or
|
|
|
37
37
|
|
|
38
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.
|
|
39
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
|
@@ -14,10 +14,16 @@ agent-hub is developed with agent-hub. This page lists every feature, whether it
|
|
|
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
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 |
|
|
17
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`) |
|
|
18
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 |
|
|
19
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 |
|
|
20
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 |
|
|
21
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 |
|
|
22
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 |
|
|
23
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 |
|
|
@@ -26,7 +32,7 @@ agent-hub is developed with agent-hub. This page lists every feature, whether it
|
|
|
26
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 |
|
|
27
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) |
|
|
28
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 |
|
|
29
|
-
| 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 |
|
|
30
36
|
| Task sweep (stalls, idle owners) | on | stall and sweep events per week |
|
|
31
37
|
| Turn-free coordination | off: experimental; needs a separate measured trial | coordination events, conflict notices |
|
|
32
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` |
|
|
@@ -74,6 +80,7 @@ agent-hub is developed with agent-hub. This page lists every feature, whether it
|
|
|
74
80
|
| Context gate | off: `context.gate = 0` | context-pressure events |
|
|
75
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 |
|
|
76
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 |
|
|
77
84
|
| Pi and the local worker on DGX | off: DGX unreachable recently; Pi is off the roster | turns served on-prem, relay errors |
|
|
78
85
|
| PII routing | on | PII tasks and where they ran |
|
|
79
86
|
| Multiple Jcode instances (#685) | pending | turns per instance |
|
|
@@ -83,6 +90,13 @@ agent-hub is developed with agent-hub. This page lists every feature, whether it
|
|
|
83
90
|
| Feature | State here | Measured by |
|
|
84
91
|
| --- | --- | --- |
|
|
85
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 |
|
|
86
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 |
|
|
87
101
|
| Usage view (#683) | CLI (`ahub usage`, U2), console Usage tab `7` (U3) and dashboard Usage view (U4) shipped | its own panels |
|
|
88
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 |
|
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
|
|
package/docs/operations.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Operations guide
|
|
2
2
|
|
|
3
|
-
This guide describes ahub 0.
|
|
3
|
+
This guide describes ahub 0.18.0 and control protocol 16. Live verification
|
|
4
4
|
results and remaining prerequisites are recorded separately in [the smoke ledger](smoke.md).
|
|
5
5
|
|
|
6
6
|
## Agent-operated live verification
|
|
@@ -67,6 +67,9 @@ smoke ledger; the presence of this option proves no outstanding #273 live leg.
|
|
|
67
67
|
`status --all` uses the projects table. `queue show`, `models status` and `budget execution status` show labelled fields; add `--json` for the raw fetched document.
|
|
68
68
|
`report --by task` keeps the attribution and coverage sentences with section headings and aligned counters.
|
|
69
69
|
Use `--json` for scripts; it returns the fetched data without color, and `status --json` keeps its existing document.
|
|
70
|
+
`sdlc status`, `sdlc explain`, `sdlc queue`, `sdlc release`, `sdlc metrics`, `sdlc costs` and `sdlc authorize-release` print readable output by default (#814: aligned columns, wrapped text, readable times, `--color=auto|always|never`). A script that parsed their JSON must pass `--json`, which prints exactly the document they printed before. `sdlc export` is a data format and stays JSON, as do `export`, `research export` and `bench export`.
|
|
71
|
+
`restart` and `upgrade` off an interactive terminal (a pipe, `--dry-run`), `recovery status` and the results of `recovery reattach`, `resume`, `abort` and `dispose`, `models setup` and `start`, `queue resolve`, and `budget execution configure` and `disable` print readable text by default (#814); a script that parsed their JSON must pass `--json`, which prints exactly the document they printed before, including `restart --json --yes`. A readable plan lists each project, each peer with how it runs and what happens to it, every blocker, every reconnect-only session and every fresh start, and says nothing was changed; `--json` keeps those as stderr lines. A pipe wraps at `COLUMNS` when it is set, else at 80 columns.
|
|
72
|
+
`task show`, `task cancel`, `backlog groom`, `backlog return|plan|pin|split|undo|pull-ready` and the `release` commands (`list`, `show`, `new`, `plan`, `activate|freeze|ship|drop`) print a readable block by default (#814): the task or release as the hub holds it, a table for lists, every identifier whole, long text wrapped to the terminal. A script that parsed their JSON must pass `--json`, which prints the hub tool's text exactly as before. A private task arrives from the hub as its stub and is shown as that stub. Because these commands also take free text, `--json` and `--color=` count as flags only at the start or the end of the arguments: `--json` in the middle of a reason is part of the reason, and a value flag's value (`task cancel --reason --json`) is never a flag. A reason that ends in a bare `--json` loses it to the flag. `backlog add|pull`, `task escalate|assign|unassign` already answered one sentence and are unchanged.
|
|
70
73
|
In `projects --json` and `status --all --json`, an unavailable hub that explicitly refuses an inspection for SQLite contention also carries `failure: { "code": "database_busy" }`; it is additive metadata alongside the neutral `error` sentence.
|
|
71
74
|
Text columns are a human interface, not a scripting contract. An identifier accepted as a command argument is whole wherever its available column can hold its own length:
|
|
72
75
|
turn ids, queue ids, project ids, execution budget ids and ids in labelled views. Only status's informational settlement and hold ids are shortened;
|
|
@@ -136,6 +139,12 @@ asking, under the same path guard and sandbox; a Pi restart or replacement, or a
|
|
|
136
139
|
hub restart, drops it. The hub log records each granted call by tool name only.
|
|
137
140
|
The dashboard can only deny Pi's requests.
|
|
138
141
|
|
|
142
|
+
In `ask-when-needed`, Pi and the local worker also grant bounded read-only shell calls at the host permit boundary (#816). The command must parse as one literal command and satisfy a closed program/flag table, trusted executable lookup and the existing root/path guard. Unknown flags, operators, expansions, pipelines, output files, unsafe helper/follow/file-list options and outside-root paths keep the ordinary approval path. Single quotes preserve literal data; double quotes containing `$`, backticks or backslashes are not automatically granted.
|
|
143
|
+
|
|
144
|
+
The fixed program set is `ls`, `cat`, `head`, `tail`, `wc`, `rg`, `grep`, `stat`, `file`, `tree`, `pwd`, `find` and supported read-only `git` forms. Git patch forms need both `--no-ext-diff` and `--no-textconv` where supported; a configured helper-bearing key (`core.fsmonitor`, `log.showSignature`, `gpg.program`, `gpg.<format>.program`, `diff.external`, `diff.<driver>.command` or `diff.<driver>.textconv`, `format.pretty` or `pretty.<name>`) outside the hub's own command scope keeps every git form asking. Custom `--format` and `--pretty` options also ask because signature placeholders can invoke the default GPG program. The host revalidates executable and path proof after each await, immediately before execution. A changed proof requires an explicit approval, including in unattended mode; persistent instability runs nothing. Neither argv nor environment is rewritten. Scoped read-only grants log the tool and class without command arguments.
|
|
145
|
+
|
|
146
|
+
`ask` retains shell approval behavior; `never-ask`, existing Pi tool-wide grants and the always-ask action policy retain their precedence. Seatbelt, the path guard, read allowlists, home and network access are unchanged. The person's Pi setting and live triage receipt are separate AC4 work; this feature's tests do not certify that rollout.
|
|
147
|
+
|
|
139
148
|
Tab toggles stream and panels; `ahub console --panels` starts in panels. A panel
|
|
140
149
|
screen has a header (the project's directory name, then the tabs), the panel and
|
|
141
150
|
a three-line footer, separated by ASCII `-` rules; the stream has one rule above
|
|
@@ -518,6 +527,41 @@ resume, budget, queue, permit, permission, ask, remember, route, turns, undo,
|
|
|
518
527
|
check-path and report operations. It executes an argument vector with closed stdin. Lifecycle,
|
|
519
528
|
launch, nested console, setup, UI, logs and tail commands are refused.
|
|
520
529
|
|
|
530
|
+
## Authority by command
|
|
531
|
+
|
|
532
|
+
`classifyPeerCommand` (src/cli/identity.ts) is the gate for an `ahub` command
|
|
533
|
+
run from an agent shell. It gives every command one of three classes:
|
|
534
|
+
**Allowed** means any agent may run it; **Conductor** means only the
|
|
535
|
+
configured conductor peer may (actor-preserving and audited); **Console** means
|
|
536
|
+
only the person's own terminal. A command the gate does not name is Console,
|
|
537
|
+
and so is every help entry the gate has no class for yet;
|
|
538
|
+
`test/identity.test.ts` pins each classed form below.
|
|
539
|
+
|
|
540
|
+
### Allowed (any agent)
|
|
541
|
+
|
|
542
|
+
| Command | Class | Why |
|
|
543
|
+
| --- | --- | --- |
|
|
544
|
+
| `ahub doctor [--json] [--full] [--color=...]` | Allowed | a read-only check of tools, daemon, plugin, gateway, models and memory |
|
|
545
|
+
| `ahub doctor --stale-anchors [--json]` | Allowed | a bounded read-only report of memory symbol anchors at current HEAD |
|
|
546
|
+
| `ahub queue list [--peer <id>] [--json] [--full] [--color=...]` | Allowed | public queue metadata only; held bodies and resolution remain conductor operations |
|
|
547
|
+
| `ahub roster [--json] [--color=...]` | Allowed | public roster status; add/remove retain conductor admission |
|
|
548
|
+
| `ahub settings [list] [--json]`, `ahub settings get <key> [--json]` | Allowed | public registry values; secret-locating values, paths, undo records and config-editor details are withheld |
|
|
549
|
+
| `ahub sdlc status\|explain [<task>] [--json] [--color=...]` | Allowed | public lifecycle status; private tasks stay stubs, without private evidence or receipts |
|
|
550
|
+
| `ahub sdlc queue <owner/repo> [--json] [--color=...]`, `ahub sdlc release <owner/repo> [live-issue...] [--json] [--color=...]` | Allowed | public delivery/release readback for the checkout's GitHub origin, explicit SDLC config repos or person-imported public external records; no release authorization or mutation |
|
|
551
|
+
| `ahub release show <name>` | Allowed | public release metadata; private scope and internal history remain withheld |
|
|
552
|
+
| `ahub projects [--json] [--color=...]` | Allowed | lists registered projects; `projects remove` stays Console |
|
|
553
|
+
| `ahub models status [--json] [--color=...]` | Allowed | reads local model state; `setup\|start\|stop` stay Console |
|
|
554
|
+
| `ahub recovery status [<op-id>] [--json]` | Allowed | reads a preflight operation; the bare interactive screen, `resume`, `abort` and `reattach` stay Console |
|
|
555
|
+
| `ahub upgrade ... --dry-run` (no `--yes`) | Allowed | reviews the upgrade plan and applies nothing; `--yes` stays Console |
|
|
556
|
+
| `ahub restart --dry-run [--json]` (no `--yes`) | Allowed | reviews the restart plan and applies nothing; `--yes` stays Console |
|
|
557
|
+
|
|
558
|
+
`backlog list`, `release list` and `sdlc costs|metrics` retain their existing
|
|
559
|
+
planner/person policy. Their CLI forms stay Console for ordinary agents; a
|
|
560
|
+
configured planner can use the existing planner tools without borrowing console
|
|
561
|
+
authority. All newly admitted reads use the ordinary tools connection and public
|
|
562
|
+
views, never console identity. The person's full readback stays unchanged.
|
|
563
|
+
Conductor operations are listed in [the operational command table](#conductor-operations-812-t2).
|
|
564
|
+
|
|
521
565
|
## Conducting a team from Claude Code or Codex
|
|
522
566
|
|
|
523
567
|
Start the daemon with `ahub up --no-console`, set exactly one conductor in the
|
|
@@ -623,13 +667,17 @@ does not automatically hand its task to a cloud peer.
|
|
|
623
667
|
For assigned, non-private work, `hub_task_spec` reads the named GitHub issues
|
|
624
668
|
through the hub's authenticated, bounded read path. `hub_check_start` runs an
|
|
625
669
|
exact Git ref's permitted tests or typecheck in the opt-in isolated container
|
|
626
|
-
runner (`checks.runner: "container"`); `hub_check_result` and `hub_check_cancel`
|
|
670
|
+
runner (`checks.runner: "container"`, or the optional `"remote"` backend); `hub_check_result` and `hub_check_cancel`
|
|
627
671
|
read or cancel that owned run. Missing spec/check capability refuses the
|
|
628
672
|
assignment early; it never grants host commands, credentials or broader sandbox
|
|
629
673
|
access. Receipts distinguish exact source/tree/lockfile, platform, exit and
|
|
630
674
|
uncertain or cancelled outcomes. See [bounded spec and check access](security.md#bounded-task-specs-and-isolated-checks-418)
|
|
631
675
|
for the authorization and resource bounds.
|
|
632
676
|
|
|
677
|
+
Remote runner configuration (#793) is permitted only in the person's untracked `config.local.json`: select `checks.runner = "remote"` and supply `checks.remote.host`, `checks.remote.image` (immutable SHA-256 image reference) and `checks.remote.root` (reserved absolute metadata for the later dependency-preparation slice). No remote destination belongs in tracked project files. The runner uses the person's host SSH capability (including the hub's authentication agent socket) in BatchMode, with TTY allocation, agent/X11 forwarding, configured forwardings and local SSH commands disabled, and fixed Docker argv; it never installs host tools, mounts host paths or falls back to this Mac. A failed SSH, Docker, image or hardened network-none/transfer probe marks sandboxed test capability unavailable with a generic remote-runner reason. A successful platform line names only Linux architecture and numeric Docker version.
|
|
678
|
+
|
|
679
|
+
The remote source is the recorded Git commit, streamed into a hardened non-root container. Mac dependencies are excluded; the project-scoped read-only named dependency volume is currently an unprepared placeholder, so the runner reports `remote dependencies unprepared` and withholds tests capability until the separate admitted preparation slice lands, even when its transport probe succeeds. That future step must key the volume by lockfile and Bun version; the run itself performs no install. Remote `root` is validated but unused in this transport-only slice because no host path operation is needed. The bounded [local Docker transport receipt](verification/2026-10-25-remote-docker-transport.md) records the extraction and cleanup leg. Actual remote-host opt-in, SSH and pi's live check receipt remain unverified follow-up work. Existing local Apple-container behavior remains available by explicit selection.
|
|
680
|
+
|
|
633
681
|
Retention bounds inactive valid check receipts to 200 records and the newest 20
|
|
634
682
|
full outputs, excluding active runs. Full output is also consumed by the owner
|
|
635
683
|
result read or expires after 24 hours.
|
|
@@ -1028,7 +1076,9 @@ includes), read the check result, and list what is
|
|
|
1028
1076
|
unmet (`hub_review` takes `unmet`, `ahub review ... --unmet <item>`). A reviewer
|
|
1029
1077
|
that missed the request, and the owner, read the done summary and check result
|
|
1030
1078
|
with `hub_task_show {id}`: the task's public view with its history, as the
|
|
1031
|
-
conductor sees it, and only a stub for a PII task.
|
|
1079
|
+
conductor sees it, and only a stub for a PII task. The configured planner reads any
|
|
1080
|
+
task outside the backlog the same way (#812); a staged backlog row stays hidden from agent
|
|
1081
|
+
task tools and is read through the planner tools. Other peers are refused. Each
|
|
1032
1082
|
request also carries the hub's review brief for the task's commit: reviewable and
|
|
1033
1083
|
excluded files with reasons, the `REVIEW.md` and `ahub-review-rules` rules read at
|
|
1034
1084
|
the merge base, and a cross-check list; `ahub review brief <id>` prints it.
|
|
@@ -1304,7 +1354,7 @@ ahub memory forget 1a2b3c4d # a person's; also promote, demote, sup
|
|
|
1304
1354
|
|
|
1305
1355
|
`"memory": { "native": false }` in `.agenthub/config.json` turns it off for a project.
|
|
1306
1356
|
|
|
1307
|
-
The exact-pinned `@staix/agent-mem` 0.
|
|
1357
|
+
The exact-pinned `@staix/agent-mem` 0.8.0 resolves `AMEM_HOME`, then `$AGENTHUB_HOME/memory`, then `~/.agenthub/memory`. An absent `M/profile.json` selects shared v1; an explicit generation selects `M/generations/G` with the package's readonly legacy bridge. All daemon memory consumers and the hub memory/goal CLI use that selected profile. Invalid metadata or a missing selected generation makes native memory unavailable while the bus and board continue; there is no fallback to legacy writes. Inspect the daemon's memory binding/availability before interpreting missing memory as an empty store.
|
|
1308
1358
|
|
|
1309
1359
|
The daemon checks its selector at hub operation entry and before candidate/counter writes. It does not hot-switch handles or provide an atomic fence against selection changes during an in-flight core call. Quiesce consumers and close their handles before migration or selection changes; reopen through the supported session-preserving lifecycle afterward. Candidate/counter storage remains `M/candidates.db`, independent of `G`; since agent-mem 0.7.0 `amem review` also lists and accepts candidates on a selected generation. Every running hub using this home needs an authenticated receipt: a matching ready binding, or a generation-capable unbound receipt (`disabled` when native memory is off, `unavailable` when opening failed) proving it holds no memory handle. An unbound daemon and zero running hubs are quiesced for memory; a stale bound handle, unreadable registry or unknown runtime is not ready. Existing JSON memory/goal results remain unchanged; the additive binding receipt belongs to daemon status, profile-check and the decay object. This source integration does not authorize installing a new runtime, migrating real memory, restarting sessions or claiming real-machine sync/capture receipts. Migration/deactivation and their limits belong to [agent-mem](https://github.com/STAIxBWLB/agent-mem); see [the hub profile contract](memory.md#selected-memory-profile).
|
|
1310
1360
|
|
|
@@ -1654,7 +1704,7 @@ denylist (Pi and local), or native vendor boundaries (Claude and Codex).
|
|
|
1654
1704
|
| Kimi Code CLI | ACP yolo | ACP auto | ACP default |
|
|
1655
1705
|
| Codex | on-request for every outgoing turn/start | never for every outgoing turn/start | Restore the known native policy once, then stop overriding |
|
|
1656
1706
|
| Claude | Allow resolving project file tools outside .agenthub/.git/.claude/.codex/.qwen/.kimi/.kimi-code/.jcode/.pi/.mcp.json/.agents/.agent/_agents/_agent/.gemini/.grok/.claude.json | Hook allow for every tool | No hook decision |
|
|
1657
|
-
| Pi and local | Grant write/edit outside native agent configuration paths
|
|
1707
|
+
| Pi and local | Grant write/edit outside native agent configuration paths and proven read-only shell calls; other shell/git writes ask | Grant each tool once within the existing sandbox/guard | Normal approval handling |
|
|
1658
1708
|
|
|
1659
1709
|
Kimi's map applies only to the actual Kimi Code CLI identity, not another ACP
|
|
1660
1710
|
agent configured in kimi_cmd. Qwen and other ACP agents start fresh/resumed
|
|
@@ -1923,7 +1973,7 @@ Instances, the review and approval blocks and the lifecycle can be edited withou
|
|
|
1923
1973
|
1. **Registry settings** (everything above) are unchanged.
|
|
1924
1974
|
2. **Structural keys**, editable in the console and in a dashboard settings session:
|
|
1925
1975
|
- `instances.<id>` with `kind`, `roles`, `start_mode`, `review_mode`, `readOnly`, `worktree` and `permission_mode`, in `config.local.json`;
|
|
1926
|
-
- `review.adaptive`, `review.min_reviews`, `review.pool`, `review.rerotate`, `snapshots.keep`, `approvals.timeout_s` and `context.gate`, in `config.local.json`;
|
|
1976
|
+
- `review.adaptive`, `review.min_reviews`, `review.pool`, `review.rerotate`, `review.same_family`, `snapshots.keep`, `approvals.timeout_s` and `context.gate`, in `config.local.json`;
|
|
1927
1977
|
- `mode` and `required_checks` in `.agenthub/sdlc.toml`. Only those two lines change: comments and every other table stay as written, and a layout the editor cannot change faithfully (a key repeated, or one inside a multi-line string) is refused with its own reason.
|
|
1928
1978
|
|
|
1929
1979
|
`roster.*`, `tasks.wip_limits.*`, `permission_modes`, `peers` and the rest of the registry keep their own `ahub settings set` path.
|
|
@@ -2492,7 +2542,7 @@ section is what they run.
|
|
|
2492
2542
|
|
|
2493
2543
|
Upgrade running projects with the target release's own coordinator. It accepts
|
|
2494
2544
|
a running source on control protocol 9 (0.6.x), 10 (0.7.0 through 0.12.0),
|
|
2495
|
-
11 (0.12.1 and 0.12.2), 12 (0.12.3), 13 (0.12.4 through 0.12.15), 14 (0.12.16), 15 (0.12.17 through 0.12.19) or 16 (0.12.20 through 0.
|
|
2545
|
+
11 (0.12.1 and 0.12.2), 12 (0.12.3), 13 (0.12.4 through 0.12.15), 14 (0.12.16), 15 (0.12.17 through 0.12.19) or 16 (0.12.20 through 0.18.0), and only
|
|
2496
2546
|
a target on its own protocol, so the target's coordinator fits every supported
|
|
2497
2547
|
source and carries every recovery fix released up to it. Protocol 8 and older
|
|
2498
2548
|
(0.5.x and earlier) are refused as `manual-bootstrap-required`. Run from the
|
|
@@ -2506,7 +2556,7 @@ bunx --package @staix/agent-hub@0.12.43 ahub upgrade --to 0.12.43 --yes
|
|
|
2506
2556
|
| Running now | Coordinator to use |
|
|
2507
2557
|
| --- | --- |
|
|
2508
2558
|
| 0.6.x (protocol 9) | the target's, through `bunx` as above |
|
|
2509
|
-
| 0.7.0 through 0.12.0 (protocol 10), 0.12.1 and 0.12.2 (protocol 11), 0.12.3 (protocol 12), 0.12.4 through 0.12.15 (protocol 13), 0.12.16 (protocol 14), 0.12.17 through 0.12.19 (protocol 15), 0.12.20 through 0.
|
|
2559
|
+
| 0.7.0 through 0.12.0 (protocol 10), 0.12.1 and 0.12.2 (protocol 11), 0.12.3 (protocol 12), 0.12.4 through 0.12.15 (protocol 13), 0.12.16 (protocol 14), 0.12.17 through 0.12.19 (protocol 15), 0.12.20 through 0.18.0 (protocol 16) | the target's, through `bunx` as above |
|
|
2510
2560
|
| any supported source, with the installed CLI already at the target | `ahub upgrade` below, which is the same coordinator |
|
|
2511
2561
|
| 0.5.x or earlier (protocol 8 and older) | not supported: bootstrap by hand with the matching CLI |
|
|
2512
2562
|
|
|
@@ -2655,15 +2705,14 @@ plugin, daemon, or terminal mutation.
|
|
|
2655
2705
|
|
|
2656
2706
|
`ahub upgrade` and `ahub restart` are interactive when standard input and output
|
|
2657
2707
|
are a terminal and neither `--dry-run` nor `--yes` is given. With either flag, or
|
|
2658
|
-
through a pipe, the JSON plan
|
|
2659
|
-
unchanged. The screens add no way to change an operation: every choice runs one
|
|
2708
|
+
through a pipe, the plan is printed as readable text (#814) and `--json` keeps the JSON plan and the `ahub: blocker:` lines; the exit codes are unchanged. The screens add no way to change an operation: every choice runs one
|
|
2660
2709
|
of the commands in this section for you, so that command's refusals decide.
|
|
2661
2710
|
|
|
2662
2711
|
The screens use the console palette and `--color=auto|always|never`, with the
|
|
2663
2712
|
same `NO_COLOR` and `TERM=dumb` precedence. Auto colours a suitable TTY; explicit
|
|
2664
2713
|
always has the console's override behaviour. Raw JSON bypasses colour and width
|
|
2665
|
-
handling. Default and never
|
|
2666
|
-
dispose
|
|
2714
|
+
handling. Default and never print the same readable pipe, dry-run, status and
|
|
2715
|
+
dispose text, and `--json` keeps the previous documents byte for byte. Presentation flags go after the command, for
|
|
2667
2716
|
example `ahub recovery --color=never` or
|
|
2668
2717
|
`ahub recovery status <operation-id> --json`. An explicit `--json` returns only
|
|
2669
2718
|
the raw document, without a human suffix. `upgrade --json` reviews a JSON plan;
|
|
@@ -3271,7 +3320,7 @@ The person can retain superseded work as closed history:
|
|
|
3271
3320
|
ahub task cancel <id> --reason <text> [--superseded-by <ref>] [--revision N]
|
|
3272
3321
|
```
|
|
3273
3322
|
|
|
3274
|
-
The console also offers cancellation with the selected task's flow revision. Approved tasks cannot be cancelled. The planner's `hub_task_cancel` tool is limited to
|
|
3323
|
+
The console also offers cancellation with the selected task's flow revision. Approved tasks cannot be cancelled. The planner's `hub_task_cancel` tool is limited to work it proposed that is in backlog or proposed with no execution evidence: never started, or accepted by an owner and then declined or returned untouched (no branch, commit, PR, local check, done, verdict or reopen, the same test as `hub_backlog_return_untouched`, #812). Work with any such evidence requires the person. Cancellation sends the former owner/reviewer a stop notice, releases overlap/reservation participation and keeps dependents blocked with a cancelled-dependency notice. It does not stop a native turn, delete a worktree or write an outcome memory note. Cancelled work is closed history, counted separately from approved work; it does not satisfy an approval dependency. A still-running private turn keeps its privacy fence after cancellation. Use the displayed revision to avoid acting on a task that changed while it was selected.
|
|
3275
3324
|
|
|
3276
3325
|
The daemon advertises `task_cancel`; a current CLI refuses cancellation against older daemons. Recovery retains verified target features beside its immutable root/digest, reuses them on resume and on each lease renewal before a terminal is closed, and checks only sources not yet past commit; a legacy receipt without that evidence needs a target probe only before commit. Upgrade/restart refuses a target without this feature while cancelled rows exist and names their ids. No cancelled row is rewritten as approved. An old binary launched by hand remains unsupported: it can count cancelled work as open or fail to move it. Use a compatible coordinator instead. Live task cancellation and sweep enablement are separate post-release operations.
|
|
3277
3326
|
|
|
@@ -3952,7 +4001,12 @@ Capacity defaults to one per peer. Each declared instance has its own `tasks.wip
|
|
|
3952
4001
|
uses the existing machine-local registry path; integers 0..32 apply live (the next
|
|
3953
4002
|
admission reads the current value), with 0 disabling planner admission for that
|
|
3954
4003
|
peer. Status and the agent legend count
|
|
3955
|
-
owned unfinished work plus unowned planner-pull reservations
|
|
4004
|
+
owned unfinished work plus unowned planner-pull reservations, and show how that
|
|
4005
|
+
count splits (#784): `ahub status` prints `<peer> planner WIP 3/3 (active 2, review-wait 1, reserved 0)`,
|
|
4006
|
+
the console Peers detail (Enter) has a `planner WIP` line, and the dashboard legend shows the same split.
|
|
4007
|
+
`active` is owned work that is not waiting for review, `review-wait` is owned work in review (it still
|
|
4008
|
+
holds its slot), `reserved` is a planner-pull reservation nobody owns yet; the three add up to the count
|
|
4009
|
+
admission reads, and the split changes no admission decision. An older hub shows the plain count. The same limit gates
|
|
3956
4010
|
acceptance and self-claim in every lifecycle mode, so 0 refuses new acceptances; a
|
|
3957
4011
|
current owner's redo of sent-back work keeps its slot (#481 S9).
|
|
3958
4012
|
|
|
@@ -4209,6 +4263,15 @@ restart the hub. For example, a reviewer can use:
|
|
|
4209
4263
|
Start them with `ahub codex --as codex-review` and
|
|
4210
4264
|
`ahub kimi --as kimi-review`. Codex keeps its native TUI and its own hub-owned
|
|
4211
4265
|
app-server proxy; extra Kimi, Qwen, Jcode, Pi and local instances use headless starts.
|
|
4266
|
+
|
|
4267
|
+
An extra instance of any kind that holds the `implementer` role needs its own
|
|
4268
|
+
registered worktree on an attached branch, at exactly `.worktrees/<id>`
|
|
4269
|
+
(`"worktree": ".worktrees/<id>"`). Configuration validation refuses a missing one
|
|
4270
|
+
only for Claude channel instances and headless writers. For other kinds, Codex and
|
|
4271
|
+
Jcode included, the hub accepts the declaration at startup and then refuses the start
|
|
4272
|
+
with `an extra implementer requires an explicit managed worktree`. Create and
|
|
4273
|
+
register the worktree before the start; the hub never creates it.
|
|
4274
|
+
|
|
4212
4275
|
Extra Jcode instances are supported (#685), in `.agenthub/config.local.json` only:
|
|
4213
4276
|
|
|
4214
4277
|
```json
|
|
@@ -4308,10 +4371,12 @@ From 0.12.37 reviews can rotate across fresh headless Claude and Codex reviewers
|
|
|
4308
4371
|
"codex-review": { "kind": "codex", "roles": ["reviewer"], "start_mode": "headless", "review_mode": "fresh" },
|
|
4309
4372
|
"kimi-review": { "kind": "kimi", "roles": ["reviewer"], "start_mode": "headless", "review_mode": "fresh" }
|
|
4310
4373
|
},
|
|
4311
|
-
"review": { "pool": ["claude", "codex", "kimi"], "rerotate": false }
|
|
4374
|
+
"review": { "pool": ["claude", "codex", "kimi"], "rerotate": false, "same_family": "fallback" }
|
|
4312
4375
|
}
|
|
4313
4376
|
```
|
|
4314
4377
|
|
|
4378
|
+
`review.same_family` (#820) is `"fallback"` (the default) or `"never"`. With `"fallback"` a reviewer of the owner's family is chosen only when no other family is eligible, and the board records a same-family fallback. With `"never"` routing never selects one, in the pool and in the legacy path: the planner reviews when it holds the reviewer role, is not the owner, is of another family and is not blocked, otherwise the person does (`reviewer = "user"`, recorded as blocked), including when every reviewer is offline: an approval without review never happens under `"never"`. A manual `ahub task assign <id> <peer> --role reviewer` of a same-family peer is refused with `same family as <owner>; review.same_family is never`. `ahub route explain <id>` names the rule in its trace. Use `"never"` when the workspace review gate needs the reviewer to be a different AI than the implementer; a Jcode running a Claude model is still family `jcode`.
|
|
4379
|
+
|
|
4315
4380
|
Start them with `ahub claude --as claude-review`, `ahub codex --as codex-review` and `ahub kimi --as kimi-review`; stop with `ahub stop <id>`. For the Claude and Codex reviewers the command asks the running hub to start the reviewer and returns: nothing runs in that terminal, it takes no launch arguments, and `--print-command` or `--dry-run` prints a hub-start note instead of a launch (#720). Fresh Claude and Codex variants share the project checkout; the Kimi variant runs in a clean temporary working directory and reviews only the screened brief and host facts. All fresh variants refuse a custom worktree or permission mode. Codex and Kimi require `review_mode: "fresh"`; without it, an existing headless instance with the reviewer role keeps its native app-server/ACP behavior, worktree and permission options and does not join this pool. Claude retains its #223 automatic headless-reviewer declaration. The agy reviewer slot remains automatically unavailable (`agy is an implementer peer (#485)`). A failed or unsupported fresh start never downgrades to a native continuing session. Ordinary implementer instances retain their own adapters. The default logical pool order is Claude, Codex, Kimi. Only configured kinds with at least one declared reviewer count as slots, including declared ones that are offline, paused, quota-limited or failing, so availability never renumbers them; configured kinds with no declaration are listed after the walk as `no declared reviewer`. `review.rerotate` defaults to false. Pool selection starts at PR number modulo the number of declared kind slots, or task id when there is no PR, then skips the implementer's family and unavailable candidates. Delta review prefers the preceding eligible independent family in a fresh child. A preceding same-family fallback does not outrank a newly available independent family. `ahub route explain` shows the same pure walk.
|
|
4316
4381
|
|
|
4317
4382
|
Codex requires a verified `codex-cli` (0.146.0, 0.163.0 or 0.164.0) or one the person trusts on this machine, and a passing bounded runtime receipt before its model child starts. 0.146.0 and 0.163.0 passed the [2026-10-22 model-free production admission](verification/2026-10-22-codex-reviewer-isolation.md) and 0.164.0 the [2026-10-24 one](verification/2026-10-24-codex-0164-reviewer-isolation.md). A refusal names the installed and verified versions. Either set `codex_bin` to a verified executable, or run `ahub agents trust codex-review <version>`: trust lifts only the version pin, and every proof below still runs before each child (#691). Each launch approves exactly `hub_review` and `hub_task_show` per tool (`approval_mode = "approve"`), never through a server-wide default: without it Codex 0.164.0 listed the tools but refused the call, so reviews ended with no verdict (#783). A model-free receipt proves the inventory, not that a verdict reaches the hub; a [model-backed 0.164.0 run](verification/2026-10-25-codex-0164-reviewer-tool-call.md) recorded one verdict through that path. The producer checks native binary/config stability, configured MCP inventory with only the two hub tools, effective Apps/plugins/hooks disablement, an explicit empty legacy `notify` command and a named filesystem profile. Apps needs separate feature readback because configured inventory omits runtime connectors. Web search is requested disabled; its effective turn mode is not read back and managed requirements can select Cached (see the verification ceiling). Each launch proves public-read, exact/glob/symlink private-denial, standard host credential denial and write-denial in temporary fixtures. Standard credential and configured tool-home/XDG subtrees are denied independently of project `local.deny`. Its sandboxed shell is filesystem read-only with private paths denied and network configured disabled; it is not Claude's Read/Grep/Glob-only tool whitelist. No credential home is copied.
|
|
@@ -4397,7 +4462,7 @@ Native launcher connections retry transport timeouts/unclean closes with bounded
|
|
|
4397
4462
|
|
|
4398
4463
|
## amem runtime (agent-mem#13 P4)
|
|
4399
4464
|
|
|
4400
|
-
The hub exact-pins @staix/agent-mem 0.
|
|
4465
|
+
The hub exact-pins @staix/agent-mem 0.8.0 for recall, curated evidence and local deterministic capture. No runtime call reaches a claude-mem worker; `memory.worker_url` is ignored. The person's `ahub memory import claude-mem` remains explicit and off by default. Existing profile, alias, PII and private-turn fences still apply. `ask --remember` and context checkpoints propose machine-local candidates, never automatic curated evidence. See [memory integration](memory.md#in-process-recall-and-capture-agent-mem13-p4) for changed behavior and bounded-view ceilings.
|
|
4401
4466
|
|
|
4402
4467
|
## Backlog issue identity and notes
|
|
4403
4468
|
|
|
@@ -4426,3 +4491,27 @@ In the dashboard Memory view (#684 phase 4), select a symbol and enable Code nei
|
|
|
4426
4491
|
`hub_memory_graph` includes the same bounded derived context for symbol starts within its existing 6 KB output budget. Task briefs prioritize attached fails and decisions within `brief_items`; the console related-note count refers to that bounded selection. Code facts remain optional: no index is copied into memory, no graph edges are persisted and no PII task/turn starts a query.
|
|
4427
4492
|
|
|
4428
4493
|
Optional code work runs only while Memory is open and visible. A cold console count reports unknown immediately; it does not prepare code facts. Direct task memory remains available when optional code facts are ineligible, interrupted or unavailable. Review/completion queries pre-empt optional native work after its owned process/tree cleanup. Unknown lookups have a five-second retry floor; topology caches are commit-bound and note bodies are refreshed under current policy. These are advisory coverage bounds, not empty-result proofs.
|
|
4494
|
+
|
|
4495
|
+
## Authority by command
|
|
4496
|
+
|
|
4497
|
+
### Conductor operations (#812 T2)
|
|
4498
|
+
|
|
4499
|
+
The selected project's configured conductor may run the commands below through its ordinary peer identity. Other peers retain the guidance "the person runs it in ahub console or a terminal". A daemon round trip is permitted, but an unreachable hub refuses promptly rather than waiting for the ordinary connection timeout. Refused operations are audited as refused. The daemon checks the authenticated connection, current role and project membership before effects and after asynchronous boundaries. Roster handoffs carry that same authority check through plan admission, owner delivery and each later task; a saved assignment is never rolled back when delivery is interrupted. It records an `agent_cli` admission with the initiating peer before proceeding; audit failure refuses the operation. Admission records are not a unique invocation counter.
|
|
4500
|
+
|
|
4501
|
+
| Command | Conductor scope |
|
|
4502
|
+
| --- | --- |
|
|
4503
|
+
| roster add/remove | Existing roster lifecycle and confirmations; no role or permission-mode grant |
|
|
4504
|
+
| stop peer | Existing hub-owned peer stop, ownership and recovery guards |
|
|
4505
|
+
| queue show/resolve/discard | Public queue projection and existing revision/confirmation guards |
|
|
4506
|
+
| task cancel | Existing hub_task_cancel with the real peer; eligibility follows the task-board policy |
|
|
4507
|
+
| doctor --orphans --kill | Selected project's positively identified hub-owned process only; no cross-project registrations or unsigned process files |
|
|
4508
|
+
| logs / export | Bounded daemon projections, with unsupported/private records withheld; logs -f ends when authority ends |
|
|
4509
|
+
| settings set | Only the twelve existing live registry keys listed below |
|
|
4510
|
+
|
|
4511
|
+
The exact settings keys are `task_sweep.enabled`, `task_sweep.auto_reassign`, `task_sweep.no_writes_turns`, `task_sweep.interval_s`, `task_sweep.unaccepted_min`, `task_sweep.idle_min`, `task_sweep.review_min`, `task_sweep.ready_min`, `task_sweep.ladder_min`, `snapshots.enabled`, `research.enabled` and `approvals.notify`. New keys receive no authority by prefix. Generic roster setting writes still use the dedicated roster lifecycle. Conductor setting records name the initiating peer, and conductor writes preserve the person's pending undo record. An intervening write can still invalidate that undo through the existing file-digest guard; preserving the record does not bypass that guard. Permission modes, roles, `pet.window`, arbitrary configuration reload and undo remain person commands.
|
|
4512
|
+
|
|
4513
|
+
Log history is limited to the last 100 retained lines and export to the last 1000 retained events, each within a 128 KiB read. Truncation is explicit. Export accepts closed metadata shapes; unknown fields and nested records are withheld rather than copied. Task titles, paths and private delivery bodies are not part of this projection. It does not change the person's ordinary logs/export output.
|
|
4514
|
+
|
|
4515
|
+
Orphan cleanup requires a missing selected root, signed process identity and the host-known hub executable/entrypoint and argv. An inaccessible root or unavailable proof remains unknown; there is no offline config-based role grant. The daemon rechecks authority and process ownership before signals. A confirmed self candidate uses the existing lifecycle stop after its reply. A roster write already committed before authority ends is acknowledged as saved, without authorizing further lifecycle work or commands.
|
|
4516
|
+
|
|
4517
|
+
`restart --yes` and `recovery resume/abort` await a project-scoped continuation proof. `task unassign` follows #481, and legacy models start/stop awaits a project that needs it. This slice adds no task eligibility rule and no MLX/recovery-driver changes. Person authority and the separately authorized `--live-test` path retain their existing behavior.
|