@staix/agent-hub 0.20.0 → 0.21.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 +59 -3
- package/README.md +2 -2
- package/docs/agent-notes/README.md +11 -0
- package/docs/agent-notes/adapters.md +7 -3
- package/docs/agent-notes/benchmarks.md +27 -0
- package/docs/agent-notes/budget.md +2 -1
- package/docs/agent-notes/bus.md +2 -2
- package/docs/agent-notes/daemon.md +15 -6
- package/docs/agent-notes/local-worker.md +4 -0
- package/docs/agent-notes/models.md +2 -0
- package/docs/agent-notes/tasks.md +17 -9
- package/docs/agent-notes/tests.md +9 -2
- package/docs/bench.md +1 -1
- package/docs/data-policy.md +2 -0
- package/docs/dogfood.md +37 -4
- package/docs/events.md +2 -1
- package/docs/memory.md +9 -3
- package/docs/operations.md +565 -62
- package/docs/profiles.md +13 -1
- package/docs/quickstart.md +14 -9
- package/docs/research.md +7 -4
- package/docs/sdlc.md +54 -10
- package/docs/security.md +396 -34
- package/docs/smoke.md +127 -0
- package/docs/specs/2026-09-19-agent-hub-design.md +1070 -22
- package/docs/verification/2026-10-27-0.19.0.md +5 -5
- package/docs/verification/2026-10-28-claude-2.1.311-2.1.312-bash-permission.json +635 -0
- package/docs/verification/2026-10-28-claude-2.1.311-2.1.312-permission-transport.json +403 -0
- package/docs/verification/2026-10-28-claude-implementer-permission-triage.md +123 -0
- package/docs/verification/2026-10-28-jcode-0.99.1-usage.md +41 -0
- package/docs/verification/2026-10-28-realpath-name-audit.md +222 -0
- package/docs/verification/2026-10-28-routing-f0bb8ad0.md +1000 -0
- package/docs/verification/2026-10-29-0.21.0.md +61 -0
- package/docs/verification/2026-10-29-model-effort-capabilities.md +123 -0
- package/docs/verification/2026-10-29-modelrouting-b152ddb9.md +953 -0
- package/docs/verification/claude-implementer-permission-triage.ts +160 -0
- package/docs/verification/claude-permission-transport-native.py +71 -14
- package/docs/verification/profiles/YYYY-MM-DD-research.md +1 -1
- package/docs/verified.json +15 -15
- package/package.json +2 -2
- package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-hub/server.js +1016 -280
- package/src/adapters/acp.ts +79 -3
- package/src/adapters/channel-peer.ts +4 -0
- package/src/adapters/claude-channel.ts +13 -6
- package/src/adapters/claude-implementer.ts +60 -7
- package/src/adapters/codex-appserver.ts +77 -4
- package/src/adapters/conformance/fakes/jcode-agent.ts +6 -2
- package/src/adapters/conformance/kit.ts +3 -9
- package/src/adapters/definitions/builtin-metadata.ts +3 -114
- package/src/adapters/definitions/native-config-defaults.ts +1 -1
- package/src/adapters/definitions/native-config-policy.ts +3 -2
- package/src/adapters/definitions/native-context.ts +12 -0
- package/src/adapters/external-metadata.ts +48 -0
- package/src/adapters/external-modules.ts +63 -3
- package/src/adapters/jcode.ts +6 -2
- package/src/adapters/local-worker.ts +40 -25
- package/src/adapters/native-proof-ports.ts +10 -0
- package/src/adapters/registry-types.ts +4 -0
- package/src/broker/protocol.ts +203 -0
- package/src/broker/server.ts +314 -0
- package/src/broker/store.ts +548 -0
- package/src/cli/bench.ts +3 -2
- package/src/cli/broker.ts +58 -0
- package/src/cli/conductor-recovery.ts +142 -0
- package/src/cli/configured-acp-entry.ts +26 -6
- package/src/cli/console-state.ts +59 -17
- package/src/cli/console.ts +17 -2
- package/src/cli/facts-hook.ts +3 -1
- package/src/cli/help.ts +28 -16
- package/src/cli/identity.ts +23 -10
- package/src/cli/init.ts +34 -7
- package/src/cli/intake-output.ts +33 -0
- package/src/cli/main.ts +314 -71
- package/src/cli/output.ts +41 -1
- package/src/cli/recovery-package.ts +18 -1
- package/src/cli/role-holders.ts +35 -0
- package/src/cli/room.ts +304 -0
- package/src/cli/say-input.ts +16 -0
- package/src/cli/schedule-output.ts +49 -0
- package/src/cli/sdlc-output.ts +44 -6
- package/src/cli/smoke-pty-bridge.ts +33 -0
- package/src/cli/smoke-pty.ts +145 -0
- package/src/cli/smoke-run.ts +139 -0
- package/src/cli/smoke-target.ts +130 -0
- package/src/cli/source-output.ts +29 -0
- package/src/cli/task-output.ts +3 -2
- package/src/cli/upgrade-runtime.ts +98 -16
- package/src/cli/upgrade.ts +66 -7
- package/src/hub/ask.ts +76 -12
- package/src/hub/board.ts +132 -22
- package/src/hub/bus.ts +399 -56
- package/src/hub/child-process.ts +2 -0
- package/src/hub/ci-watch.ts +42 -20
- package/src/hub/claude-failure-stage.ts +85 -0
- package/src/hub/claude-shell-wrapper.ts +11 -5
- package/src/hub/conductor.ts +17 -4
- package/src/hub/config-apply.ts +32 -5
- package/src/hub/config-reload.ts +7 -5
- package/src/hub/config-trust.ts +7 -1
- package/src/hub/context-window.ts +33 -3
- package/src/hub/control-client.ts +14 -2
- package/src/hub/corpus-config.ts +34 -0
- package/src/hub/corpus-evidence.ts +184 -0
- package/src/hub/corpus.ts +373 -0
- package/src/hub/cost-governance.ts +26 -0
- package/src/hub/daemon.ts +1150 -171
- package/src/hub/deliverable-files.ts +8 -2
- package/src/hub/delivery-journal.ts +123 -17
- package/src/hub/dogfood-profile.ts +32 -5
- package/src/hub/dogfood-read.ts +6 -2
- package/src/hub/envelope.ts +11 -0
- package/src/hub/eval-gate.ts +131 -0
- package/src/hub/events.ts +1 -1
- package/src/hub/external-instance.ts +69 -0
- package/src/hub/facts.ts +15 -5
- package/src/hub/folder-trigger.ts +223 -0
- package/src/hub/hub-tools.ts +2 -1
- package/src/hub/inference.ts +20 -5
- package/src/hub/instance-config.ts +69 -3
- package/src/hub/instance-execution.ts +6 -2
- package/src/hub/instances.ts +32 -4
- package/src/hub/intake.ts +125 -0
- package/src/hub/learning.ts +85 -0
- package/src/hub/lifecycle.ts +7 -3
- package/src/hub/limits.ts +12 -5
- package/src/hub/native-permission-request.ts +77 -0
- package/src/hub/operational-authority.ts +63 -5
- package/src/hub/operational-settings.ts +76 -0
- package/src/hub/operational-view.ts +1 -1
- package/src/hub/packs.ts +132 -0
- package/src/hub/path-policy.ts +7 -0
- package/src/hub/peer-doing.ts +130 -0
- package/src/hub/peers.ts +12 -0
- package/src/hub/queue-backpressure.ts +58 -0
- package/src/hub/read-tools.ts +2 -0
- package/src/hub/recovery-authority.ts +151 -0
- package/src/hub/recovery-policy.ts +220 -0
- package/src/hub/research.ts +24 -14
- package/src/hub/restart.ts +7 -0
- package/src/hub/routing.ts +54 -11
- package/src/hub/schedules.ts +266 -0
- package/src/hub/sdlc-merge.ts +38 -14
- package/src/hub/sdlc-metrics.ts +32 -10
- package/src/hub/sdlc-queue.ts +7 -1
- package/src/hub/sdlc-release.ts +1 -1
- package/src/hub/sdlc.ts +37 -7
- package/src/hub/security-baseline.ts +38 -0
- package/src/hub/settings.ts +6 -1
- package/src/hub/shipped-packs.ts +8 -0
- package/src/hub/snapshots.ts +72 -6
- package/src/hub/source-read.ts +160 -0
- package/src/hub/sources.ts +311 -0
- package/src/hub/tasks.ts +910 -110
- package/src/hub/workflow.ts +1 -1
- package/src/local/doc-read.ts +69 -0
- package/src/local/proxy.ts +4 -13
- package/src/local/tools.ts +37 -3
- package/src/local/web-fetch-config.ts +43 -0
- package/src/local/web-fetch.ts +183 -0
- package/src/memory/brief.ts +85 -13
- package/src/memory/capture.ts +1 -1
- package/src/rooms/application.ts +114 -0
- package/src/rooms/boundary.ts +103 -0
- package/src/rooms/commands.ts +294 -0
- package/src/rooms/crypto.ts +371 -0
- package/src/rooms/link.ts +584 -0
- package/src/rooms/policy.ts +82 -0
- package/src/rooms/remote-peer.ts +80 -0
- package/src/rooms/service.ts +426 -0
- package/src/rooms/status.ts +146 -0
- package/src/rooms/store.ts +1210 -0
- package/src/rooms/wire.ts +465 -0
- package/src/ui/index.html +91 -4
- package/templates/packs/example.pack.json +13 -0
|
@@ -5,9 +5,11 @@ Scope: the model relay and its journal, the hub's own inference and its slots, O
|
|
|
5
5
|
- The model relay journals per-request identity (`RelayRequestRecord`): a request's served model comes only from its own gateway header, its own generation SSE event (#137 heartbeat classification), or the locally validated MLX configuration. A backend's mutable last-served label is never a request's evidence, HTTP 200 plus the requested alias identifies nothing, and a request cancelled before identification stays explicitly unidentified (`identified: false`, `outcome: "cancelled"`). Journal records carry no prompts, tools, keys or Access headers.
|
|
6
6
|
- Inference is optional and fail-open: it returns its input or `undefined` on any failure and backs off, so a delivery never waits on a dead model twice. Its output is only ever capped text or a value checked against a closed list.
|
|
7
7
|
- The PII screen (`screenPii`, #198) is the exception: it fails closed. Every failure (timeout, no or off-campus model, an answer outside `clear` / `pii <category>`, text over `SCREEN_MAX_BYTES` UTF-8 bytes, since a token is at least a byte) is `unknown`, which `Tasks` handles as PII. It never goes through the inference backoff, asks the gateway only after `onCampus()` and with `onCampusOnly`, takes the on-device generation slot under its own deadline (`acquire(abort.signal, 0)` first, one look and `MlxBusyError` when all are taken: a taken slot sends it to the campus gateway, and only off campus does it wait; on campus a failing or still-loading device hands over to the gateway after 60% of the deadline), sends temperature 0 and `reasoning_effort: "none"` on both paths so a reasoning model cannot spend its 32 tokens thinking, and never puts the text in a log line, an error or an event.
|
|
8
|
+
- An `Inference.complete` call that carries PII text (`ahub ask` over PII evidence or a PII question, a private intake `split`) passes `onCampusOnly`: the model is pinned from `fixedModel()` before any gateway await and the transport refuses the Access endpoint. Its `allowed` predicate is read before the call, after the gateway probe and after the answer; `false` or a throw returns `undefined`. The intake split's JSON is never trusted by `Inference`; its caller validates it (`parseIntakeSplit`).
|
|
8
9
|
- Secrets stay inside `OmniRoute`: never put the key or Access values in a log line, an error message, a return value or routing configuration.
|
|
9
10
|
- Keep model selection in process; retain the Apache-2.0 notices in `src/models/route/` and the upstream provenance check. Treat legacy routing tables as retired and never rewrite operator files during load.
|
|
10
11
|
- Shared inference slots must recover after a hub process dies, without evicting a live owner. Cancellation has to reach slot acquisition from the relay caller, not just exist in the helper signature.
|
|
11
12
|
- Stay/switch (#197): `hub/auto` and local stage routes apply `stay_switch` only through `stayOrSwitch` (`src/models/route/stage.ts`), never at a call site; `shadow` must not change a routing decision (the recorded-decision golden test in `test/route-relay.test.ts`). No route summarizes, trims or rewrites the forwarded conversation: a tier that cannot fit is filtered out. A PII route's planner never sees the conversation size (no prefill bound) and its event omits `prefillTokens`.
|
|
13
|
+
- A change to the model-routing keys of `templates/routing.toml` (`hub_routes`, `local`, `fixed_model`, `stay_switch`, the `[pi]` windows) carries a model-routing receipt (#637 part 2): `bun scripts/benchmarks/model-routing-gate.ts --receipt`, plus `--live` by hand when an alias or a judge moves. The corpus drives the real `HubRouteRuntime`; see "Model-routing promotion rule" in `docs/agent-notes/benchmarks.md`.
|
|
12
14
|
- Load and cooldowns (#199) only reorder a `hub/auto` request's candidates (the route's own backend, then its fallback, as on main), in one ordering step in the relay's fetch handler; a fixed alias is never reordered. Never remove a candidate. A candidate goes first only while it is neither cooling nor `failing` (any failure, an error status or a failed stream included, for 30 s or until a success; separate from the cooldown streak). Load and pin moves are opt-in (`efficientWaitMs` absent: none) until #199 AC5 measures them. Their attempt is bounded only up to its response headers (`MOVE_FIRST_BYTE_MS`, after `omni.base()`); a cut there marks `failing` without counting toward a cooldown, and a stream that fails after the headers cannot fall back, so do not claim a move never fails a request MLX would have served. None happens while the request runs under an execution budget (`underBudget`, a read that admits nothing; a throw counts as a budget). The selector only names `prefer`; it never picks a moved backend itself. They never consider PII because the relay serves only Pi, which refuses PII work before any model call (`selectModel` in `daemon.ts`); a relay caller that can carry PII must not get a reorder toward DGX without a positive `onCampus()`. Count a cooldown failure only for a transport or startup failure outside a cooldown that was neither cancelled, refused admission, a busy MLX slot (`MlxBusyError`: load, never failure), nor a deadline the relay cut itself (`journalEntry.cut`: `"budget"` for the execution budget's, `"move"` for a moved attempt's first byte, which only marks `failing`); any HTTP answer, whatever its status, ends the streak and the cooldown (`answered`).
|
|
13
15
|
- MLX owner and generation identity read `ps lstart` with LC_ALL=C/TZ=UTC pinned (#177): unpinned, a record written under one environment reads as a foreign process under another, and the fail-closed paths would refuse the hub's own server.
|
|
@@ -5,7 +5,7 @@ Scope: the task flow, assignment, the state machine and the hub's MCP tools. Rea
|
|
|
5
5
|
- Scope every task-place comparison through `TaskRepositories` (`src/hub/task-repository.ts`, #476): explicit `refs.repo`, else a common outside-absolute-ref repository or parent, else this project. Preserve relative display spelling within that key and gate overlap, contradiction, replay and held evidence by equal keys. Warm/cache repository identity at task mutation and startup; never repeat Git reads for unchanged overlap inputs. Set `refs.repo` for work in another repository; repo-only accept metadata never grants execution or file access.
|
|
6
6
|
- Tool callers are models: MCP `inputSchema` is not enforced on the way in. Normalize at the boundary (`cleanRefs`) before anything reaches the board, and never throw after a board write: a publish or notify that follows a `review()` or `done()` board write goes through the one `releaseNotice` guard, which turns a failed publish into a console notice, drops a failed notify and never retries (#231, #243). An assignment's delivery once its board write is saved (#276, #297): the move is fact, the failed publish is not retried in place (the journal's own recovery covers a storage error, and a retry may duplicate a publish that actually left), and a person looks for a queued or needs_review delivery with `ahub queue list --peer <owner>` (then `ahub queue show <id>`, or the accepted entry in `ahub task show <id>` if the journal is down) before re-sending with `ahub task assign <id> <owner>`. The distinction is made once, in `assignOwner`: a failure after the write prints the one console notice from `undeliveredNotice` right there, once, and throws `AssignmentUndeliveredError` carrying the saved task; callers (`review()`'s repeated rejection, `escalate` and its callers, the ready offer, both relays, the idle sweep, rescreen) decide control flow by the type and print nothing more, and `propose`, `assignTo` and `decline` additionally let the error reach the caller in its turn, which is a person or peer that can act. Two exceptions get no notice, by construction: a row superseded meanwhile, meaning a later history entry that moves or settles the task (another assignment, an unassign, the owner's accept, done, approval, close), while entries that do neither (the hub's quiet bookkeeping: answers, reviewer changes, idle sweeps, check results) leave the assignment undelivered and the notice stands, so the original error goes to the caller as before only on supersession; and a claim's own accepted write and "claimed by" line stay outside the delivery rule (a claim is its own hand-over; its overlap and routing announcements to earlier owners are deliveries and share the wrapper, so a failure there does read as one). A failure before the write stays a plain `Error` (#254), a failed write leaves the ready offer unconsumed (#231), and the notices before the write (waits, no-owner, the reserved-owner line) go through `releaseNotice` too, so those pre-write notices cannot fail their caller.
|
|
7
7
|
- Post-await assignment publication is fenced by current role authority: supersession returns the current task without a saved-delivery notice; reviewer-only changes refresh current metadata and do not suppress a valid owner delivery. A replacement notice goes only to a role that actually received the request, not to an unread-only queue.
|
|
8
|
-
- Console task row assignment/unassignment carries the exact role, task id and `flowRevision` (history length), checks it before effects and passes it to `Board.update`; a changed revision requires reload.
|
|
8
|
+
- Console or conductor task row assignment/unassignment carries the exact role, task id and `flowRevision` (history length), checks it before effects and passes it to `Board.update`; a changed revision requires reload. Authorized person or conductor owner removal clears owner/reservation and suppresses automatic offers until a later authorized explicit assignment/reservation; retain the host-only `conductorManual` history marker, never infer historical authority from current roles, and require `conductor_recovery` on a recovery target while marked tasks remain open; refuse owner removal under review or after approval, and preserve the required PII person reviewer.
|
|
9
9
|
- Role withdrawal matches the task id, role kind and owner-request header. Owner acceptance evidence must be that peer's accepted entry after the latest ownership event, never another owner's state or an earlier assignment span. Withdraw unread requests without inventing delivery evidence. Find them with `bus.pendingEnvelopes(peer)`, which includes recovery-deferred notices, never the snapshot's queues (#478). Preserve the original former-owner stop notice after a superseding escalation, while suppressing stale success/decision tails.
|
|
10
10
|
- Capacity declines (#531): `hub_task_decline` takes an optional `capacity: true`, recorded as `capacity: true` on the `declined` history entry. `excluded()` skips such an entry, and `Tasks.decline` passes the decliner as `exclude` to that move's own reroute only, so a later reroute or planner pull may offer the task to it again from its reservation. Never infer a capacity decline from the note text, and never treat a row without the field as one: older rows are refusals. `plannerRefusal` (peer ids and counts only, never task text) names why `pullPlanned` found no owner, and `planBacklog` refuses a reservation for a peer `excluded()` returns. `assign()` stays pure, so `route explain` reads the same `excluded()` and `plannerFull` inputs as assignment.
|
|
11
11
|
- `assign()` never defaults to the task's current owner, or a decline can only come back to the decliner. A reservation (#207) is the same risk: the reserved owner goes first only through `blocked()`, and a decline or an escalation for reason `rejections` or `manual` records the owner it left in the history entry's `from`, which `excluded()` reads, so no later reroute hands the task back through its reservation.
|
|
@@ -17,10 +17,10 @@ Scope: the task flow, assignment, the state machine and the hub's MCP tools. Rea
|
|
|
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.
|
|
19
19
|
- Capture the coordination preference once per task at creation (`coordinationModes` from `TasksDeps.coordination`, in `propose` and for each split child, #439). `formCohort` joins only overlaps with the same captured mode and forms a silent cohort only for a turn-free one; a reload never silences an existing task or lifts an existing silent cohort. A daemon restart re-pins retained rows to the startup mode (a compatibility ceiling, not task metadata). `configureSweep` takes effect at the next pass: a running `sweep` keeps the config it started with.
|
|
20
|
-
- The planner "board changed" notice (#758, only with the lifecycle on) is built from what was added to a card's history since the last scan (`Tasks.groomNotices`, `groomScan`), never from the status revision alone: skip the peer whose own entry it was (`entry.by`), tell only the card's planners (`taskPlanner`, #525-2), send a planning revision or dependency change (a `planning` entry, or `deps` that differ from the last scan whatever wrote them: a propose with `after`, a split) and a card's creation (a peer's propose, a split child, a generated card) at task weight, since new work and its dependencies are what a plan review consolidates, and replace an unsettled copy (`sendGroomNotice` withdraws it by the `The board changed` prefix) so a planner meets the latest revision once, and send state moves, reviews and assignments at `fyi` weight (recorded, no turn), at most once per planner per `GROOM_FYI_WINDOW_MS
|
|
20
|
+
- The planner "board changed" notice (#758, only with the lifecycle on) is built from what was added to a card's history since the last scan (`Tasks.groomNotices`, `groomScan`), never from the status revision alone: skip the peer whose own entry it was (`entry.by`), tell only the card's planners (`taskPlanner`, #525-2), send a planning revision or dependency change (a `planning` entry, or `deps` that differ from the last scan whatever wrote them: a propose with `after`, a split) and a card's creation (a peer's propose, a split child, a generated card) at task weight, since new work and its dependencies are what a plan review consolidates, and replace an unsettled copy (`sendGroomNotice` withdraws it by the `The board changed` prefix) so a planner meets the latest revision once, and send state moves, reviews and assignments at `fyi` weight (recorded, no turn), at most once per planner per current `tasks.groom_fyi_window_min` (default `GROOM_FYI_WINDOW_MS`, 10 minutes); read the host config at each notice flush, never capture it at construction. The hub's own bookkeeping entries (`by` is `hub` with no `planning`: lifecycle links, idle sweeps, holds, plan observations) tell nobody and spend no window; a deps change with no new entry has no known author, so no planner is skipped for it. Scan every configured planner, not only the attached ones, so a change made while one was offline is kept for it. The first notice a planner gets after a hub start is task weight. Keep the scan out of the status revision and never read history that is not new.
|
|
21
21
|
- Planner work-ready notices (#785 P1a, `writePlannerNotices`): a write that can free a reserved owner's WIP or release backlog (done, approval, decline, cancel, backlog return or pull, untouched return, pull undo) takes `capacityBefore` (the `wip()` of the task's owner and reservation) before its board write and calls `writePlannerNotices` after it. Each rostered planner of the task (`taskPlanner`) gets one ids-only `task` envelope starting `Planner work ready:`: `capacity_released: <peer> WIP <n>; reserved ready #<id>|none` for a peer whose WIP dropped, and, when the write approved the task, `backlog_ready: #<id>` for each backlog dependent with nothing left to wait for. Merge the lines of that planner's unsettled copies, read back from `bus.pendingEnvelopes` (they survive a restart; never keep them in memory), withdraw them, then publish, so at most one stays queued per planner; all of it runs inside `releaseNotice`. The notice only wakes the planner: never pull, reserve or assign from it, and never put a title or note in it.
|
|
22
22
|
- Opt-in auto pull (#785 P1c, `tasks.auto_pull`, `Tasks.queueAutoPulls`, `autoPull`, `topReservedReady`): `writePlannerNotices` collects the peers whose WIP this write lowered and, only when `TasksDeps.autoPull()` is true (read at each release, so a reload applies to the next one), queues one `autoPull(peer)` per peer on a promise chain, so two releases never pick the same card and each job re-reads the board. It takes the top backlog card explicitly reserved for that peer (`topReservedReady(peer, undefined, true)`, the rule the notice's `reserved ready #id` line also uses; never an unreserved card, never PII, which `topReservedReady` skips for a pull), finds the card's own planner of record (`taskPlanner(card)[0]`, which must be rostered) and calls `pullPlanned(HUB, input, { onBehalfOf: planner })`. `onBehalfOf` is accepted only with `by === HUB`; the planner's authority is what `planningAuthority` checks, the actor in history and the planning telemetry stays `hub`, and the planner binding records the planner it acted for. Every other check is `pullPlanned`'s own, unchanged, and a refusal is one console notice with the public title (never detail). A held or recovering hub, a storage error or the setting off pulls nothing, and that is checked twice: at entry and again by `pullPlanned`'s `opts.guard` (`autoPullAdmitted`) wherever authority is rechecked, which includes right before the assignment or board write after the awaited PII screen and `planReviewGap`, so turning the setting off or starting recovery or shutdown mid-admission writes nothing and reports nothing. `autoPullsPending()` counts queued and running pulls and `recoveryReady` waits for it (also named in the recovery view's blockers), as it does for `checksPending`. The key is in `ALL_MACHINE_LOCAL` (`config-trust.ts`) so a committed config is ignored for it, and it is not in `CONDUCTOR_SETTING_KEYS`: an agent never turns it on. Never add a timer or sweep that pulls: the only trigger is the write that freed the slot.
|
|
23
|
-
- Per-head CI watcher (#785 P1b, `src/hub/ci-watch.ts`, `Tasks.watchCi`, `ciCompleted`, `reofferHeldReview`): when a `done` leaves a review whose admission was not ready at the task's exact full-commit head (or a review is held as premature in `sendReview`), `CiWatchers` starts at most one watcher per task and head, with chained unref'd `setTimeout`s and no interval. Each poll is one `gh pr view` through the `sdlc.gh` port (`pollCi`); it ends on
|
|
23
|
+
- Per-head CI watcher (#785 P1b, `src/hub/ci-watch.ts`, `Tasks.watchCi`, `ciCompleted`, `reofferHeldReview`): when a `done` leaves a review whose admission was not ready at the task's exact full-commit head (or a review is held as premature in `sendReview`), `CiWatchers` starts at most one watcher per task and head, with chained unref'd `setTimeout`s and no interval. Each poll is one `gh pr view` through the `sdlc.gh` port (`pollCi`); it ends on a passed result for the required checks, on `CI_WATCH_DEADLINE_MS`, on `stopCiWatchers` (daemon `stopOnce`), or silently when `ciWatchCurrent` fails (the task left `in_review`, its head or PR changed, the lifecycle is off, it turned private). `complete`, `review` (both verdict writes) and `cancel` cancel it. Each (head, outcome) is told once (`settled`), and only `passed` settles a head for good (#540): after `failed` or `incomplete` the watcher keeps reading for a rerun of that head (the first pending read after each newly told result is a new run attempt and renews the deadline once; later pending or unreadable reads and an unchanged failed result never renew it, so the watcher always ends; a deadline after a told result ends silently), and a later `hub_task_done` at that head starts one again. A newer head replaces the watcher. The result is one ids-only `ci_completed:` task envelope at `status` weight to the owner and the reviewer (`fyi` is never delivered to a peer, so it would not reach them), a console notice for `user`, and for a deadline a console notice only. A passed or incomplete result re-offers a review held as premature once (`heldReviews`, keyed by task, head, reviewer and review revision) after a fresh `reviewRequestAdmission` says ready; a failed one re-offers nothing. No task text goes in the notice, no PII task is watched, and nothing is restored after a hub restart (the next `hub_task_done` starts one). Never add a second periodic gh read outside this watcher.
|
|
24
24
|
|
|
25
25
|
- Completed-change notices (#713, one per recipient per done, accepted work only, superseding by `completedFor`): rule in `bus.md`.
|
|
26
26
|
- Plan-symbol overlap (#682 1b) compares through `symbolsOverlap` (`src/hub/symbol-normalizer.ts`): arguments stripped, `section `/`source ` keys exact, two qualified names equal, otherwise last segments equal. `places`, `overlapHits`, `contradict`, the completed-notice replay, `plannerGrooming` and `planSymbolMatches` all use it; never compare plan symbols with `includes` or set membership.
|
|
@@ -54,8 +54,8 @@ Scope: the task flow, assignment, the state machine and the hub's MCP tools. Rea
|
|
|
54
54
|
|
|
55
55
|
- Resolve any id that may be an extra instance or a configured kind through the startup-bound project catalog: `this.d.adapterCatalog` with `this.d.instances?.()` in Tasks, `opts.adapterCatalog` in `assign()` and `relayCandidates`, and the `adapterCatalog` hook in `Conductor`/`conductorStart`; a bare `definitionOf(id)` answers builtins only. Only a kind's routing preference expands to its instances; manual ids, reservations and nominations stay exact. Reviewer independence compares families: take the own family only when no other is eligible, record it through `assignmentNote` (`sameFamilyFallback=true`), explicit nominations included; persist the `instance`/`family` refusal codes as `policy`.
|
|
56
56
|
- Tool publication (`projectToolList`, `projectConductorTools`, `configuredToolContext` in `src/hub/hub-tools.ts`) only narrows the list or adds start targets, using authenticated status metadata (`configuredAdapters`, `startablePeers`, `readOnlyPeers`); in a configured project, malformed or incomplete metadata publishes the read-only set and adds no start target. Publication is never authority: `taskOp` refuses read-only mutations independently, and MCP task and conductor refusals keep their text and set `isError: true`.
|
|
57
|
-
- SDLC metrics and bands (#481 S7): `sdlc-metrics.ts` is pure (history in, numbers out, no clock, no I/O); every metric a task cannot derive is `null`/`unknown`, never 0, and a private task is only counted as withheld. Stale-head re-reviews need both heads on a round (`reviewCost.head` on the done, `reviewFindings.head` on the verdict): never derive them from event order, the board cannot produce an `approved` followed by a `done`. `Tasks.sdlcMetrics` is the one read (planner or person, refused during a private turn) and `Tasks.proposeFromBands` the one writer. It is held like every host writer (`sweepHeld`, `bus.isRecoveryHeld`, `bus.storageError`), keys a breach by its episode (`BandResult.episode`, derived from history so a restart finds the same key), writes nothing while the metric has an open band card, and records a log notice in `band_notices` so it is announced once. Card text is fixed words, numbers and task ids, screened through `signalsFor` and `candidateTextAllowed` like the recurring-finding card; never put task titles, notes or PR text in it, and never open a GitHub issue. Drift honours `min_delta` like every tier. `[bands]` is validated by `bandsConfig` with field-level paths; do not add a tier or a metric without a test that fires it.
|
|
58
|
-
- Admit a declared extra (a person-started `ahub claude --as` instance and a headless Claude implementer included, #742, #747) as owner of any class whose owner role is implementer (`ownerRoleOfClass`) only with the current implementer role and `ownerIsolation[id] === true`, the daemon's proof that the live adapter generation started in `executionRoots[id]` outside the canonical cwd. Never derive it from the declaration's `worktree`, its roles or a reload.
|
|
57
|
+
- SDLC metrics and bands (#481 S7): `sdlc-metrics.ts` is pure (history in, numbers out, no clock, no I/O); every metric a task cannot derive is `null`/`unknown`, never 0, and a private task is only counted as withheld. Stale-head re-reviews need both heads on a round (`reviewCost.head` on the done, `reviewFindings.head` on the verdict): never derive them from event order, the board cannot produce an `approved` followed by a `done`. `Tasks.sdlcMetrics` is the one read (planner or person, refused during a private turn) and `Tasks.proposeFromBands` the one writer. It is held like every host writer (`sweepHeld`, `bus.isRecoveryHeld`, `bus.storageError`), keys a breach by its episode (`BandResult.episode`, derived from history so a restart finds the same key), writes nothing while the metric has an open band card, and records a log notice in `band_notices` so it is announced once. Card text is fixed words, numbers and task ids, screened through `signalsFor` and `candidateTextAllowed` like the recurring-finding card; never put task titles, notes or PR text in it, and never open a GitHub issue. Drift honours `min_delta` like every tier. `[bands]` is validated by `bandsConfig` with field-level paths; do not add a tier or a metric without a test that fires it. Merge-time figures (#442) come from `MergeRecord.cost`, written once by `guardedMerge` after it observes the merge (`mergeCostFrom`, `cleanMergeCost` in `cost-governance.ts`): re-validate a stored record with `cleanMergeCost` before use, never let that read touch the merge result, and keep a missing or partial read `null`, never 0. `hotfix_rate` and `security_open_alerts` are external series (`EXTERNAL_METRICS`, `Tasks.externalSeries`) whose points are releases and scans, so a card names its `subject`; a legacy release with no train history is left out of the series, never a 0. The security scan (`security-baseline.ts`, `Tasks.runSecurityScan`) reads GitHub's own open-alert counts, runs no scanner, schedules nothing without `[security_scan]`, is held like every host writer, re-judges its schedule after the awaited read and records counts only; a source it cannot read is an unknown total, never zero.
|
|
58
|
+
- Admit a declared extra (a person-started `ahub claude --as` instance and a headless Claude implementer included, #742, #747) as owner of any class whose owner role is implementer (`ownerRoleOfClass`) only with the current implementer role and `ownerIsolation[id] === true`, the daemon's proof that the live adapter generation started in `executionRoots[id]` outside the canonical cwd. Never derive it from the declaration's `worktree`, its roles or a reload. Only the status path passes a display `ownerIsolation` projection (`stalled(projection)`); every admission and effect caller uses fresh `TasksDeps.ownerIsolation`.
|
|
59
59
|
|
|
60
60
|
- Lifecycle identity (#694): the hub links `sdlc.repo`/`issue` only through `Tasks.linkIssue` (the one issue `planIssueIdentity` resolves) at propose and accept (`linkOnWrite`, #723: a local parse, never a read, never a refusal, advise and enforce only), plan review, done and the groom backfill (`backfillIssueLinks`: first in `groomingDigest`, 32 links and 1 s per call, before the slow metadata reads), and stages a done's PR through `Tasks.identityLink`/`withLink` (the link carries the repository and the commit or branch it was read for, and is dropped when the done's effective repository or head changed during the read; the issue is re-resolved at apply time; `reviewRequestAdmission` compares the board copy through the same `stage` projection) (`openPrFor`: the one open PR whose head is the done's full commit, or with no commit its branch; a full 100-row page or a malformed row is unknown). Persist a done's link only in the admitted done update or `done (checking)`, never before a gate that can still throw `PreEffectToolRefusal` (#254); reapply it after every re-read in `complete()`. Write a first link with `linkedReceipt`, never `sdlcReceipt` (its relink rule would drop red-first and local-gate records); never relink an existing or foreign identity, never parse agent text for a PR, skip PII tasks before any read, and run the groom backfill after its authority checks with its own bound.
|
|
61
61
|
- Spec acceptance (#481 S1) is written only by `Tasks.acceptSpec` for the person; never accept it from a tool or a receipt. The daemon uses `Tasks.acceptReviewed` to bind spec and plan readbacks to one task snapshot before `Tasks.accept`; retain its synchronous missing-spec and plan-admission refusals. Apply WIP and plan admission before `done` implicitly accepts proposed work and before cohort writes. Never read or store an issue body: identity and edit times only.
|
|
@@ -67,6 +67,7 @@ Scope: the task flow, assignment, the state machine and the hub's MCP tools. Rea
|
|
|
67
67
|
- `Tasks.review` captures `reviewAuthority` once and rechecks it in `valid()` after every await, right before each write; a headless reviewer's verdict, and its `hub_task_show` (checked before and after the read), holds only while the same child runs for that task at its bound envelope head and review revision (`Tasks.reviewBindingCurrent`, host-only: `in_review`, same reviewer, non-PII, matching `reviewRequestRevision` and review head). Never cache the answer (#223, #533).
|
|
68
68
|
- Review scope (#852, `src/hub/review-scope.ts`): a reviewer reads `full` (code, affected tests, CI) or `code` (code only). The default is data on the fixed reviewer variants (`AdapterDefinition.reviewer.scope`, `code` for the sandboxed codex and kimi variants, fixed-builtin-only like `instances.modelSelect`); a fresh-reviewer instance may override it with `review_scope` from `config.local.json` only (tier 3, never from a tracked file or the dashboard). `TasksDeps.reviewScope(peer)` is the one read. `sendReview` replaces the Hub-verified, check-result and no-check lines with `CODE_SCOPE_LINE` for a `code` reviewer, which requires every code defect as a structured finding with an id. Reserve findings for defects in the reviewed code, docs or tests; missing check/CI/test-run/PR-or-branch identity evidence goes in `unmet`, never `findings` (Amendment 2). The verdict rule is structural (amended AC3): a `code` reviewer's `changes_requested` is always recorded, never refused, and never classified by reading its unmet text; never add a text classifier (background: README). If its structured `findings` carry no item with disposition `open`, the history entry is `evidenceOnly: true`: it adds nothing to `rejections` (so it cannot escalate), records no failed outcome, contradicts no earlier approval, writes no recurring-finding candidate, is not a rework round in the SDLC metrics, still returns the task to its owner as usual, and sends the configured conductor one ids-only task notice (`evidenceOnlyNotice`; with no conductor, one console notice). A verdict with an open finding counts as today. A person's and a `full` reviewer's verdict are never evidence-only. A `code` reviewer's verdict history entry carries `reviewScope: "code"` and a `full` reviewer's carries nothing, so old rows are unchanged. Merge rule: a task approved by a `code` reviewer is merged by the conductor or the person only after exact-head CI is green and no review thread is unresolved, and the merge note names the CI run.
|
|
69
69
|
- Keep structured review findings in verdict history, bound to the reviewed head. Validate bounded reports and person-only accepted dispositions before effects; recheck authority, privacy and head identity after awaits. A supplied `head` that differs from the task's resolved head (`findingHead`) is refused pre-effect, and the resolved head is part of the identity rechecked after the screen (#657). Key findings by `id` alone, never by pass (`findingsForHead`): a nit or open reclassification never discharges an Important, only an explicit disposition does. Omitted IDs never settle findings; carry open Important across heads until explicitly dispositioned, while nits stay head-local. Use the shared valid-head resolver for projections. Project text through publicView, and deduplicate recurring public pass/id pairs into backlog proposals through durable `Review finding key:` markers (a pair recurs only across separate work: tasks sharing a PR, a branch in one repository or a supersession count once, `workUnits`, #736; a task counts only where a review that is not withheld recorded the finding's id, whatever its pass, as open or accepted, never where it is only recorded fixed, #328) read only from hub-proposed `plan` cards (first history entry by `hub`), without editing instruction files.
|
|
70
|
+
- Recurring unmet criteria (#514a, `src/hub/learning.ts`): grouping is pure (rows in, groups out); `Tasks.proposeCriterionCandidates` is the only caller and runs beside `proposeRecurringFindings`. It files a backlog card with ids and counts and the `profile_overrides` fragment as text, never applies it, and counts only rows that `candidateTextAllowed` passes (no PII). Its dedupe is the `Criterion candidate key:` line; keep it first in the detail.
|
|
70
71
|
- Plan conformance (#481 S2, `src/hub/plan-conformance.ts`): compare only literal repository-relative `plan.paths` entries (file or directory prefix, no glob) with complete PR file metadata; `completePlanInventory` requires a full head, `pathsComplete` and `typesComplete` (false on any rename or copy). Root, absolute-unverified or unsafe entries, a missing plan, a truncated inventory and a failed read are `unknown`, never covered; never read patches or the filesystem. An untouched planned path is advice; a changed path outside the plan is a departure, and `planConformanceBlockers` blocks enforce approval on `unknown` and on an unaccepted departure. Plan `proof` and `risks` (32 strings of 512 characters) cover no file. `Board` records `planRevision` only while `trackPlanRevisions` (mode not off), separate from backlog `planningRevision`.
|
|
71
72
|
- Departure consent is written only by `Tasks.acceptPlanDeparture` for the person (`ahub sdlc accept-departure <task> <head> <reason...>`; no peer tool) on a currently known departure at the current PR head, never on `unknown`, and never for a PII task. It binds repo, PR, head, plan revision and fingerprint, the inventory, `planScopeIdentity` and this host's `departureGeneration`, so a restart or a change to any of them invalidates it (`validPlanDepartures`). Status and merge-queue reads never write the board: they only note observations and invalidations in memory (`observedPlans`, `invalidatedPlanDepartures`), which `planObservation()` attaches to the next owner or reviewer transition write. Off captures no observation or revision; a plan or refs change still invalidates an existing acceptance.
|
|
72
73
|
- The review coverage gate (#520) pins `refs.commit` and the sdlc receipt and rechecks them in `valid()`; an unavailable brief is never an empty one (enforce refuses, advise notes). Read brief attributes and rules at the merge base (`--attr-source`, `check-attr --source`, `git show <base>:`), never from the head or the checkout.
|
|
@@ -86,8 +87,8 @@ Scope: the task flow, assignment, the state machine and the hub's MCP tools. Rea
|
|
|
86
87
|
- `ahub report --dogfood` (#687, `src/hub/dogfood.ts`, `src/hub/dogfood-read.ts`) opens `hub.db` read-only with byte and row caps; an unavailable or incomplete source reports `null`, never 0, and per-PR GitHub reads run only when the task privacy inventory is complete.
|
|
87
88
|
- Qualified titles (#723): `planIssueIdentity` links a sibling `repo#N` only when `refs.repo` names that repository (`siblingTitleRefs`, `spec-read.ts`), and an `owner/repo#N` only when it is `refs.repo`, else this project's repository; a sibling shorthand beside a bare `#N` is two identities and links neither. A title that names another repository with no `refs.repo` does not link, and `refsRepoHint` tells the proposer to set it (advice in the `hub_task_propose` result, never a refusal; none in mode off, for a PII task or once `refs.repo` or an issue is set). There is no retitle path on the board: a title is fixed at propose.
|
|
88
89
|
|
|
89
|
-
- Planner walk and reviewer independence (#525-3): `plannerWalk` and `implementers` in `src/hub/routing.ts` are pure; the planner is chosen only inside `assign()` (`planners`, `namedPlanner`, `keptPlanner` in, `Assignment.planner`/`plannerReason` out), and `Tasks` never picks one outside it. `Tasks.weights()` is the one place `planners` is built (rostered holders of `d.roles`, sorted), so every `assign()` path and `explain` agree. Pass a bound task as `keptPlanner` to that same walk: its explanation names the binding as `kept`; drafts and legacy null bindings still rotate, and explanation never writes a binding or history. Rotation is `task.id mod n` over every holder and is never renumbered by availability; a named planner is exact and never replaced. I-2 (`implemented this task`) lives in `blocked()` for the reviewer role as its
|
|
90
|
-
- Owned implementer child (#747): `propose`, `acceptReviewed`, `done` (through `complete()`) and `remember` take `requestCheck(by)` (`TasksDeps.requestAuthority`) at entry and call it after every await, before the next write; a new await on those paths adds a call, and its refusal stays pre-effect. The child's tool surface is the fixed `IMPLEMENTER_HUB_TOOL_NAMES` (`src/hub/hub-tools.ts`); never widen it from project roles.
|
|
90
|
+
- Planner walk and reviewer independence (#525-3): `plannerWalk` and `implementers` in `src/hub/routing.ts` are pure; the planner is chosen only inside `assign()` (`planners`, `namedPlanner`, `keptPlanner` in, `Assignment.planner`/`plannerReason` out), and `Tasks` never picks one outside it. `Tasks.weights()` is the one place `planners` is built (rostered holders of `d.roles`, sorted), so every `assign()` path and `explain` agree. Pass a bound task as `keptPlanner` to that same walk: its explanation names the binding as `kept`; drafts and legacy null bindings still rotate, and explanation never writes a binding or history. Rotation is `task.id mod n` over every holder and is never renumbered by availability; a named planner is exact and never replaced. I-2 (`implemented this task`) lives in `blocked()` for the reviewer role as its last capability/independence check before queue admission, so the legacy, named and pool paths share it and it only ever removes an otherwise eligible candidate; when I-2 removed every otherwise eligible reviewer, the person reviews with `reviewBlocked`, never an implementer.
|
|
91
|
+
- Owned implementer child (#747): `propose`, `acceptReviewed`, `done` (through `complete()`), `recordSdlc`, `review` (in `valid()`), `correct` and `remember` take `requestCheck(by)` (`TasksDeps.requestAuthority`) at entry and call it after every await, before the next write; a new await on those paths adds a call, and its refusal stays pre-effect. The child's tool surface is the fixed `IMPLEMENTER_HUB_TOOL_NAMES` (`src/hub/hub-tools.ts`); never widen it from project roles.
|
|
91
92
|
- Shell branch binding (#845, `Tasks.bindShellBranch`): the daemon's host shell wrapper path is its only caller, and it is the only writer of the hub's `shell branch bound` entry (the branch and a sha256 of the worktree root, setting `refs.branch` in the same write). It binds only for the current owner of an `in_progress` or `changes_requested` non-PII task in this project's repository, outside a private turn, whose verified execution root (`TasksDeps.executionRoot`) is not the project root and is the binding's worktree. It refuses a branch that differs from an earlier binding or from `refs.branch`, or that another open task in the same repository holds, and rechecks `current()`, the private turn and the flow revision right before the write. Every failure returns `false` (no grant), never a throw. It records host-observed evidence, never a branch a tool call claims, and grants no execution by itself. The entry is in `QUIET_EVENTS`: it neither moves nor settles the task, so it never makes a running check's result late, supersedes an assignment or counts as a move for review admission (#880).
|
|
92
93
|
- Deliverables (#509a, `src/hub/deliverables.ts`, `src/hub/deliverable-files.ts`): the hub hashes each file itself under the owner's verified execution root (`TasksDeps.executionRoot`, the daemon's `ownerWriteRoot`; an unproven root refuses); never accept a caller's hash, size or unknown field. Vet and hash before any board write and refuse pre-effect; a `done (checking)` carries its records to `complete()`, never a later read. An approval rereads the delivered files: a reviewer's verdict is refused before any write unless every file is `unchanged`, and only the person approves moved files, which are rehashed and named in the note. A file the hub cannot read or vouch for is `unknown`, never `unchanged`. Peer-facing records go through `publicDeliverables`: for a PII task or a `pii.paths` file, id, hash and size only.
|
|
93
94
|
|
|
@@ -95,7 +96,7 @@ Scope: the task flow, assignment, the state machine and the hub's MCP tools. Rea
|
|
|
95
96
|
- Plan-review independence (#525 I-4): `recordPlanReview` admits any settled planner holder or the person, independently of the task binding, and rechecks author/eligibility after every await. Derive the author from the latest authoring `planRevision`, then legacy planning `plan`; exclude `plan reviewed` scope-bookkeeping entries from both. Use the initial author only for nonempty intent list fields, never review/CI scope alone; unknown is null. Preserve later actual intent-edit authorship. Refuse agent self-review while another rostered non-offline holder exists; otherwise record `selfReview: true`. Keep person overrides explicit in that marker. Independent requests use the same pure assignment walk excluding the author, ids only through public view, and a history-flow fence rather than timestamps. This exception grants no planning or lifecycle evidence powers on another holder's task.
|
|
96
97
|
|
|
97
98
|
- Planner lifecycle stage identity (#525-2): allow the effective task planner only `stage`/`repo`/`issue` for an explicit stage whose actor is `planner` or `owner/planner`. Preserve existing enforce identity-relink and stage/spec gates, and advise relink behavior. Keep reviewer evidence, checklists, heads and deliverables restricted to their existing owner/reviewer/person authorities.
|
|
98
|
-
- Deliverables list and badge (#509b, #317): `Tasks.deliverableList` is the one reader for `ahub deliverables` (the `deliverables` task op,
|
|
99
|
+
- Deliverables list and badge (#509b, #317): `Tasks.deliverableList` is the one reader for `ahub deliverables` (the `deliverables` task op; for an agent, #933, only with `visible` set to `taskShowAllowed` from `src/hub/conductor.ts`, the one rule hub_task_show also uses, applied before any file is read) and `Tasks.deliverableBadge` is the snapshot's per-card field. Both go through `publicDeliverables` and `publicTitle`; neither the snapshot nor a list without `check` may open a file (the page polls every second). A `changed` claim comes only from `deliverableStatus`, cached by record ids and hashes in `deliverableSeen`: never derive it from the board alone. The cache keeps the whole-task `result` (`deliverableResult`: changed, missing, unknown or unchanged), never a boolean: `unchanged` only when every file measured unchanged, so a missing or unreadable file is never shown as unchanged.
|
|
99
100
|
|
|
100
101
|
- 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.
|
|
101
102
|
- 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.
|
|
@@ -103,4 +104,11 @@ Scope: the task flow, assignment, the state machine and the hub's MCP tools. Rea
|
|
|
103
104
|
- 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.
|
|
104
105
|
- 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.
|
|
105
106
|
- 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`.
|
|
106
|
-
- 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
|
|
107
|
+
- 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 and the eval gate'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 the sweep, recovery and storage holds). 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.
|
|
108
|
+
- Schedules (#512a, `src/hub/schedules.ts`, `Tasks.proposeFromSchedules`): the hub proposes a scheduled task only through `Tasks.propose` as `HUB`, never `Board.propose`, so the PII screen, class validation, triage and routing apply. "Its previous task is open" is read from the board (`openScheduled`: first history entry by the hub, detail starting `Schedule key: <id>`, state not closed), never from `schedule_runs`. A run restarts the period at `now`; a hold or an open task counts a skip and proposes nothing, so a long hold ends in one task. Status rows carry ids, times and a state, never the title or detail.
|
|
109
|
+
- Folder triggers (#512c, `src/hub/folder-trigger.ts`, `Tasks.runFolderTriggers`): the folder pass shares `schedulesRunning` with the schedules pass and `ahub schedule run`, so it is serialized with them and counted by `schedulesPending`; keep it so, and never start it from a path that does not take the guard. A file is recorded in `folder_seen` only AFTER its task exists, and the pass stops at the first failure so order is kept; do not record first (a failure would lose the file) or continue past a failure. A task made for a file names the source id and never the file's name or path, because both can be private. A trigger's baseline (the files there when it starts, possibly over several polls) proposes nothing, and a refused folder never begins the trigger; do not make either propose. The scan must keep no file's bytes (it hashes in chunks under a file and byte budget and never cuts the listing); `registerScanned` re-reads the one file being registered and refuses it unless it still hashes to what the scan saw. Ask `isNew` for each file inside the loop, not only when building the list, and `observe` (never skip) every file whose content is already known, in the list and in the loop, so it is found by `known` next time and cannot starve a new file of the hash budget. `folder_triggers` stays in `MACHINE_LOCAL` and `NEVER_EDITABLE`.
|
|
110
|
+
- A scheduled run is fenced twice (#512a): `proposeFromSchedules` checks the hold before it starts and passes `propose` a `fence` that `requestCheck` asks again right before the board write, because the PII screen and triage are awaited in between and `requestAuthority` only fences child generations. Any other host writer that awaits a screen before `Board.propose` needs the same fresh admission at the write. A recovery commit also waits for a pass in flight (`Tasks.schedulesPending`, read by `recoveryReady()` in the daemon): every host writer that can write across an await gets a pending count there, next to `checksPending` and `autoPullsPending`. A person's schedule command that changes what a run in flight should do (`pause`, `resume`, #335) must reach that run's fence too: `ScheduleStore.epoch` is bumped by `setPaused` and `runSchedule`'s fence compares it, so the acknowledged command wins over an admitted run; `ScheduleStore.ran` never moves the anchor back. Any manual entry into the same writer (`Tasks.runScheduleNow`) takes the same `schedulesRunning` guard and is registered with the shutdown drain (`manualSchedulePasses` in `startDaemon`), never called around it.
|
|
111
|
+
|
|
112
|
+
- Person-or-conductor writers (#933): `importGitHub`, `approveIntake`, `runScheduleNow`, `setSchedulePaused` and external intake in `propose` admit only the person or a peer for which `TasksDeps.operational` answers true, and record that peer as the actor, never `USER`; `runScheduleNow` asks the same check again in the hold its board-write fence reads, so authority that ended during the PII screen writes nothing. `setRole` admits the person or the task's owner, and only the person while the task is `in_review`. An agent's `ahub task cancel` reaches `Tasks.cancel` unchanged; never add a second own-task rule for it. `task_label_check` (the daemon's answer before an agent's CLI writes a research label) admits only the task's owner of the current board row, so the CLI refuses an agent's `--created` (an earlier task with that id) before asking; an agent's label record carries `by`, and every reader of labels goes through `effectiveLabels` (`src/hub/research.ts`), where a person's label always wins.
|
|
113
|
+
|
|
114
|
+
- Claim verdicts and fact_check provenance (#901, `Tasks.recordClaimVerdict`, `parseClaimVerdict`, `claimListId`): only the console user records a verdict (`ahub task claim`; an agent tool never does), the claim is named by its 1-based slot and its text is never typed, stored or printed, and every field is parsed closed. A verdict entry carries `list`, the `claimListId` of the plan's ordered claims, and counts only while that id equals the plan's current one, so a replaced or reordered list leaves the slot unknown. `claim verdict` is in `QUIET_EVENTS`. The hub stamps `checkKey` and `missingKeys` on its own check entries from the lookup that chose the command, never from output beyond the example check's exact last line. In `dogfood-profile.ts`, `factCheck.agreement` counts per label only the slots whose model verdict was entered and equals the person's: a slot without the model's verdict is unknown, one that differs counts for no label, and a person verdict alone is never an agreement. Every one of these rows is null, never zero, when its source is missing.
|
|
@@ -6,6 +6,7 @@ Scope: tests, fakes and the test gate. Read before adding or changing a test or
|
|
|
6
6
|
- When extracting production closures into a VM fixture, bind every referenced host port and actual imported policy helper. Keep native identity checks on real unstarted instances; never replace a policy predicate with an always-true stub or weaken existing assertions to accommodate a moved dependency.
|
|
7
7
|
- A fixture that walks a policy table (a `COMPAT` entry list, a registry) derives its per-entry values from the table, never a fixed list of keys or versions a planned entry is not in: the compat watch test builds every served version and the ahead/unknown row from `COMPAT[id]`, so an added entry (`pi`, #843 AC2) is covered without editing the test.
|
|
8
8
|
- Tests that are not about batching build the bus with `batchMs: 0`; with the default 15 s window a lone status envelope looks like a lost message.
|
|
9
|
+
- A test that writes an executable shim and then times a product call through it (a `git` wrapper on `PATH`, as `test/memory-code-head-684.test.ts` does) runs the shim once by absolute path first, outside the measured region: the first exec of a newly written executable costs about 250 ms on macOS (#519). Never widen the product's bound or weaken the assertion for it.
|
|
9
10
|
- A test timeout that fires while `Bun.spawnSync` runs can start the next test inside spawnSync's own event loop (Bun 1.3.14 and 1.4.2; background: README). `scripts/check.sh` runs tests with a 20 s timeout and under `scripts/hang-watch.sh`; a test that spawns and reads the process table gets a timeout of its own, never above 20 s: an explicit larger timeout overrides the gate's `--timeout 20000`, so shorten the scenario instead.
|
|
10
11
|
- Sandboxed commands and the MLX server get a fixed environment (`PATH`, `HOME` and their own keys), so test knobs for their fakes travel in a wrapper script or argv, not in `process.env`; native peer children inherit the hub's environment minus the agent markers (`peerChildEnv`).
|
|
11
12
|
- Bind agent identity markers explicitly in native/CLI fixture children. The full gate starts the test runner without inherited agent markers (including `AGENTHUB_DELIVERY_ONLY`) and without an `ahub claude` session's hub variables (`AGENTHUB_PROJECT_DIR`, `AGENTHUB_STATE_DIR`, `AGENTHUB_INSTANCE_ID`, `AGENTHUB_LAUNCH_ID`); a new variable a native session exports and a fixture child reads joins that `env -u` list in `scripts/check.sh`. Never weaken production marker detection to make them pass.
|
|
@@ -15,7 +16,11 @@ Scope: tests, fakes and the test gate. Read before adding or changing a test or
|
|
|
15
16
|
- `ahub upgrade` without `--to` asks the registry (`npm view`) and hands a target newer than the CLI to `bun x` (#272): a test that reaches either puts a fake `npm` first on `PATH` and a recording stand-in for `process.execPath`, as `test/upgrade-cli.test.ts` does. The interactive screens are tested through a scripted `ScreenIO`; a CLI test that needs them marks `process.stdin` and `process.stdout` as terminals in a wrapper script.
|
|
16
17
|
- Cross-process fixtures wait for the consuming client to process its handshake (the channel connected diagnostic), not merely the daemon's attached/idle state. Deadline-routing fixtures hold and release responses at the fallback decision; do not place a fixed sleep between deadline fractions and assume scheduler headroom (#287). Receipt-order fixtures use the normal watchdog for ordinary I/O, trigger expiry after observed native acceptance, and await a consumed transport barrier before asserting on late results.
|
|
17
18
|
- A bounded polling wait must assert its condition after the last attempt, or document why giving up is intentional. Append-only event histories wait for a correlated closing event, not removal of the opening event. Before changing a slow wait, identify the unreached state and repair its cause (#296).
|
|
18
|
-
- A test of turn or notice behavior that is not about the snapshot budget yields an I/O turn (`await new Promise(r => setImmediate(r))`) between captures: captures in one synchronous burst share the 250 ms budget and may correctly record unknown evidence. A daemon test whose assertion needs a turn's snapshot or end evidence passes `snapshotClock: () => 0` (`DaemonOptions.snapshotClock`, #661) so a slow runner cannot spend that budget; never lengthen the budget instead. A wait on concurrent-edit evidence correlates the expected turn ids and paths (#617).
|
|
19
|
+
- A test of turn or notice behavior that is not about the snapshot budget yields an I/O turn (`await new Promise(r => setImmediate(r))`) between captures: captures in one synchronous burst share the 250 ms budget and may correctly record unknown evidence. A daemon test whose assertion needs a turn's snapshot or end evidence passes `snapshotClock: () => 0` (`DaemonOptions.snapshotClock`, #661) so a slow runner cannot spend that budget; never lengthen the budget instead (#550). A wait on concurrent-edit evidence correlates the expected turn ids and paths (#617).
|
|
20
|
+
- Holding the snapshot budget clock (`snapshotClock`, #661) holds the deadline and, since #554, the git child's wall-clock timeout (the 10 s ceiling instead of the remaining budget). The rule lives in the pure `childTimeoutMs` (`src/hub/snapshots.ts`: production keeps min(10000, remaining), only a budget carrying `now` gets the ceiling). Reproduce a slow git with a deliberately slow `git` first on PATH (`slowGit` in `test/snapshots.test.ts`), never with host-wide load, and never run a high-parallel loop while another gate shares the dgx02 host.
|
|
21
|
+
- The macOS CI leg is four shards (#548): `check (macos-latest) shard i/4` runs `AHUB_CHECK_SHARD=i/4 scripts/check.sh` (`bun test --shard --timings=scripts/test-timings.json`), and the job named `check (macos-latest)` is only the aggregator over them; `scripts/ci-gates.mjs`, branch protection and the reuse proof read that name, so never rename it or turn it back into a matrix job without reading them. `gateJob()` in `scripts/ci-gates.mjs` counts the shard names (`check (macos-latest) shard i/N`) as gate jobs, so a run with Linux and the seeded shards done and only macOS shards running reads as a launched full gate and not as unknown scope (`test/ci-reuse.test.ts` pins every mid-run window). A new test file needs no timing entry (it gets the median), but a shard that grows past about 15 minutes means the timings are stale: regenerate them with `scripts/make-test-timings.ts`. A shard starts the suite cold, so `scripts/check.sh` execs the system git once (`AHUB_CHECK_WARM_GIT`, default `/usr/bin/git`) before the tests, because tests that give it `PATH=/usr/bin:/bin` and a 1000 ms timeout (`read-only-shell-proof`, `task-shell-default`) are killed by a cold first exec; a failing or absent warm-up never fails the shard. `check.sh` unsets `AHUB_CHECK_SHARD` after building the test arguments, so the runner and every scratch `check.sh` a test spawns (`test/check-lock.test.ts` copies `process.env`) see an unsharded environment; keep that unset. A test must not depend on running in the same process as another file (shards split files across machines), and anything that has to happen once per gate belongs behind the shard-1 condition in `scripts/check.sh`, which `test/macos-shard.test.ts` pins.
|
|
22
|
+
- A test that proves a daemon answers while work is still running asserts ordering (the answer arrived before the producer finished), never an elapsed wall-clock bound: the daemon promises no millisecond figure for `status`, so a bound measures the runner, not the product (#531); a request timeout in such a test is a hang guard only.
|
|
23
|
+
- A tight wall-clock bound in a test must be sized for the slowest CI runner, or replaced by a barrier (#559). Hosted macOS shards run each test about 1.5 times slower than the old single leg (and git-heavy tests about 7 times), so three unchanged tests tipped over once each after #954: `test/path-policy.test.ts` (the #541 hardlink loops took 10.6 s idle for three layouts in one test and 27 s on a shard, past the 20 s limit: the loops are now five parts of 1000 iterations per layout, 5000 per layout kept, about 0.8 s per part), `test/code-facts.test.ts` (a 400 ms deadline raced the fake CLI writing its child marker, which takes about 0.3 s idle and 1.4 s at 30-way load: the test now holds the deadline timers by the configured value, waits for the marker and fires the analysis's own deadline callback, a barrier with the real deadline code; because the held deadlines never fire by themselves, its `finally` must `close()` the reader and await the analysis before the fixture is removed, and a second test pins that teardown) and `test/smoke-pty.test.ts` (the macOS PTY bridge process sometimes never gets past `Bun.spawn` with a `terminal` under load: 13 of 60 at 12-way parallel on a Mac, 0 of 60 at 4-way, never for a plain no-op bun script, and a longer timeout cannot help a process that never starts, so a run that timed out with no output is run once more in the tests whose property is not the timeout, after asserting the first attempt's owned process group was confirmed stopped; that is a bounded second attempt, `runStarted`, which only lowers the odds (two hangs in a row still fail) and was measured on local macOS only, and the real fix is in the bridge or in Bun). When a test needs a bound, measure the slow tail under 30-way local load, size the bound from it with the number in a comment, and prefer a readiness signal (a marker file the fake writes before it hangs, with the deadline fired after it) over a shorter deadline or a longer margin: a margin still depends on the runner's speed, a barrier does not.
|
|
19
24
|
- A request that a test expects a stop or shutdown to cut gets its rejection handler where the request is created, never after the awaited teardown: a late `catch` races the reset and the run fails on an unhandled rejection.
|
|
20
25
|
- Run `bun scripts/seeded-check.ts` before reporting the seeded-guard gate complete. After changing seeded guard logic, run its affected seeds even when search-anchor counts are unchanged. Put asynchronous calls inside `resolves`/`rejects` expectations so a mutated refusal fails a named assertion. Keep the `scripts/seeds.json` cases sequential; require the same named test green before its seeded assertion fails. Seed rot, survivors, setup/compiler failures, timeout/watchdog termination and process leaks fail the gate. A change that moves or rewrites a line a `scripts/seeds.json` case searches for updates that case's search and replacement in the same PR (seed rot fails the gate), keeping its mutant effect and named test. Never run the full seed runner from a unit test or mutate the source checkout; inspect preserved failed fixtures before removing them. CI runs it as four sequential Linux shards (`--shard i/4`) beside the ordinary check, separately from `scripts/check.sh`; the required `seeded guards` job passes only when every shard passed, or when the decision job reused a verified identical-tree full gate (`admission == 'reuse'`). Keep that job name: branch protection and `scripts/ci-gates.mjs` read it (#457). On seed rot the checker names the anchor's symbol at a base commit and where it is now (`seedRotContext`): `--base <commit>` sets that base (CI passes the PR base from a `fetch-depth: 0` checkout; keep both), and without it the base is `merge-base origin/main HEAD`. The context is evidence only: it never relaxes the one exact match, and an unavailable base says so instead of guessing.
|
|
21
26
|
- `scripts/lib/ts-ast.ts` is the one TypeScript AST helper for scripts and source-scanning tests (`parseSource`, `callSites`, `symbolSpans`, `enclosingSymbol`, `findSymbol`; inline HTML scripts keep their offsets). It gives lexical locations only, never resolved call edges, and stays dev tooling: never import it from `src/`.
|
|
@@ -30,6 +35,8 @@ Scope: tests, fakes and the test gate. Read before adding or changing a test or
|
|
|
30
35
|
|
|
31
36
|
- A test of `runCheck` (`src/hub/checks.ts`, #558) injects a `CheckSpawn` (argv and options in, `{ pid, exited, signalCode, stdout, stderr }` out) and never patches `node:child_process`: the runner reads a child's exit and pipes as values through `Bun.spawn`, because node's event layer can report a quick child's exit inside `spawn()` before a listener exists under load on macOS (oven-sh/bun#45483). Keep the `ended` fixtures for the already-ended case, and do not raise the 5 s limit or the 1 s kill grace to fix a flake.
|
|
32
37
|
|
|
38
|
+
- A daemon test that writes a config file and then calls `settings_reload` passes `configWatch: false` (`DaemonOptions`, #546), unless the watcher is what it tests. The daemon's file watcher (100 ms poll, 100 ms debounce) publishes the same bytes, so whichever of the two runs second reports `applied: []`, and a watcher pass between a preview and its confirm replaces the confirmation token. Never lengthen a sleep or loosen `applied` for it; the watcher has its own tests in `test/settings-reload-daemon.test.ts`.
|
|
39
|
+
- A daemon test of a tool that reaches a public host passes the host-only transport seam (`DaemonOptions.webFetchTransport`, #553) and asserts what the daemon handed the tool, never a real round trip or a longer wait. The tool, the proxy URL and the allowlist stay the daemon's own; only the request is replaced. The proxy's own behaviour has its tests in `test/proxy.test.ts` and the web-proxy block of `test/web-fetch.test.ts`.
|
|
33
40
|
- Keep CI's Bun version sourced from `.bun-version` through the pinned setup-bun action; update the version file rather than adding per-job version literals.
|
|
34
41
|
- Release verification requires both OS checks and the aggregate `seeded guards` success for an identical tree, a tag matching `package.json` on `origin/main`, and fresh ancestor-bound documentation stamps. Preserve the separate npm OIDC publish job and GitHub Release write job; neither job gets the other's credential authority.
|
|
35
42
|
- `test/conformance/` runs real adapters against fake transports. The shared kit and the transport fakes live in `src/adapters/conformance/` (public `@staix/agent-hub/conformance`); `test/conformance/kit.ts` and the matching `test/fakes/*.ts` only re-export them, so change the packaged file, and `--changed` runs everything for it; `test/conformance/enrollment.ts` enrolls every `ADAPTERS` definition, each `:reviewer` variant and each `:implementer` (`headlessVariant`) variant. Record a case that does not apply in its `UNSUPPORTED` or `UNSUPPORTED_LEGS` map with a concrete reason naming the dedicated coverage, never `test.skip` or synthetic success, and never change native production behavior to satisfy a generic case.
|
|
@@ -41,4 +48,4 @@ Scope: tests, fakes and the test gate. Read before adding or changing a test or
|
|
|
41
48
|
- A daemon test that declares a headless Claude implementer in a repository whose `origin` is on GitHub passes `shellDefaultBranchProbe` (`DaemonOptions`, #845), or the start runs `git ls-remote` against github.com; a test of `readTaskShellDefaultBranch` itself puts a fake `git` first on the `PATH` it passes.
|
|
42
49
|
- A test of `scripts/release-prepare.ts` or `scripts/release-tag.ts` runs the script with its root in a scratch repository whose only remote is a local bare directory: set `AHUB_RELEASE_ROOT` to it, or copy the script into it as `test/release-tag-daemon.test.ts` does. Without that the script takes its root from its own location and fetches and runs `ls-remote` against this checkout's real origin. Consent comes from a fake port, a stand-in `AHUB_CLI` or a scratch daemon, never the person's hub.
|
|
43
50
|
- 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.
|
|
44
|
-
- 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`)
|
|
51
|
+
- 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`) or a named `DATA_FORMAT` entry (`NOT_YET_CONVERTED` is empty since #899; keep it empty). 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/bench.md
CHANGED
|
@@ -76,7 +76,7 @@ ahub bench run ../suites/suite.json --arm baseline --repeat 5
|
|
|
76
76
|
ahub bench run ../suites/suite.json --arm ripwire-on --repeat 5 --tasks b,a,c # --tasks also sets the order
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
-
Only a person can run a suite (agent shells are refused). Each attempt resets the tree to `ref`, runs `setup`, proposes
|
|
79
|
+
Only a person or the configured conductor can run a suite (#933: the hub admits and audits the conductor's run; other agent shells are refused). Each attempt resets the tree to `ref`, runs `setup`, proposes
|
|
80
80
|
the task as the console does, waits until it is approved or `timeout_s` passes, then runs `verify`. Outcomes:
|
|
81
81
|
|
|
82
82
|
- `pass`: approved and `verify` exited 0;
|
package/docs/data-policy.md
CHANGED
|
@@ -35,3 +35,5 @@
|
|
|
35
35
|
- The previous malformed-config fixture that refused every wildcard now refuses an unsupported bracket class; its rejection assertion is retained. New tests establish the deliberately added glob grammar, future paths, wildcard aliases/retargeting, private scopes, discovery failures and actual daemon hook replies. Native account/model consumption is unverified.
|
|
36
36
|
|
|
37
37
|
- Hardlink identity refusal precedes path matching for managed writes, including an already protected original. This keeps the refusal stable across platform-specific real-path alias choices; read access and policy-off behavior remain unchanged.
|
|
38
|
+
- The local worker's opt-in `doc_read` tool (`local.doc_read`, machine-local, off by default) reads a PDF, DOCX or HWPX document through the project's `text_projection` converters. A document under `pii.paths`, or whose text matches `signals.pii_patterns`, is refused to an ordinary turn and readable in a PII task turn. `data.readonly` does not block it (it only reads), the result is bounded and passed through `sanitize()`, and it is never captured to memory.
|
|
39
|
+
- The local worker's opt-in `web_fetch` tool (`local.web_fetch.allow`, machine-local, off by default) is the one place a model's own request can leave the machine, so it is refused for the whole turn of a PII task, whatever the host: the URL or a query could carry PII to the web, and `pii.paths` and `sanitize()` cover what comes back, not what goes out. In an ordinary turn it reaches only the hosts the person listed, over https, through its own egress proxy (never the shell's), as a bare GET with no header or body from the model, re-checking every redirect. What it returns is bounded text passed through `sanitize()`, and neither the page nor the URL is captured to memory.
|
package/docs/dogfood.md
CHANGED
|
@@ -10,26 +10,35 @@ agent-hub is developed with agent-hub. This page lists every feature, whether it
|
|
|
10
10
|
|
|
11
11
|
| Feature | State here | Measured by |
|
|
12
12
|
| --- | --- | --- |
|
|
13
|
+
| `ahub down` (counterpart of `ahub up`) | on: an alias of `ahub kill` for stopping this project's hub | the person stops the hub with `ahub down` and `ahub status` then reports it stopped; test/identity.test.ts keeps it person-only (`console`) and labelled `down`, and test/help.test.ts holds its help entry |
|
|
14
|
+
| Encrypted rooms (#446 X1) | off; source-only crypto/wire, with no broker, room link, invitation CLI or delivery enabled | real WebCrypto vectors and negative wire fixtures; atomic memory fake ports establish ordering only; durable X3 state and the X7 two-machine person receipt remain unverified |
|
|
15
|
+
| Blind room broker (#446 X2) | off; standalone source/fixture work, no public TLS service or hub room link configured here | fake-client auth/fan-out/revocation/restart, ciphertext-only child-process canary, owner admission/offline room reset, queue caps/TTL/FIFO/migration, safe directory modes, and person-only CLI tests; live proxy/account/X7 receipts remain unverified |
|
|
16
|
+
| Room membership and presence (#446 X3) | off; no person room enrollment or exposure configured here | isolated fake-hub/broker tests for OOB enrollment, MITM refusal, durable sequence/replay/grace, explicit fingerprint confirmation/abort, refused-frame retirement, exposure revocation and ids-only presence; room list/status for ids and link state; X4 chat and X7 two-machine live receipt remain separate and unverified |
|
|
13
17
|
| Message bus, digests, priorities | on | `ahub report`: messages, digests, overflow, undeliverable; delivery latency from events |
|
|
18
|
+
| Control-send recipient normalization (#963) | active after release and hub upgrade; this PR does not change the running hub | string recipients reach only the named peer, invalid shapes publish nothing, omitted/empty arrays preserve broadcast; real native recipient behavior remains unverified |
|
|
14
19
|
| Compact board (#724) | on | `ahub board` at 80, 120 and 200 columns: one line per task (test/board-compact.test.ts) |
|
|
15
20
|
| 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
21
|
| Native branch ratchet and host proof ports (#625) | 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; ACP delivery-only/owned/headless admission and generic delivery/stop/form/roster ports; plain-peer daemon tests cover session binding and recovery settlement, builtin context/provenance parity and ACP/channel notices; roster-detached Pi seed proves the recovery fence; Claude launch/generation proof uses the fixed-builtin real channel owner, with legacy hello/facts and non-native generation refusal tests; fixed-builtin frontend proxy/notice/revert ports have plain-peer and native debt/restore parity tests, with the action-policy Codex predicate intentionally retained |
|
|
17
22
|
| Review scope for sandboxed reviewers (#852) | on after the release for the fresh Codex and Kimi reviewers (default `code`); a person may set `review_scope` on an instance in `config.local.json` and restart the hub daemon | `ahub status` shows `review scope` on each reviewer row; a code reviewer's review request carries the one fixed line and no check or CI line (test/review-scope.test.ts); its approval history entry carries `reviewScope: code`; a `changes_requested` with no open finding is recorded `evidenceOnly`, adds nothing to `rejections`, and the conductor gets one ids-only notice (count the `evidenceOnly` history entries against the 7 verification-only rejections seen on #467, #512 and #510, and watch that no task escalates on them) |
|
|
18
23
|
| Headless Claude host shell grants (#845) | source/fixture work; active only after release and hub/plugin upgrade with `ask-when-needed` on the declared implementer | automatic wrapper grants versus person requests; native poisoned-environment and replay receipt; unknown/expired/reused/stale task, branch, root or child tickets execute zero commands |
|
|
24
|
+
| Headless Claude failure stages (task 551, #845) | source and fake-native fixtures; installed/native/account proof unverified until release and restart | fixed stage and host metadata in failed outcomes/logs; private stub, raw-canary absence, stale/foreign launch and bounded-probe cases in `test/claude-failure-stage-daemon.test.ts` |
|
|
19
25
|
| Optional model for a headless Claude implementer (#844) | on after the release and once the person sets `model` on `instances.claude-impl` in `config.local.json` (a hub daemon restart applies it); unset it changes nothing | `ahub status` shows `model source` on the instance row; the native argv carries `--model <value>` on every turn and none when unset (test/claude-implementer.test.ts, test/claude-implementer-daemon.test.ts); a tracked-file or non-implementer declaration is refused or ignored with a reason (test/instances.test.ts, test/jcode-instances.test.ts) |
|
|
20
26
|
| Remote isolated check transport (#793) | off here; fake checks, a bounded local Docker transport receipt and a local run of the lockfile-keyed dependency volume; person opt-in, SSH and the live receipt follow separately | remote tests capability unavailable on failed SSH/Docker/image/transfer probe; one networked install per new lockfile key, none on later runs, the check mounting the volume read-only with no network; 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 |
|
|
21
27
|
| 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 |
|
|
22
|
-
| 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
|
|
28
|
+
| Readable `sdlc` output by default (#814 T1, #899) | on after the release: `ahub sdlc status\|explain\|queue\|release\|metrics\|costs\|authorize-release\|merge` print tables and labelled fields here; since #899 `ahub sdlc merge` (guarded merge, dry-run included) prints its report for a person by default and the JSON document only with `--json`, and `ahub doctor --orphans --kill` likewise prints the stopped pids by default and the hub's reply only with `--json`; scripts pass `--json` | the person reading `ahub sdlc status`, `sdlc metrics` and a `sdlc merge --dry-run` report without `--json`; `test/sdlc-output.test.ts` holds every `sdlc merge` outcome's readable default with `--json` byte for byte the pre-#899 output and the exit code unchanged; `test/conductor-cli.test.ts` holds the same for `doctor --orphans --kill`; `test/cli-json-guard.test.ts` lists the CLI paths that still print JSON by default (since #899 only the named data formats: its not-yet-converted list is empty) |
|
|
23
29
|
| 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) |
|
|
30
|
+
| Conductor-bound recovery continuation and task unassign (task 535, #900) | source/fixture coverage only; whether this repository's running installed daemon advertises the matching `conductor_recovery` feature is unverified | fake source/target authorization, same-actor and policy-mismatch checks, task-unassign actor/suppression fixtures; no native or real-account receipt; same-user key and record replacement remains outside the host boundary |
|
|
24
31
|
| 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`) |
|
|
25
32
|
| 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) |
|
|
26
33
|
| 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 |
|
|
27
34
|
| 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 |
|
|
28
35
|
| 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) |
|
|
36
|
+
| Recurring unmet criteria candidates (#514a) | on after the release: three tasks (at least two separate pieces of work) of one profile and class whose reviews name the same criterion id unmet in 30 days file one backlog card with the `profile_overrides.review_checklist` line as text; the hub applies nothing. Tasks here rarely record a checklist, so cards will be rare. | cards filed and the share a person accepts into their checklist, from the backlog history |
|
|
29
37
|
| 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 |
|
|
30
38
|
| 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 |
|
|
31
39
|
| `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 |
|
|
32
40
|
| 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 |
|
|
41
|
+
| Codex planner sandbox and role visibility (#525-6) | source/fixture coverage after merge and release; six declared planner/implementer/reviewer instances are not configured here, and the person live leg is unverified | fake thread-start/resume read-only confirmation and refusal before adoption; planner-only Claude mutating-tool grants withheld; status form/capable roles/isolation and task planner/holder-reason parity across CLI, console, dashboard and explain; the native six-instance receipt in `docs/smoke.md` is recorded separately when run |
|
|
33
42
|
| Read-time memory code neighbours (#684 phase 4) | off until released; dashboard toggle is opt-in | derived call edges with current SHA; attached fail/decision brief coverage; explicit unknown/truncated results; unchanged authoritative memory log bytes |
|
|
34
43
|
| Canonical memory symbol anchors (#684 phase 3) | off until released; optional `doctor --stale-anchors` report only | actual-symbol completion anchors versus plan guesses; symbol-related earlier notes at memory hop two; stale/unknown/truncated report counts; zero note mutations during inspection |
|
|
35
44
|
| 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 |
|
|
@@ -59,9 +68,12 @@ agent-hub is developed with agent-hub. This page lists every feature, whether it
|
|
|
59
68
|
| Planner capacity and dependency notices (#785 P1a) | source/fixture coverage; active after release and hub upgrade | write-triggered `capacity_released` and `backlog_ready` lines in `Planner work ready:` task envelopes; at most one queued envelope per effective planner, peer/WIP/card ids only; no automatic pull |
|
|
60
69
|
| Release preparation and guarded tag (#787 P3b) | scratch-repo fixtures with a local bare remote; the real history was dry-run in a disposable clone; first real use is the next release | `scripts/release-prepare.ts` run on a `release/<version>` branch and a release PR that carries the version, CHANGELOG, report and stamps; `scripts/release-tag.ts` refusing without the person's consent and pushing one tag after it |
|
|
61
70
|
| Opt-in auto pull (#785 P1c) | source/fixture coverage and a real daemon test; off by default, active only where a person sets `tasks.auto_pull` in `config.local.json` | `backlog pulled` history entries with `by: hub` and an `auto_pull:` note; at most one per capacity release; no pull with the setting off, for unreserved, PII, pinned, frozen or dependency-blocked cards |
|
|
71
|
+
| Opt-in assignment queue backpressure (#800) | off by default; this change enables no live repository policy | `ahub route explain` and status queue-count/age/unknown reasons beside WIP; bounded summary reads, held-offer release, post-await offline/not-attached origin retention with plan-policy handoff, and role-scoped override regressions; no delivery disposition or routing-template change |
|
|
62
72
|
| Per-head CI watcher and `ci_completed` (#785 P1b) | source/fixture coverage with a fake gh; active after release and hub upgrade | one `ci_completed:` task notice per head to owner and reviewer, a premature review re-offered once green; watchers started per `hub_task_done` head and gone at the result, deadline, newer head, verdict or shutdown; no gh read outside a watcher |
|
|
63
73
|
| Plan conformance (#481 S2) | advise | PRs with files outside the plan |
|
|
64
74
|
| Test integrity for bug fixes (#481 S4) | advise | bug PRs without a red-first receipt, modified pre-existing tests |
|
|
75
|
+
| Eval gate for agent configuration (#481 S5) | advise; mechanism only, `eval_check` is not set here until the person chooses a suite (#251, #266), so configuration PRs show `eval check not configured` as a note | configuration PRs with a missing, failing or unreported eval check; recorded pass rate and suite on the approval note |
|
|
76
|
+
| Merge-time PR readback, release metrics and security-scan baseline (#481 S7, AC12) | source and fixture coverage with a fake gh and real train transitions; the readback is on wherever auto merge is `on`, release metrics need shipped trains with recorded history, and the security scan and its bands stay off here until `[security_scan]` and `[bands.security_open_alerts]` are set | `cost` on merge records, `ahub sdlc metrics` review/CI-at-merge rows, the releases row of `ahub sdlc release`, `security_scans` rows and `security_open_alerts` cards |
|
|
65
77
|
| Required checks | advise (`check (ubuntu-latest)`, `check (macos-latest)`, `seeded guards`) | first-pass CI rate, same-head reruns (`hub_sdlc_costs`) |
|
|
66
78
|
| Seed anchor diagnostics (#684) | on in the seeded checker; exact-match failure remains mandatory | missing anchors report the base commit, enclosing symbol and current file/line or explicit unknown; `test/seeded-check.test.ts` |
|
|
67
79
|
| Release authorization (#481 S6) | pending (#199) | releases with a receipt |
|
|
@@ -76,6 +88,15 @@ agent-hub is developed with agent-hub. This page lists every feature, whether it
|
|
|
76
88
|
| amem runtime integration (agent-mem#13 P4) | on after release; no claude-mem worker dependency | no-worker tripwire requests (must be 0), screened capture rows, profile refusals, all-author short-term recall, bounded-view truncation notices and curated-only ask citations |
|
|
77
89
|
| Shared memory through amem, briefs, recall preface | on; library intent briefs and plan guard (#436) take effect after release and hub upgrade; legacy index/timeline reads and worker notices retire in intent mode | notes saved per week, brief hits, fails that prevented a retry; same-input hub/library brief equality, host privacy refusals and front-of-brief plan fails despite the library byte cap or standalone screen |
|
|
78
90
|
| `ahub ask` | on | answers with cited ids |
|
|
91
|
+
| Source registry, `ahub source add\|list` (#508 AC1, AC2, AC5 CLI) | on after release; local (no daemon needed for the person); `add` is the person's or the configured conductor's (#933, admitted and audited by the hub); `hub_source_read` adds bounded, screened snapshots and private stubs; plan validation and the dashboard list are later slices | `ahub source list` rows (id, size, private, origins); `sources/` under the hub state dir; a refusal in the CLI audit for an agent shell |
|
|
92
|
+
| Intake drafts, `ahub intake` (#511 AC1 CLI, AC2 to AC4) | on after release; person-only except `approve`, which the configured conductor may run too (#933); a draft is held until `ahub intake approve`, so nothing changes for work that does not use it; the dashboard text box is a later slice | drafts opened, approved and discarded in `ahub backlog list` and the board history (`draft approved`, `intake unsplit` with its reason); the share of drafts the planner or hub model split without a reason on file |
|
|
93
|
+
| Machine-local schedules (#512 AC1, AC3) | on after release for a person who adds `schedules` to `config.local.json` (`every` `<n>m\|h\|d`, 15 minute minimum); nothing runs without it, and a committed config cannot set it | `ahub status` shows one `schedule <id>` line (next run, last run, task, skipped runs); the backlog cards the hub proposed; no second card while the first is open |
|
|
94
|
+
| Schedule commands and dashboard next run, `ahub schedule list\|run\|pause\|resume` (#512 AC4, AC3 UI) | on after release for a person who configured `schedules`; `run`, `pause` and `resume` are the person's or the configured conductor's (#933), nothing changes for a project with none; the dashboard table is read-only | `ahub schedule list` rows and the dashboard Schedules table (state, next run, last run, task); `paused` and manual runs in the board history of the task a run makes; a pause that is never replayed as a run on resume |
|
|
95
|
+
| Folder triggers, `folder_triggers` (#512 AC2) | on after release for a person who adds `folder_triggers` to `config.local.json`; the first poll records only, nothing runs without it, and a committed config cannot set it | tasks whose first detail line is `Folder trigger: <id>`; `folder_seen` rows and `last_error` in hub.db; sources registered by a trigger in `ahub source list`; the share of new files that produced exactly one task |
|
|
96
|
+
| Jcode peers told their hub tools are deferred MCP tools (#912) | on after release for every Jcode peer; nothing to configure | the first prompt of a Jcode session starts with the guidance; a fresh Jcode v0.99.x peer asked to reply with `hub_send` finds and calls it in its first turn (AC1, a live leg the conductor runs with the person after merge) |
|
|
97
|
+
| Knowledge corpus index, `corpus.paths` (#510 AC1) | not yet reachable: the index is a library, and no command or status line reads it until the ask and brief slice; setting `corpus.paths` in `config.local.json` only validates it | the config is accepted or refused with a reason at load; nothing else is visible yet |
|
|
98
|
+
| On-prem `doc_read` for the local worker (#515 AC2, AC3) | off by default; a project opts in with `local.doc_read: true` and a `text_projection` converter per extension in `config.local.json`, then restarts the local worker; `web_fetch` waits for an owner decision | the local worker lists `doc_read` among its tools; a PII task turn reads a `pii.paths` PDF and an ordinary turn is refused with the reason; the live leg with real PDF, DOCX and HWPX converters on the on-prem worker is the person's |
|
|
99
|
+
| On-prem `web_fetch` for the local worker (#515 AC1, AC3) | off by default; a project opts in with `local.web_fetch.allow` (a short list of hosts) in `config.local.json` and restarts the hub; a PII task turn is always refused; the first run is on one project with a single documentation host | the local worker lists `web_fetch` only when hosts are listed; `network: web_fetch egress proxy` and `network: refused` lines in `hub.log`; a PII task turn that names the tool gets the fixed refusal; the live leg against a real host through the real proxy is the person's |
|
|
79
100
|
| Code structure in the memory graph (#684) | pending | stale anchors, briefs with related notes |
|
|
80
101
|
|
|
81
102
|
## Models, budget and safety
|
|
@@ -84,7 +105,10 @@ agent-hub is developed with agent-hub. This page lists every feature, whether it
|
|
|
84
105
|
| --- | --- | --- |
|
|
85
106
|
| Quota budgets and pauses | on | pauses, resets, time paused (#683) |
|
|
86
107
|
| Token usage per peer and task | `ahub report` and, from the roll-up in `hub.db`, `ahub usage [--by peer\|task\|issue\|pr\|release\|day\|model]`, `usage limits` and `usage total` (#683 U2); console tab `7` (U3) and dashboard Usage view (U4) shipped | tokens per merged PR |
|
|
108
|
+
| Operational settings mechanism (#489 H1) | available after release; this PR does not change this hub's running configuration; planner FYI interval defaults to the existing 10 minutes | shared descriptor/schema/UI/apply parity, independent legacy-default pins, real planner-notice boundary and settings inherit/undo/reload fixtures; native console/dashboard editing remains unverified |
|
|
109
|
+
| Tasks and routing operational settings (#489 H2, task 441) | available after release; this PR does not change this hub's running configuration: the six `tasks.*` settings default to the constants they replace (demotion threshold 1.5, half-life, contradiction window, re-screen cap 10, split minimum 5 observations, split failure share 0.3), and nothing is set in this repository's `config.local.json` | each setting's default equals its old constant (pinned independently of the descriptor), each reader is called by the code that used the constant, `ahub settings set`, inherit and undo for `tasks.demote_at`, and a reload that reaches the next demotion, re-screen and split read; 29 hand-made mutants, none surviving; inherit and undo are not yet looped over all six keys, and no live `ahub settings set tasks.*` against a running hub |
|
|
87
110
|
| Context gate | off: `context.gate = 0` | context-pressure events |
|
|
111
|
+
| Context headroom admission (#802) | off by default: `context.max_assignment_used = 0`; not enabled here by this PR | `ahub route explain` known/unknown context decisions and offered owners; pure/session-freshness, real Tasks/manual override and machine-local config regressions; native live admission remains unverified |
|
|
88
112
|
| 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 |
|
|
89
113
|
| Permission modes | on, person-chosen | approval prompts and their wait times |
|
|
90
114
|
| 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 |
|
|
@@ -92,20 +116,27 @@ agent-hub is developed with agent-hub. This page lists every feature, whether it
|
|
|
92
116
|
| Pi native version gate (#843 AC2) | source/fixture and isolated model-free 1.4.0 receipt; active after release and hub upgrade, Pi stays off this repository's roster | `ahub agents` pi verdict, one version probe on each real launch, rejected versions naming the exact trust command; account-backed inference remains unverified |
|
|
93
117
|
| Local agent CLI drift notice (#843 AC3) | source/fixture and injected-probe daemon test; active on this repository after release and hub upgrade | one planner notice per (id, version), the `agent CLI drift` line in `ahub status`, no repeat after a hub restart |
|
|
94
118
|
| PII routing | on | PII tasks and where they ran |
|
|
119
|
+
| External module-kind instances (#754) | off here: no person-pinned module with an explicit headless-instance opt-in is configured | fake mixed builtin/module starts, current-owner and MCP refusal counters, retained stopped bindings and no-import source/target fences (`test/external-instance-*.test.ts`); native/account receipts remain unmeasured |
|
|
95
120
|
| Multiple Jcode instances (#685) | pending | turns per instance |
|
|
121
|
+
| Jcode usage and context readings (#801) | source/fixture coverage and one isolated live probe of 0.99.1 against a fake gateway (docs/verification/2026-10-28-jcode-0.99.1-usage.md); a real account and provider remain unverified; no native quota reader (Jcode gives no window length) | `ahub status` and the dashboard show `jcode_usage_update` occupancy, `ahub usage` and `ahub report` rows for source `jcode_acp`, and `ahub budget` still says `no reading` for Jcode |
|
|
96
122
|
|
|
97
123
|
## Operations
|
|
98
124
|
|
|
99
125
|
| Feature | State here | Measured by |
|
|
100
126
|
| --- | --- | --- |
|
|
127
|
+
| Screened room chat and person inbox (#446 X4/X5/X5b) | default off here; requires person enrollment/exposure; console panel 8 and dashboard Agents remain metadata-only; person CLI alone can show one inbox body; real two-machine/account leg remains unverified | signed completed (admitted) versus immutable completed (inbox); once-only inbound count, body-free metadata/list JSON, framed/control-stripped person show, done/discard/expiry purge and fixed stored-byte cap/age refusals, post-ack retries; 1,100-row complete-list ceiling; no broadcast/roles/board/body telemetry, key/storage creation from status or mixed-version success claim; existing restart/binding/disposition regressions |
|
|
101
128
|
| Dogfood scorecard (#687) | on after release; read-only weekly receipt | `ahub report --dogfood --since 7d --json`; bounded evidence and explicit unknowns |
|
|
129
|
+
| Claim verdicts and fact_check provenance (#901) | source/fixture coverage; the live research pilot (a person entering verdicts with `ahub task claim`) remains unverified | `ahub report --dogfood` research rows: `claims.reachedPersonVerdict`, per-slot `personVerdict`/`modelLabel`/`agreement`, `factCheck.selection`/`passed`/`failed`/`missingKeysPerRun`; null until a verdict or a keyed check is recorded |
|
|
130
|
+
| Dirty-submodule notices (#798) | source/fixture coverage; the live writing/general pilot (a workspace with 26 submodules) remains unverified | `ahub turns` shows `<path> (changed, contents unobserved)` for a turn that only edited a submodule; the D1 notice at `hub_task_propose` and `hub_task_accept`; `ahub undo` names what it cannot restore; a project without `.gitmodules` makes the same git calls per turn (counting-shim test) |
|
|
102
131
|
| 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 |
|
|
132
|
+
| Domain packs (#318, #513a) | off here: this repository has no pack in `.agenthub/roles`, so `ahub init --profile <pack>` is not used on it; live adoption in another project is unverified | the `ahub init --profile <pack>` preview and apply, the pack validator and the shipped-pack catalogue, covered by test/packs.test.ts (40 mutants killed); adoption would show as a `.pack.json` under `.agenthub/roles` and a `profile` in `.agenthub/config.json` of the adopting project, read from `ahub init --profile` runs. Runnable checks stay display-only (#513b needs its own reviewed runner) |
|
|
103
133
|
| 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) |
|
|
104
134
|
| 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 |
|
|
105
|
-
| Conductor operational CLI (#812 T2) | source/fixture coverage; active after release | peer-attributed effect admissions, authority/domain-error separation, delayed-handshake and incomplete-output CLI tests, cached privacy projections and revocation checks |
|
|
135
|
+
| Conductor operational CLI (#812 T2, #899, #933) | source/fixture coverage; active after release; `ahub doctor --orphans --kill` from the conductor's shell prints the stopped pids for a person and the hub's reply with `--json`; #933 adds board import, intake approve, schedule run/pause/resume, research backfill, bench run, source add, goal link and `tasks.escalate_after`, and lets any agent cancel, label or re-role its own task and list the deliverables it may see | peer-attributed effect admissions, authority/domain-error separation, delayed-handshake and incomplete-output CLI tests, cached privacy projections and revocation checks; `test/conductor-cli.test.ts` holds the orphan cleanup's readable default and `--json` byte equality; `test/console-class-933.test.ts` holds one admission row per #933 form, the operational refusal for another agent, own-task cancel and role, and the screened deliverables list; metric: `agent_cli` rows by label in `events.jsonl` and the person-only refusals that remain |
|
|
106
136
|
| 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 |
|
|
107
137
|
| 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 |
|
|
108
|
-
| 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
|
|
138
|
+
| Guarded merge and result recording (#786 T1/T2, #899) | `auto_merge = "dry-run"` first in this repository's `.agenthub/sdlc.toml`; `on` after one dry-run report matches a manual merge; the report prints for a person by default and as a document with `--json` | `ahub sdlc merge <task> --dry-run` reports the mode, the decision, each refusing gate and the `gh` command it would run, readable without `--json`; `--json` is byte for byte the view printed before #899 (`test/sdlc-output.test.ts`); 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 |
|
|
139
|
+
| Peer `DOING` column (#878) | on after the release for Claude sessions started with `ahub claude` and the Codex TUI; ACP peers show it only when their agent sends a plan | `ahub status` shows a title or current item for an active Claude and Codex peer without a hub tool call; `[private]` during PII work; the column disappears when no peer has a value |
|
|
109
140
|
| 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 |
|
|
110
141
|
| Usage view (#683) | CLI (`ahub usage`, U2), console Usage tab `7` (U3) and dashboard Usage view (U4) shipped | its own panels |
|
|
111
142
|
| 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 |
|
|
@@ -115,7 +146,9 @@ agent-hub is developed with agent-hub. This page lists every feature, whether it
|
|
|
115
146
|
| Upgrade and recovery | on, per release | upgrade receipts in `docs/verification/` |
|
|
116
147
|
| Agent CLI version policy and trust (#691) | on after the release: Codex 0.163.0 verified by the [2026-10-22 receipt](verification/2026-10-22-codex-reviewer-isolation.md) and 0.164.0 by the [2026-10-24 receipt](verification/2026-10-24-codex-0164-reviewer-isolation.md); Kimi 2.3.0 and Qwen 0.26.0 verified by the [model-free receipt](verification/2026-10-22-kimi-qwen-compat.md), Kimi 2.3.1 by the [2026-10-25 model-free receipt](verification/2026-10-25-kimi-231-reviewer-compat.md) and 2.3.2 by the [2026-10-26 one](verification/2026-10-26-kimi-232-reviewer-compat.md), with account/provider live legs unverified; agy 1.4.4 in-line | model-free isolation receipts, `ahub agents` verdicts, versions trusted per machine, lead time from an `agent-compat` issue to a receipt |
|
|
117
148
|
| Qwen's own TUI over dual output (#286 Part 3) | off here: this repository's hub runs no Qwen peer (roster.qwen is off), and the limits (a hub deny ends the turn without an answer; a stalled turn is stopped only with Esc in the Qwen terminal) are for the person at that terminal to accept. Verified on Qwen 0.26.1 under an isolated HOME by the [2026-10-25 receipt](verification/2026-10-25-qwen-dual-output-t0.md); the live account leg is the person's | `test/qwen-dual.test.ts` against a fake dual-output Qwen; in real use `ahub status` stalled/detached notices per peer and the `needs_review` count of Qwen deliveries (an Esc or an exited terminal ends a turn that way; a deny completes) |
|
|
118
|
-
| Benchmarks (#251, #637) | off: `bench.enabled = false
|
|
149
|
+
| Benchmarks (#251, #637) | off: `bench.enabled = false`. The task-routing gate (#637 part 1) is on in CI for every PR: `routing-receipt` fails a change to `templates/routing.toml` without a matching receipt; the model-routing gate (#637 part 2) is the second step of the same workflow: `scripts/check-model-routing-receipt.ts` fails a change to the model-routing keys without a matching `-modelrouting-` receipt, and wants a by-hand `--live` half when an alias or judge moves | bench receipts; `docs/verification/*-routing-*.md` and `*-modelrouting-*.md` receipts; a `routing-eval` check run on every PR with `eval_pass_rate=` and `eval_suite=` in its title (name it as `eval_check` to make it the eval gate); `bun scripts/benchmarks/routing-gate.ts` and `model-routing-gate.ts` print the default's numbers (100% on both splits); the live half (served model ids) has not been run against a gateway yet |
|
|
150
|
+
| Disposable smoke runner (#787 Q2) | off on this repository: it refuses this checkout; use a new temporary fixture; real-account Q2 remains unverified | sanitized receipt checks/counts/timings, zero owned survivors on timeout; fake transport tests are separate |
|
|
151
|
+
| Plain corpus evidence in ask and task briefs (#510b) | off here until a person configures plain `corpus.paths`; vaults remain refused; no corpus body is written to amem | `note:<path>#heading` ids in ask evidence or public task briefs; PII/deny and stale-context refusal regressions |
|
|
119
152
|
|
|
120
153
|
## Decisions
|
|
121
154
|
|
package/docs/events.md
CHANGED
|
@@ -40,7 +40,7 @@ marked `private: true`, and PII tasks `pii: true`.
|
|
|
40
40
|
| `turn_start` | `peer`, `turn` (`<peer>#<hub run>.<n>`, unique across restarts). A turn follows the adapter: pausing a busy peer does not end it |
|
|
41
41
|
| `turn_end` | `peer`, `turn`, `ms`, `tokens` (when the adapter reported any during the turn), `files` and `snapshotMs` (when snapshots are on: how many files the turn changed, and the time both snapshots took); optional numeric `task`, `attribution` (`delivery`, `single_open`, `unattributed`) frozen at turn start, `pii: true` for an attributed PII task |
|
|
42
42
|
| `tokens` | `peer`, `n` (tokens added since the previous report), optional numeric `task`, `attribution` (`delivery`, `single_open`, `unattributed`), `pii: true` for an attributed PII task |
|
|
43
|
-
| `usage` | `peer`, `source` (`omniroute`, `claude_transcript`, `codex_appserver`, `kimi_wire`), opaque `id`, optional `measuredAt` (provider/source time), the `requestedModel`, `servedModel` and `provider` labels, and any provider-reported counters (`inputTokens`, `outputTokens`, `cacheReadTokens`, `cacheWriteTokens`, `reasoningTokens`, `totalTokens`); optional numeric `task`, `attribution` (`delivery`, `single_open`, `unattributed`), `pii: true` for an attributed PII task. Missing counters stay unknown. |
|
|
43
|
+
| `usage` | `peer`, `source` (`omniroute`, `claude_transcript`, `codex_appserver`, `kimi_wire`, `jcode_acp`), opaque `id`, optional `measuredAt` (provider/source time), the `requestedModel`, `servedModel` and `provider` labels, and any provider-reported counters (`inputTokens`, `outputTokens`, `cacheReadTokens`, `cacheWriteTokens`, `reasoningTokens`, `totalTokens`); optional numeric `task`, `attribution` (`delivery`, `single_open`, `unattributed`), `pii: true` for an attributed PII task. Missing counters stay unknown. |
|
|
44
44
|
| `task` | `id`, `event` (the board history event, e.g. `proposed`, `assigned`, `done`, `check failed`, `blocked`, `ready`), `by`, `state`, `owner`, `reviewer`, `class`, `pii`, and `reason` when the board recorded a move reason (`manual`, `budget`, `offline`, `idle`, `rejections`, `declined`, `inference_failed`, `delivery_failed`) |
|
|
45
45
|
| `overlap` | `task`, `owner`, `others` (`task`, `owner`, `paths`, and `symbols` when plans name the same symbol; a name that matches a PII pattern is left out, so either list can be empty), the structured twin of the console notice |
|
|
46
46
|
| `permission_mode` | `peer`, `from`, `to` (`ask`, `ask-when-needed`, `never-ask`): a person set a peer's permission mode (`from` equals `to` when it was already in force) or confirmed a never-ask default (issue #242) |
|
|
@@ -92,6 +92,7 @@ Token usage by adapter:
|
|
|
92
92
|
- The local worker records optional counters returned by OmniRoute. Requested route/model and gateway-reported served model/provider are separate fields; an alias is never treated as a served model.
|
|
93
93
|
- `ahub report` deduplicates usage records by peer, source and id. Coverage counts distinguish calls with provider usage from calls where usage was absent. Token counters are provider-reported values; the report never derives a price or treats missing spend as zero. Estimated price and measured provider spend remain unknown unless a future source reports them.
|
|
94
94
|
- Usage telemetry has no prompt, completion, task text, credential, Access header, session id or transcript path.
|
|
95
|
+
Jcode (#801): each prompt whose result carries a well-formed `usage` writes one `usage` event with `source: jcode_acp`, `inputTokens`, `outputTokens`, `cacheReadTokens` and `cacheWriteTokens` when present, and task attribution; never `totalTokens` (Jcode's adds the cached read to an input that already holds it for OpenAI-style providers), never a `tokens` event, and no session id, text or path. The id names the hub run, the peer and a per-run sequence.
|
|
95
96
|
|
|
96
97
|
## The usage roll-up (#683)
|
|
97
98
|
|
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.13.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
|
|
|
@@ -100,7 +100,7 @@ A running hub syncs 5 seconds after start and every `memory.sync_interval_min` m
|
|
|
100
100
|
- **Session-start recall**: this project's long-term decisions, contracts and fails (and shared ones) lead the recall block; display-only filtering omits automatic approval notes, while changes-requested notes show their nonempty reasons. Lines preserve their headers and end at word boundaries with a visible marker when capped; each delivery deduplicates memory ids across prefaces and note lines. Stored notes and brief evidence are unchanged. Recent short-term amem curated notes follow; each gets half of `memory.inject_tokens` when both answer.
|
|
101
101
|
- **`ahub ask`**: entries that match the question and the graph neighbourhood of what they are about are evidence, cited
|
|
102
102
|
as `[mem:<8 hex>]` for existing native projections or `[mem:<32 hex>]` for full entry references. A question that carries PII skips them. `ahub ask --remember` writes a pending candidate, never curated evidence; a person must review it.
|
|
103
|
-
- **`ahub memory`** needs no running hub. The hub CLI identity gate allows screened list/search/show reads in agent shells; graph and unscreened views are a person's. Managed peers use the bounded `hub_memory_search`, `hub_memory_show` and `hub_memory_graph` tools through the daemon, with project scope, `memory_read`, PII-turn refusal and capture exclusion:
|
|
103
|
+
- **`ahub memory`** needs no running hub. The hub CLI identity gate allows screened list/search/show reads in agent shells; graph and unscreened views are a person's, and so are changes: `promote`, `demote`, `forget`, `supersede`, `rebuild`, `machine --label` and the `goal` changes other than `link` stay person-only because the pinned amem (0.12.0) records their actor as the user (#933). The upgrade path is agent-mem 0.13.0 attribution (agent-mem#143) after the pin bump in #958, once the hub passes an agent actor. `ahub goal link` is also the configured conductor's: the hub records the peer as the link's actor. Managed peers use the bounded `hub_memory_search`, `hub_memory_show` and `hub_memory_graph` tools through the daemon, with project scope, `memory_read`, PII-turn refusal and capture exclusion:
|
|
104
104
|
|
|
105
105
|
```bash
|
|
106
106
|
ahub memory # this project's entries, newest first, with tier counts
|
|
@@ -119,7 +119,7 @@ ahub memory graph 1a2b3c4d --hops 2 # its neighbourhood as edges, a person'
|
|
|
119
119
|
|
|
120
120
|
`list`, `search` and `show` may run in agent shells, so they leave out notes that match the project's current PII
|
|
121
121
|
patterns or are about a path its `pii.paths` makes private (a shared note was accepted under another project's patterns) and say how many; the dashboard shows a person
|
|
122
|
-
everything. A person's, from a terminal (agent shells are refused):
|
|
122
|
+
everything. A person's, from a terminal (agent shells are refused, the conductor's too, until the hub passes an agent actor to amem):
|
|
123
123
|
|
|
124
124
|
```bash
|
|
125
125
|
ahub memory promote 1a2b3c4d # a short-term entry gets a long-term copy
|
|
@@ -254,3 +254,9 @@ Task briefs merge direct and neighbour-attached fail/decision notes first, dedup
|
|
|
254
254
|
Code-neighbour reads are optional work: review and completion queries take priority, interrupt an in-flight optional CLI and wait for its owned cleanup before starting. Optional unknown results are retained for five seconds; known topology is cached for thirty seconds, with a shared five-second minimum between derive starts. Current attached notes are refreshed and screened at publication, so this cache does not retain a stale note selection. Direct path/title/symbol advice is independent of code eligibility or availability and keeps its existing fail-open behaviour.
|
|
255
255
|
|
|
256
256
|
Only an open, visible Memory view requests dashboard code work. Console task detail uses a cache-only overlay lookup and returns unknown on a cold cache, without starting a code query. A derive makes one hardened Git HEAD probe; cached publication checks bounded Git-owned HEAD/ref files instead of spawning Git on every poll. Unsupported ref layouts or packed-ref inventories above 1 MiB remain unknown. Dashboard note filters apply to neighbour notes as well, and selecting a non-symbol resets the toggle. Stored symbol nodes retain their authored identity when derived edges are added.
|
|
257
|
+
|
|
258
|
+
## Plain corpus evidence
|
|
259
|
+
|
|
260
|
+
Machine-local `corpus.paths` can supply public `note:<path>#heading` evidence to `ahub ask` and task briefs. It is separate from durable amem notes: the hub writes no corpus body into the index or store, never reads an Obsidian vault from disk, and applies deny/`pii.paths` before reads and full-note screening before excerpts. Native memory counts exclude corpus rows. Existing brief item caps and priority fail notes remain in force.
|
|
261
|
+
|
|
262
|
+
PII questions and private task contexts do not query the corpus or shared observers. PII ask inference requires a positive `onCampus()` result, the pinned local fixed model and `onCampusOnly: true` through dispatch; unknown locality returns evidence without a model answer. Task/privacy/policy changes across awaits discard a brief before observation. Ask answers recheck the PII and note-PII verdicts of their gathered task rows and the caller-private context before dispatch and publication; unrelated board activity does not discard an answer. Corpus paths may be enabled with memory off; they are never exported as persistent memory automatically.
|