@staix/agent-hub 0.14.0 → 0.16.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 +42 -0
- package/README.md +23 -9
- package/docs/agent-notes/README.md +8 -0
- package/docs/agent-notes/adapters.md +21 -4
- package/docs/agent-notes/budget.md +6 -4
- package/docs/agent-notes/bus.md +1 -0
- package/docs/agent-notes/daemon.md +20 -9
- package/docs/agent-notes/local-worker.md +2 -2
- package/docs/agent-notes/tasks.md +21 -4
- package/docs/agent-notes/tests.md +6 -3
- package/docs/dogfood.md +19 -5
- package/docs/events.md +21 -7
- package/docs/memory.md +78 -10
- package/docs/operations.md +197 -39
- package/docs/profiles.md +1 -0
- package/docs/quickstart.md +16 -6
- package/docs/sdlc.md +17 -0
- package/docs/security.md +110 -21
- package/docs/smoke.md +7 -3
- package/docs/specs/2026-09-19-agent-hub-design.md +156 -16
- package/docs/verification/2026-10-23-0.15.0.md +80 -0
- package/docs/verification/2026-10-23-daemon-responsiveness.md +44 -0
- package/docs/verification/2026-10-23-headless-claude-permissions.md +47 -0
- package/docs/verification/2026-10-23-terminal-claude-execution-root.md +41 -0
- package/docs/verification/2026-10-24-always-ask-adapters.md +52 -0
- package/docs/verification/2026-10-24-amem-runtime.md +69 -0
- package/docs/verification/2026-10-24-codex-0164-reviewer-isolation.md +55 -0
- package/docs/verification/2026-10-24-delivery-holds-ac8.md +264 -0
- package/docs/verification/2026-10-24-hardcoded-values.md +2108 -0
- package/docs/verification/2026-10-25-0.16.0.md +82 -0
- package/docs/verified.json +40 -19
- package/package.json +2 -2
- package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-hub/server.js +3109 -2046
- package/src/adapters/acp.ts +46 -7
- package/src/adapters/claude-channel.ts +26 -4
- package/src/adapters/claude-implementer.ts +243 -0
- package/src/adapters/claude-print-child.ts +90 -0
- package/src/adapters/claude-task-sessions.ts +80 -0
- package/src/adapters/codex-appserver.ts +69 -1
- package/src/adapters/compat.ts +6 -2
- package/src/adapters/conformance/enrollment.ts +2 -1
- package/src/adapters/conformance/fakes/acp-server.ts +3 -1
- package/src/adapters/definitions/builtin-metadata.ts +17 -5
- package/src/adapters/definitions/native-channel-state.ts +1 -1
- package/src/adapters/definitions/native-cli-entry-claude.ts +13 -8
- package/src/adapters/definitions/native-cli-entry-types.ts +2 -0
- package/src/adapters/definitions/native-factories.ts +8 -0
- package/src/adapters/kimi-wire-usage.ts +303 -0
- package/src/adapters/local-worker.ts +5 -4
- package/src/adapters/registry-types.ts +8 -4
- package/src/adapters/registry.ts +14 -2
- package/src/adapters/reviewer-print.ts +30 -53
- package/src/cli/console-config.ts +123 -0
- package/src/cli/console-state.ts +48 -3
- package/src/cli/console-symbols.ts +3 -2
- package/src/cli/console-usage.ts +5 -2
- package/src/cli/console.ts +30 -2
- package/src/cli/facts-hook.ts +9 -5
- package/src/cli/help.ts +10 -7
- package/src/cli/identity.ts +3 -1
- package/src/cli/launch.ts +10 -4
- package/src/cli/main.ts +91 -19
- package/src/cli/memory.ts +119 -7
- package/src/cli/output.ts +40 -4
- package/src/cli/reset.ts +1 -1
- package/src/cli/settings-edit.ts +89 -0
- package/src/cli/terminal-recovery.ts +1 -6
- package/src/hub/action-class.ts +211 -17
- package/src/hub/action-enforce.ts +90 -0
- package/src/hub/action-policy.ts +40 -17
- package/src/hub/ask.ts +16 -4
- package/src/hub/board.ts +110 -31
- package/src/hub/budget-notes.ts +65 -0
- package/src/hub/budget.ts +44 -7
- package/src/hub/bus.ts +6 -6
- package/src/hub/claude-launch-proof.ts +33 -0
- package/src/hub/code-facts.ts +56 -16
- package/src/hub/conductor.ts +5 -2
- package/src/hub/config-apply.ts +12 -0
- package/src/hub/config-edit-host.ts +173 -0
- package/src/hub/config-edit.ts +282 -0
- package/src/hub/config-tier.ts +89 -0
- package/src/hub/control-client.ts +24 -3
- package/src/hub/daemon.ts +614 -104
- package/src/hub/deliverable-files.ts +131 -0
- package/src/hub/deliverables.ts +125 -0
- package/src/hub/delivery-journal.ts +18 -2
- package/src/hub/envelope.ts +1 -1
- package/src/hub/events.ts +5 -3
- package/src/hub/hub-tools.ts +22 -6
- package/src/hub/instances.ts +7 -4
- package/src/hub/kimi-usage.ts +156 -0
- package/src/hub/lifecycle.ts +10 -5
- package/src/hub/manager.ts +1 -1
- package/src/hub/memory-code-head.ts +44 -0
- package/src/hub/memory-code-overlay.ts +119 -0
- package/src/hub/memory-plan-guard.ts +10 -1
- package/src/hub/memory-stale-anchors.ts +159 -0
- package/src/hub/memory-symbols.ts +74 -0
- package/src/hub/peers.ts +7 -0
- package/src/hub/permission-payload.ts +15 -0
- package/src/hub/planning.ts +10 -4
- package/src/hub/read-tools.ts +21 -3
- package/src/hub/routing.ts +118 -3
- package/src/hub/sdlc.ts +34 -0
- package/src/hub/settings.ts +18 -3
- package/src/hub/settled-roles.ts +15 -3
- package/src/hub/task-sweep.ts +25 -3
- package/src/hub/tasks.ts +551 -75
- package/src/hub/token-window.ts +46 -0
- package/src/hub/usage-rollup.ts +16 -6
- package/src/hub/window-kind.ts +13 -0
- package/src/hub/workflow.ts +16 -0
- package/src/local/tools.ts +5 -5
- package/src/memory/brief.ts +22 -12
- package/src/memory/capture.ts +26 -40
- package/src/memory/client.ts +137 -65
- package/src/memory/recall.ts +13 -22
- package/src/ui/index.html +215 -19
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Task board and hub tools
|
|
2
2
|
|
|
3
|
-
Scope: the task flow, assignment, the state machine and the hub's MCP tools. Read before editing `src/hub/tasks.ts`, `src/hub/board.ts`, `src/hub/routing.ts`, `src/hub/task-sweep.ts`, `src/hub/settled-roles.ts`, `src/hub/conductor.ts`, `src/hub/supervision.ts`, `src/hub/tool-refusal.ts`, `src/hub/hub-tools.ts`, `src/hub/review-brief.ts`, `src/hub/reviewer-pool.ts`, `src/hub/reviewer-pool-config.ts`, `src/hub/release-admission.ts`, `src/hub/plan-conformance.ts`, `src/hub/sdlc-metrics.ts` or `src/hub/
|
|
3
|
+
Scope: the task flow, assignment, the state machine and the hub's MCP tools. Read before editing `src/hub/tasks.ts`, `src/hub/board.ts`, `src/hub/routing.ts`, `src/hub/task-sweep.ts`, `src/hub/settled-roles.ts`, `src/hub/conductor.ts`, `src/hub/supervision.ts`, `src/hub/tool-refusal.ts`, `src/hub/hub-tools.ts`, `src/hub/review-brief.ts`, `src/hub/reviewer-pool.ts`, `src/hub/reviewer-pool-config.ts`, `src/hub/release-admission.ts`, `src/hub/main-checkout.ts`, `src/hub/checks.ts`, `src/hub/task-repository.ts`, `src/hub/review-findings.ts`, `src/hub/plan-conformance.ts`, `src/hub/sdlc-metrics.ts`, `src/hub/no-writes.ts`, `src/hub/workflow.ts`, `src/hub/deliverables.ts` or `src/hub/deliverable-files.ts`.
|
|
4
4
|
|
|
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.
|
|
@@ -10,16 +10,20 @@ Scope: the task flow, assignment, the state machine and the hub's MCP tools. Rea
|
|
|
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.
|
|
12
12
|
- A named owner that `assign()` refuses is never a silent no-op (#601): `assignTo` passes `refuseNamed` so `assignOwner` throws with the routing refusal reason before any write (pre-effect for tool callers, a plain `Error` for the console), and a proposal's unmet named owner is reported in the propose result through `namedOwnerNote`, read from the stall the proposal recorded. The task stays where it was; never deliver to an offline peer to make the name stick.
|
|
13
|
+
- Write-class owners (#735): `ownerRoleOfClass` (`src/hub/routing.ts`) is the one derivation of the role that owns a class (`plan` planner, `review` reviewer, everything else implementer); the role ceiling and the owner-admission gate both use it, so never test a fixed list of class names; `explain` and `assign` stay the same function.
|
|
13
14
|
- The state machine allows `in_progress -> approved` only for classes without a reviewer; `review()` checks `in_review` itself.
|
|
14
15
|
- Treat `cancelled` as closed through `isClosedTaskState`, but keep dependency release restricted to approval. Cancellation goes through `Tasks.cancel`, preserves the row and roles, clears its reservation/cohort, sends revision-bound stop notices and writes no outcome memory; never map it to approval or cancel a native turn or worktree. Planner cancellation stays limited to its own historically unaccepted proposed/backlog work; accepted work remains person-only.
|
|
15
16
|
- Capture `TaskOffer.pullOnly` and the exact offer envelope id at publication. Keep unread-offer attention independent of attachment and restart until acceptance, decline, supersession or `offerRead` evidence; never replace that evidence with a generic inbox timestamp or completion state.
|
|
16
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.
|
|
17
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.
|
|
18
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`. 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
|
+
|
|
19
22
|
- Completed-change notices (#713, one per recipient per done, accepted work only, superseding by `completedFor`): rule in `bus.md`.
|
|
20
23
|
- 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.
|
|
21
24
|
- Task code symbols (#684 2a) are host-derived: stored only in the `task_code_symbols` table and read through `TASK_SELECT` (`src/hub/board.ts`), so a `SELECT * FROM tasks` reader never exports them. `Board.update` writes `codeSymbols` only with `HOST_CODE_SYMBOLS` and drops it when the head (`refs.commit`, else `sdlc.head`, else `sdlc.localHead`), `refs.repo`, `refs.paths` or `sdlc.repo` changes or the task turns private. Tasks observes them only inside an admitted `done (checking)` or `done` write (`observedCodeSymbols`: non-PII, a full commit, the project repository); a failed or mismatched observation is unknown, never a refusal. Optional code facts (`prepareReviewBrief`) skip PII tasks, never refuse a review, and the actor guard runs again after them.
|
|
22
25
|
- Public reads of code symbols go through `publicTaskCodeSymbols` (re-screened, no `repositoryKey`). `taskCodeSymbolsDisjoint` only labels a completed-change notice: never suppress a notice or gate on it. `symbolOverlaps` is console detail only: it persists no pair and drops any task marked `pii` or `pii_path`, even with a routing opt-out.
|
|
26
|
+
- Completion-note symbol anchors (#684 phase 3, `completionMemorySymbols`): anchor a done note only on the current head's screened host observations (`publicTaskCodeSymbols`, this project's repository) that `TasksDeps.memorySymbolNodes` positively confirms; never on plan symbols, and nothing for a PII or foreign task. An unknown observation writes no anchor and the note labels its coverage unknown; earlier notes stay immutable, and missing evidence never refuses done. Read-time code advice (graph neighbours, briefs) asks `memoryCodeEligible` (non-PII, this project's repository, no `refs`/`plan` repository mismatch).
|
|
23
27
|
- `.agenthub/routing.local.toml` (#269) may hold only `stay_switch` and per-class `peers`, `escalate_to`, `route` and `pi_backend` (`checkOverlay` in `src/hub/routing.ts`): never widen it to targets, routes, signals or constraints, which stay in the hand-written `routing.toml`. `parseRouting` is the one merge, and `assign()` reads `routing.sources` only for its trace line.
|
|
24
28
|
- Conductor authority requires the explicit unique role before capability checks. The only exceptions run before that check in `Conductor.execute` and stay task-scoped: a task's current owner and reviewer read it with `hub_task_show` (#208; a reviewer-backend peer only during its bound review, rule below), and its proposer redirects it with `hub_task_assign` while nobody ever accepted it and no person moved it (#207). Removing them strands reviewers that missed a review request. Assignments preserve the acting peer. A peer releases only its own conductor hold; operator release and manual, quota and recovery holds remain separate paths.
|
|
25
29
|
- The PII screen (#198) runs in `propose` before anything else reads the task (no board row, envelope, triage or brief before its verdict), and `unknown` is PII. A call that screens free text (`done`, `review`, `remember`) screens only after its own checks pass, and `done` and `review` run them again after the awaited screen, because the task can change meanwhile. A history entry marked `withheld` stays withheld in public views, completed notices and review requests, whatever `nameable()` says; `ahub ask` shows it only on campus, like a note that matches a PII pattern.
|
|
@@ -30,7 +34,7 @@ Scope: the task flow, assignment, the state machine and the hub's MCP tools. Rea
|
|
|
30
34
|
|
|
31
35
|
- Pass off-roster ids as data into `assign()` so its trace and every owner/reviewer choice share the membership filter. Refuse a manual off-roster assignment before a board write, including a reservation. Use `reassignForRoster` through the existing assignment delivery path on removal, including in-review ownership while keeping its review state; keep the old owner or reviewer when no eligible replacement exists and emit the guarded public-title notice. Preserve budget-relay behavior when sharing that implementation.
|
|
32
36
|
|
|
33
|
-
- Criteria checklists (`criteria`, retired alias `phase`) are written only through Tasks.recordLifecycle by that task's reviewer/person, after the PII screen. Refuse a checklist task list before any write; never change dependencies, accepted owners or reservations from a checklist. Keep verified checklist claims separate from externally read CI, and bind them to the current reviewed head. Screen `checklistOf(next)`, never `next.criteria` alone: off stores a phase write under `phase`. Plain off (no retired `workflow`) keeps the #257 receipt bytes, the schema-1 export and the `phase` tool schema; advertise `criteria` only when daemon status carries `lifecycleCriteria`. Export only derived sanitized board/config projections.
|
|
37
|
+
- Criteria checklists (`criteria`, retired alias `phase`) are written only through Tasks.recordLifecycle by that task's reviewer/person, after the PII screen. Refuse a checklist task list before any write; never change dependencies, accepted owners or reservations from a checklist. Keep verified checklist claims separate from externally read CI, and bind them to the current reviewed head. Screen `checklistOf(next)`, never `next.criteria` alone: off stores a phase write under `phase`. Plain off (no retired `workflow`) keeps the #257 receipt bytes, the schema-1 export and the `phase` tool schema; advertise `criteria` only when daemon status carries `lifecycleCriteria`. Export only derived sanitized board/config projections. A verdict's `unmetCriteria` (#507b) comes only from `unmetCriteriaIds` (`src/hub/workflow.ts`) over the board's checklist at the verdict write, never from the caller, and stays empty for a PII task.
|
|
34
38
|
|
|
35
39
|
- A planner patch that names `reserved` at all, the same value included, is refused while `hasPersonReservation` holds (#378).
|
|
36
40
|
- A planner pull of a reserved backlog task (#718) admits only that exact peer, whoever reserved it: refuse when it is excluded, ineligible or at its planner WIP cap, never fall back to another peer. It goes through `assignOwner` with `plannerPull`, whose `guard` rechecks authority, revision and owner right before `Board.pullAssigned` writes `backlog pulled` and the assignment in one transaction; a stale prior offer is withdrawn, never reused, and the pull is not undoable through planning. An unreserved pull stays the ownerless `Board.pullPlanned` move.
|
|
@@ -48,7 +52,7 @@ Scope: the task flow, assignment, the state machine and the hub's MCP tools. Rea
|
|
|
48
52
|
- 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`.
|
|
49
53
|
- 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`.
|
|
50
54
|
- 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.
|
|
51
|
-
- Admit a declared extra as owner of
|
|
55
|
+
- 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.
|
|
52
56
|
|
|
53
57
|
- 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.
|
|
54
58
|
- 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.
|
|
@@ -58,7 +62,7 @@ Scope: the task flow, assignment, the state machine and the hub's MCP tools. Rea
|
|
|
58
62
|
- Cost reports recheck planner/private-turn authority and current linked privacy before each underlying external read and final projection. Do not emit a stale/relinked observation as a complete zero-cost row. S7 owns band configuration and automatic proposal writes.
|
|
59
63
|
|
|
60
64
|
- `Tasks.review` captures `reviewAuthority` once and rechecks it in `valid()` after every await, right before each write; a headless reviewer's verdict, and its `hub_task_show` (checked before and after the read), holds only while the same child runs for that task at its bound envelope head and review revision (`Tasks.reviewBindingCurrent`, host-only: `in_review`, same reviewer, non-PII, matching `reviewRequestRevision` and review head). Never cache the answer (#223, #533).
|
|
61
|
-
- 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 read only from hub-proposed `plan` cards (first history entry by `hub`), without editing instruction files.
|
|
65
|
+
- Keep structured review findings in verdict history, bound to the reviewed head. Validate bounded reports and person-only accepted dispositions before effects; recheck authority, privacy and head identity after awaits. A supplied `head` that differs from the task's resolved head (`findingHead`) is refused pre-effect, and the resolved head is part of the identity rechecked after the screen (#657). Key findings by `id` alone, never by pass (`findingsForHead`): a nit or open reclassification never discharges an Important, only an explicit disposition does. Omitted IDs never settle findings; carry open Important across heads until explicitly dispositioned, while nits stay head-local. Use the shared valid-head resolver for projections. Project text through publicView, and deduplicate recurring public pass/id pairs into backlog proposals through durable `Review finding key:` markers (a pair recurs only across separate work: tasks sharing a PR, a branch in one repository or a supersession count once, `workUnits`, #736) read only from hub-proposed `plan` cards (first history entry by `hub`), without editing instruction files.
|
|
62
66
|
- Plan conformance (#481 S2, `src/hub/plan-conformance.ts`): compare only literal repository-relative `plan.paths` entries (file or directory prefix, no glob) with complete PR file metadata; `completePlanInventory` requires a full head, `pathsComplete` and `typesComplete` (false on any rename or copy). Root, absolute-unverified or unsafe entries, a missing plan, a truncated inventory and a failed read are `unknown`, never covered; never read patches or the filesystem. An untouched planned path is advice; a changed path outside the plan is a departure, and `planConformanceBlockers` blocks enforce approval on `unknown` and on an unaccepted departure. Plan `proof` and `risks` (32 strings of 512 characters) cover no file. `Board` records `planRevision` only while `trackPlanRevisions` (mode not off), separate from backlog `planningRevision`.
|
|
63
67
|
- Departure consent is written only by `Tasks.acceptPlanDeparture` for the person (`ahub sdlc accept-departure <task> <head> <reason...>`; no peer tool) on a currently known departure at the current PR head, never on `unknown`, and never for a PII task. It binds repo, PR, head, plan revision and fingerprint, the inventory, `planScopeIdentity` and this host's `departureGeneration`, so a restart or a change to any of them invalidates it (`validPlanDepartures`). Status and merge-queue reads never write the board: they only note observations and invalidations in memory (`observedPlans`, `invalidatedPlanDepartures`), which `planObservation()` attaches to the next owner or reviewer transition write. Off captures no observation or revision; a plan or refs change still invalidates an existing acceptance.
|
|
64
68
|
- The review coverage gate (#520) pins `refs.commit` and the sdlc receipt and rechecks them in `valid()`; an unavailable brief is never an empty one (enforce refuses, advise notes). Read brief attributes and rules at the merge base (`--attr-source`, `check-attr --source`, `git show <base>:`), never from the head or the checkout.
|
|
@@ -76,3 +80,16 @@ Scope: the task flow, assignment, the state machine and the hub's MCP tools. Rea
|
|
|
76
80
|
|
|
77
81
|
- `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.
|
|
78
82
|
- 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.
|
|
83
|
+
|
|
84
|
+
- 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 check, 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.
|
|
85
|
+
- 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.
|
|
86
|
+
- 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.
|
|
87
|
+
|
|
88
|
+
- Task/release planners (#525-2): keep nullable `planner` columns additive, with no backfill. Bind new proposals and null-planner pulls only from `assign()`; install the fixed synchronous `Board.proposalPlanner` host port so proposal, split-child and generated-card bindings enter the original insert transaction before notification. Preserve explicit null callers and existing rows. Preserve existing bindings on owner moves. `Tasks.taskPlanner` keeps a bound rostered settled holder even while offline/paused; a missing, removed or off-roster holder falls back to legacy holders. Every task/release planner effect and its post-await validation uses that fresh scope; project-wide reads remain holder-wide. Naming a planner is person-only, refused for closed or PII work, admits only the holder `assign()` returns for `namedPlanner`, and records only `planner changed`, never an owner delivery event. Split children intentionally run their own pure rotation and may bind a different planner from their parent; do not inherit the parent binding. A removed/off-roster binding is preserved as legacy metadata on pull without recording a false `kept` reason.
|
|
89
|
+
- 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.
|
|
90
|
+
|
|
91
|
+
- 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.
|
|
92
|
+
- Deliverables list and badge (#509b, #317): `Tasks.deliverableList` is the one reader for `ahub deliverables` (the `deliverables` task op, console only) 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.
|
|
93
|
+
|
|
94
|
+
- Backlog issue planning (#759): allow only `fields.sdlc.repo`/`issue` on unstarted ownerless backlog through `Tasks.planBacklog`, scoped to the effective planner/person; a PII card's identity is person-only and a public one must pass `publicMetadataText`, both pre-effect. Keep arbitrary lifecycle receipts refused on backlog. Reuse lifecycle identity invalidation, keep the planning write atomic and recheck after awaits. Store identity-only undo values; never revive old spec acceptance. Omitted/empty planning fields with a bounded reason record a note-only revision and retain normal pins/frozen/stale guards.
|
|
95
|
+
- 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.
|
|
@@ -8,7 +8,7 @@ Scope: tests, fakes and the test gate. Read before adding or changing a test or
|
|
|
8
8
|
- 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.
|
|
9
9
|
- 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`).
|
|
10
10
|
- 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.
|
|
11
|
-
- `bunfig.toml` preloads `test/hub-home.ts`, which first clears inherited `AMEM_HOME
|
|
11
|
+
- `bunfig.toml` preloads `test/hub-home.ts`, which first clears inherited `AMEM_HOME`, gives every test run (the gate and `bun test <file>` alike) a temp `AGENTHUB_HOME` unless one is set, and always replaces `KIMI_CODE_HOME` with a temp dir, an inherited value included (#285); scoped tests may set their own `AMEM_HOME` or `KIMI_CODE_HOME` after preload. Hub memory (#258) is on with any project config, and no test may write the person's `~/.agenthub`. Do not remove it; a test that needs its own home sets and restores the variable. The preload keeps an inherited `AGENTHUB_HOME`, which an agent shell exports as the person's hub home and `scripts/check.sh` does not unset: run `bun test` and the gate with `env -u AGENTHUB_HOME`.
|
|
12
12
|
- `test/registry-guard.test.ts` statically scans `src/` against the committed `test/registry-guard.baseline.json` (#451); a test never writes, regenerates or repairs that baseline, and a failing guard is fixed in the source by moving the policy into a definition (rule: `docs/agent-notes/adapters.md`); the baseline stays `[]`. Scanner behavior is pinned on inline sources in `test/registry-guard-fixtures.test.ts`, never on edits to real files.
|
|
13
13
|
- A fixture that needs a port takes it from a free-port probe, never a fixed number, which collides with whatever holds it on a shared runner (#548). The probe binds `127.0.0.1`, matching the loopback server that takes the port (#232).
|
|
14
14
|
- `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.
|
|
@@ -23,12 +23,15 @@ Scope: tests, fakes and the test gate. Read before adding or changing a test or
|
|
|
23
23
|
- The whole-suite watchdog is `AHUB_CHECK_HANG_S` (default in `scripts/check.sh`, hosted values for both OSes in `.github/workflows/check.yml`; #378, #593): raise it only when the suite was still progressing at the cutoff, never for a hung test, and keep the per-test 20 s timeout. The check job's `timeout-minutes` must leave room above that value for setup: raise them together.
|
|
24
24
|
- A seed's named test must run on Linux, where the seeded job runs: a darwin-only test reports no result there and fails the job (#382). Cover a macOS-only protection with its regression test instead.
|
|
25
25
|
|
|
26
|
+
- The `closing-refs` workflow (`.github/workflows/closing-refs.yml`, #760) runs `scripts/check-closing-refs.mjs` (PR number from argv or `PR_NUMBER`) on `opened`, `edited`, `reopened` and `synchronize`: it reads `gh pr view --json closingIssuesReferences,body,title,commits` and fails when GitHub would close an issue the title and body only mention with `Refs`, a negation or a qualifier (`Refs #15. Does not close #15.` and `Refs #15. Partially fixes #15.` fail, `Refs #15.` and `Fixes #15` pass), or when a commit message has a closing keyword, negated or not, for an issue the title and body do not mean (a squash merge carries it). An issue linked only in the sidebar passes. A keyword is meant only when nothing earlier in its sentence (ending at `.`, `!`, `?`, `;` or a line break, never a comma) matches `NEGATION`; widen that list, never narrow it to let a qualified close pass (background: README). `mentioned`, `keywordReferences`, `intentionalCloses`, `closingMisuse` and `commitMisuse` are pure and covered by `test/closing-refs.test.ts` with the real `gh` shape (`repository.name`, `repository.owner.login`); its command test puts a fake `gh` first on `PATH`, never the real one. Run it by hand with `GITHUB_REPOSITORY=<owner/repo> node scripts/check-closing-refs.mjs <number>`.
|
|
27
|
+
|
|
26
28
|
- 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.
|
|
27
29
|
|
|
28
30
|
- 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.
|
|
29
31
|
- 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.
|
|
30
|
-
- `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 and each `:
|
|
32
|
+
- `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.
|
|
31
33
|
|
|
32
34
|
- Keep the CI admission lock around the whole controller workflow, not just its decision job, in its per-PR (`check-pr-<number>`, `queue: single`: a newer pending run replaces the older) or main (`check-main`, `queue: max`) group, never with `cancel-in-progress: true` beside `queue: max` (GitHub refuses it) (#609); two groups admitting one tree at once is an accepted duplicate run, never a reason to fail closed on another tree. Preserve successful identical-tree full proof and required job names; a skipped job, pending controller or unavailable active inventory is never a successful full gate. In `readFullGateAdmission`, lost successful proof (404, malformed proof, a per-read timeout) only means a fresh gate runs; unknown active coverage and the global read ceilings (`GateReadLimitError`) stay unknown and refuse. CI checks out the PR head (`pull_request.head.sha`), not a merge with main: after a fix lands on main, merge main into the branch before reading CI as proof. CI reuse tests use fixed metadata fixtures and never dispatch real duplicate workflows.
|
|
33
35
|
|
|
34
|
-
- Headless-reviewer tests run `test/fakes/fake-claude-print.ts` through `command` or a PATH stand-in, and fresh Codex reviewer tests run `test/fakes/fake-codex-reviewer.ts` through `codex_bin`; a test that reaches the daemon's reviewer start must never run the real `claude` or `codex`. The fake's sandbox markers test host admission only, never native isolation. Fresh Kimi reviewer tests set and restore `KIMI_CODE_HOME` to a fixture native home (the backend links that home's `credentials/`, `oauth/` and `device_id`, else the person's `~/.kimi-code`) and run `test/fakes/fake-kimi-reviewer.ts` through `command` or a PATH stand-in, never the real `kimi`. agy tests put a fake `agy` first on PATH and set `HOME` to a temp dir before any transcript readback (`readAgyTranscript` reads `$HOME/.gemini`). Code-facts tests build `new CodeFacts({ env })` with a PATH whose first `ripwire` is a wrapper around `test/fakes/fake-ripwire.ts` (`FAKE_RIPWIRE_MODE` selects its failures), or inject Tasks' `prepareReviewBrief`; never rely on an installed ripwire, which CI does not have.
|
|
36
|
+
- Headless-reviewer tests run `test/fakes/fake-claude-print.ts` and headless Claude implementer tests run `test/fakes/fake-claude-implementer.ts` through `command` or a PATH stand-in (the implementer with `HOME` and `CLAUDE_CONFIG_DIR` in a temp dir), and fresh Codex reviewer tests run `test/fakes/fake-codex-reviewer.ts` through `codex_bin`; a test that reaches the daemon's reviewer or implementer start must never run the real `claude` or `codex`. The fake's sandbox markers test host admission only, never native isolation. Fresh Kimi reviewer tests set and restore `KIMI_CODE_HOME` to a fixture native home (the backend links that home's `credentials/`, `oauth/` and `device_id`, else the person's `~/.kimi-code`) and run `test/fakes/fake-kimi-reviewer.ts` through `command` or a PATH stand-in, never the real `kimi`. agy tests put a fake `agy` first on PATH and set `HOME` to a temp dir before any transcript readback (`readAgyTranscript` reads `$HOME/.gemini`). Code-facts tests build `new CodeFacts({ env })` with a PATH whose first `ripwire` is a wrapper around `test/fakes/fake-ripwire.ts` (`FAKE_RIPWIRE_MODE` selects its failures), or inject Tasks' `prepareReviewBrief`; never rely on an installed ripwire, which CI does not have.
|
|
37
|
+
- 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.
|
package/docs/dogfood.md
CHANGED
|
@@ -12,9 +12,19 @@ agent-hub is developed with agent-hub. This page lists every feature, whether it
|
|
|
12
12
|
| --- | --- | --- |
|
|
13
13
|
| Message bus, digests, priorities | on | `ahub report`: messages, digests, overflow, undeliverable; delivery latency from events |
|
|
14
14
|
| Compact board (#724) | on | `ahub board` at 80, 120 and 200 columns: one line per task (test/board-compact.test.ts) |
|
|
15
|
+
| 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 |
|
|
15
16
|
| 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 |
|
|
16
|
-
|
|
|
17
|
+
| 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) |
|
|
18
|
+
| Per-task and release planners, I-4 (#525-2) | off until this change is released and the running hub is upgraded; this PR enables no new project planner configuration | task/release planner bindings and selection reasons; wrong-planner pre-effect refusals; independent plan-review requests and `selfReview` receipts; queued notices rerouted after a planner change; `task_planners` recovery refusals |
|
|
19
|
+
| 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) |
|
|
20
|
+
| Deliverable records (#509a) | on after the release: `deliverables` on `hub_task_done` and `hub_sdlc_record`, hashed by the hub in this repository's folder; most tasks here are code with a PR and name none, so only document-shaped work (a report, a spec draft) uses it | the `Deliverables` block in a review request and `deliverables` in `ahub task show`; approvals refused with `deliverable changed since it was delivered`; records with `state: unknown` (a file the hub could not vouch for); claimed-hash refusals as a measure of agents trying to supply a hash |
|
|
21
|
+
| `ahub deliverables` and the dashboard badge (#509b) | on after the release: `ahub deliverables [--check]` on this repository's board; only tasks that named deliverables appear, so it lists document-shaped work only | the person runs `ahub deliverables --check` after a document task is approved and the next time its file is edited: a `changed since review` row names the edit; `Deliverables n` chips on cards; `not measured now` in the sheet means nobody ran `--check` since the daemon started |
|
|
22
|
+
| 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 |
|
|
23
|
+
| 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 |
|
|
24
|
+
| 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 |
|
|
25
|
+
| 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 |
|
|
17
26
|
| Overlap notices | on; plan symbols match by normalized name (arguments stripped, a bare name matches the last segment, two qualified names must be equal; #682 phase 1b) | `ahub report`: overlap warnings, task pairs; false-overlap rate, and symbol-only matches on bare common names (#682) |
|
|
27
|
+
| Backlog phase grooming and issue planning (#759) | off until released and the running hub supports the new planning fields; relinks for cards #138/#287 remain a post-landing planner action | related-phase duplicate count; unrelated same-issue duplicate controls; planning revisions with `fields: ["sdlc"]` or `[]`; old spec acceptance absent after relink/undo |
|
|
18
28
|
| Planner WIP caps and backlog pull (#655, #718) | on (`tasks.wip_limits.codex = 2`); reserved-pull handoff on after release | board history/offer readback: reserved `backlog pulled` -> `assigned` -> `accepted` for the same peer; stale or unowned offers, capacity refusals and held-ready entries |
|
|
19
29
|
| Task sweep (stalls, idle owners) | on | stall and sweep events per week |
|
|
20
30
|
| Turn-free coordination | off: experimental; needs a separate measured trial | coordination events, conflict notices |
|
|
@@ -35,6 +45,7 @@ agent-hub is developed with agent-hub. This page lists every feature, whether it
|
|
|
35
45
|
| Spec acceptance (#481 S1) | advise | build entries without acceptance |
|
|
36
46
|
| Lifecycle identity linked by the hub (#694, #723) | on in advise; links at propose and accept from the title and `refs.repo`, with a groom backfill as the safety net | open tasks still showing `issue identity missing` right after propose (should be only titles that name no issue or a qualified title without `refs.repo`) |
|
|
37
47
|
| Planner plan review (#481 S9) | advise | plans reviewed before build |
|
|
48
|
+
| Planner board-changed notice (#758) | on here, with the lifecycle in `advise`: `claude` is the planner, so a card another peer proposes or re-plans reaches it once at task weight, and its own writes, state moves, reviews and assignments cost it no turn | per planner and week: `task`-kind hub envelopes whose body starts `The board changed` (target: one per planning change, none after the planner's own write), the same envelopes at `fyi` priority (recorded, no turn: at most one per 10 minutes), and the count still queued at once (target: at most one). Read from `ahub log` and the event log; the 12 copies in 44 minutes seen on 2026-10-24 are the baseline |
|
|
38
49
|
| Plan conformance (#481 S2) | advise | PRs with files outside the plan |
|
|
39
50
|
| Test integrity for bug fixes (#481 S4) | advise | bug PRs without a red-first receipt, modified pre-existing tests |
|
|
40
51
|
| Required checks | advise (`check (ubuntu-latest)`, `check (macos-latest)`, `seeded guards`) | first-pass CI rate, same-head reruns (`hub_sdlc_costs`) |
|
|
@@ -47,6 +58,7 @@ agent-hub is developed with agent-hub. This page lists every feature, whether it
|
|
|
47
58
|
|
|
48
59
|
| Feature | State here | Measured by |
|
|
49
60
|
| --- | --- | --- |
|
|
61
|
+
| 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 |
|
|
50
62
|
| Shared memory through amem, briefs, recall preface | on | notes saved per week, brief hits, fails that prevented a retry |
|
|
51
63
|
| `ahub ask` | on | answers with cited ids |
|
|
52
64
|
| Code structure in the memory graph (#684) | pending | stale anchors, briefs with related notes |
|
|
@@ -58,7 +70,7 @@ agent-hub is developed with agent-hub. This page lists every feature, whether it
|
|
|
58
70
|
| Quota budgets and pauses | on | pauses, resets, time paused (#683) |
|
|
59
71
|
| 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 |
|
|
60
72
|
| Context gate | off: `context.gate = 0` | context-pressure events |
|
|
61
|
-
| Always-ask action classes (#636) |
|
|
73
|
+
| 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 |
|
|
62
74
|
| Permission modes | on, person-chosen | approval prompts and their wait times |
|
|
63
75
|
| Pi and the local worker on DGX | off: DGX unreachable recently; Pi is off the roster | turns served on-prem, relay errors |
|
|
64
76
|
| PII routing | on | PII tasks and where they ran |
|
|
@@ -69,12 +81,14 @@ agent-hub is developed with agent-hub. This page lists every feature, whether it
|
|
|
69
81
|
| Feature | State here | Measured by |
|
|
70
82
|
| --- | --- | --- |
|
|
71
83
|
| Dogfood scorecard (#687) | on after release; read-only weekly receipt | `ahub report --dogfood --since 7d --json`; bounded evidence and explicit unknowns |
|
|
72
|
-
| Console and dashboard | on | control-request latency, connection timeouts
|
|
84
|
+
| 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 |
|
|
73
85
|
| Usage view (#683) | CLI (`ahub usage`, U2), console Usage tab `7` (U3) and dashboard Usage view (U4) shipped | its own panels |
|
|
86
|
+
| 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 |
|
|
87
|
+
| Budget window kinds and notes (#285 phase 4) | on after the release; `ahub budget`, the console peer detail, the dashboard Usage budgets row and `ahub doctor` (`kimi usage source`) run here | each window shows its kind and source, a peer without a number says why (Pi, local and Kimi included); a manual `ahub budget set` value outranks the Kimi count at the gate; the doctor row reads ok while the wire log counts |
|
|
74
88
|
| Settings reload | on | reloads applied, pending restarts |
|
|
75
|
-
| Config editing from the console and dashboard (#689) |
|
|
89
|
+
| Config editing from the console and dashboard (#689) | on after release; `ahub settings edit` and console key `c` run here, the dashboard Structure group needs `ahub ui --settings`; executable keys stay console only | `config_edit` events (edits applied by tier and source), refusals shown with the loader reason, restarts left pending |
|
|
76
90
|
| Upgrade and recovery | on, per release | upgrade receipts in `docs/verification/` |
|
|
77
|
-
| 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); Kimi 2.3.0 and Qwen 0.26.0 verified by the [model-free receipt](verification/2026-10-22-kimi-qwen-compat.md), with account/provider live legs unverified; agy 1.4.4 in-line | model-free isolation receipts, `ahub agents` verdicts, versions trusted per machine, lead time from an `agent-compat` issue to a receipt |
|
|
91
|
+
| Agent CLI version policy and trust (#691) | on after the release: Codex 0.163.0 verified by the [2026-10-22 receipt](verification/2026-10-22-codex-reviewer-isolation.md) and 0.164.0 by the [2026-10-24 receipt](verification/2026-10-24-codex-0164-reviewer-isolation.md); Kimi 2.3.0 and Qwen 0.26.0 verified by the [model-free receipt](verification/2026-10-22-kimi-qwen-compat.md), with account/provider live legs unverified; agy 1.4.4 in-line | model-free isolation receipts, `ahub agents` verdicts, versions trusted per machine, lead time from an `agent-compat` issue to a receipt |
|
|
78
92
|
| Benchmarks (#251, #637) | off: `bench.enabled = false`; routing gate pending | bench receipts |
|
|
79
93
|
|
|
80
94
|
## Decisions
|
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`), 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`), 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) |
|
|
@@ -56,12 +56,26 @@ marked `private: true`, and PII tasks `pii: true`.
|
|
|
56
56
|
|
|
57
57
|
Token usage by adapter:
|
|
58
58
|
|
|
59
|
-
- Kimi (
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
- Kimi (#285 phase 2): consumption is the hub's own count of Kimi's session wire log, never an ACP total. The `{used, size}`
|
|
60
|
+
`usage_update` Kimi sends is context occupancy: it is shown as the peer's native context reading (source
|
|
61
|
+
`acp_usage_update`, phase 1) and writes no `tokens` event. The reader (`src/adapters/kimi-wire-usage.ts`, the only code that knows
|
|
62
|
+
the format) finds the session directory from `session_index.jsonl` (resolved with `realPath`, kept inside `<kimi home>/sessions`)
|
|
63
|
+
and reads `agents/*/wire.jsonl` incrementally from saved offsets, sub-agents included, at a turn end, when a `usage_update`
|
|
64
|
+
arrives and when the peer goes offline (a turn start only binds the session and fixes the history floor, it reads nothing): no
|
|
65
|
+
watcher, no timer. One call reads at most a fixed byte bound across all of the session's files (the rest waits for the next call),
|
|
66
|
+
and two agent entries that are one file count once. Each `usage.record` row becomes a `usage` event
|
|
67
|
+
(`source: kimi_wire`, `inputTokens` = Kimi's `inputOther`, `outputTokens`, `cacheReadTokens`, `cacheWriteTokens`, `totalTokens`
|
|
68
|
+
including cache reads, `servedModel`, `measuredAt` = Kimi's row time) and a `tokens` event of the counted share. The id is an
|
|
69
|
+
opaque hash of session, agent and byte offset; no path, session id or text reaches an event. Only the four integers, the model
|
|
70
|
+
label and the time are copied from a row. The counted share is `inputOther + inputCacheCreation + output` (`kimiCountedTokens`,
|
|
71
|
+
one function): cache reads are recorded and shown, never counted against `budget.kimi_tokens_5h`. The rolling 5 h sum of the
|
|
72
|
+
counted share against that ceiling is the `tokens` window (`hub count, kimi wire log, of kimi_tokens_5h N`, kind `count`: #285 phase 4 names each window's kind in the budget reply, never in an event, and a person's `ahub budget set` value, kind `manual`, outranks it at the gate). Rows already in the
|
|
73
|
+
session's log when the hub first sees it (a resumed session) are history and are not counted. An unreadable index, a missing,
|
|
74
|
+
shrunk or replaced file (a replacement is told by a hash of the consumed head and tail kept with each offset, since Linux can reuse the inode), a truncated last line, an unknown row shape or an untested wire `protocol_version` (only 1.5 is known)
|
|
75
|
+
makes the count unknown: one notice (`kimi usage unknown (<reason>)`), the `tokens` reading is dropped, and no figure is reported
|
|
76
|
+
until the 5 h window the gap touched has passed (a transient read error or truncated line heals on the next read). Wire format
|
|
77
|
+
followed: protocol 1.5 as written by Kimi Code 2.3.0 (the issue read 2.1.1; the hub verifies 2.2.0 and 2.3.0 in `compat.ts`). The
|
|
78
|
+
live check against Kimi's own `/usage` (#285 AC11) is not done yet.
|
|
65
79
|
- Codex (app-server `thread/tokenUsage/updated`): recorded as the growth of the thread's running total, so
|
|
66
80
|
compaction estimates, usage-limit refreshes and the replay to a reattaching connection add nothing. A thread
|
|
67
81
|
started under the hub counts from zero; a resumed thread's first update is its history and only sets the
|
package/docs/memory.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
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.7.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
|
|
|
7
7
|
The [agent-mem repository](https://github.com/STAIxBWLB/agent-mem) is the reference for the data model, immutable entry IDs, append-only segments, derived SQLite index and rebuild equality, home selection, import cursors, dedupe and machine sync. Shared v1 remains the default and needs no migration: schema `agent-hub.memory/v1`, canonical hashes and log/machine/lock paths remain compatible. `memoryHome()` comes from the package and selects `AMEM_HOME`, then `$AGENTHUB_HOME/memory`, then `~/.agenthub/memory`; an explicit `StoreOptions.home` keeps its parent-directory meaning. The test preload (`test/hub-home.ts`) clears inherited `AMEM_HOME` and supplies an isolated `AGENTHUB_HOME` when none is provided.
|
|
8
8
|
|
|
9
|
-
`src/memory/` contains hub integration only: briefs, recall, the
|
|
9
|
+
`src/memory/` contains hub integration only: briefs, recall, the in-process amem client and deterministic capture. `src/hub/memory-policy.ts` supplies `withoutPii`, `promotionScreen` and `mentionsPrivatePath`; `src/hub/read-tools.ts` supplies screened peer reads. Deny, hardened git, realPath and process identity come from the package's Node-safe `@staix/agent-mem/platform` entry and are re-exported through the existing local/hub/Pi modules. No core implementation is vendored in agent-hub. The `0.1.1` platform subpath is required because `0.1.0` exposes only a root entry that loads `bun:sqlite`, which Node-hosted Pi extensions cannot import. This is a packaging compatibility export; the platform algorithms are unchanged.
|
|
10
10
|
|
|
11
11
|
## Selected memory profile
|
|
12
12
|
|
|
@@ -20,7 +20,7 @@ Person-facing status and memory output show the profile and nonzero malformed-ro
|
|
|
20
20
|
|
|
21
21
|
A running daemon keeps its original binding. Explicit checks at hub operation entry and before sidecar writes fence later operations when the selector changes or becomes unavailable; they do not switch stores in place. These checks are not atomic with an in-flight core operation: changing selection during such an operation is outside the supported cutover contract. Before migration or a selector change, quiesce every consumer of this memory home and close its handles; reopen through the supported session-preserving lifecycle afterward. A public core pre-write hook is the upgrade path for atomic in-flight cutover. Current policy screening, PII-turn refusal, capture exclusion and fail-open task delivery remain in force.
|
|
22
22
|
|
|
23
|
-
Candidate/counter data stays at `M/candidates.db`, independently of the selected note profile, using the package's explicit sidecar home convention. It is not copied into `G`, migrated or synced. The hub fences sidecar producers with the note-profile binding.
|
|
23
|
+
Candidate/counter data stays at `M/candidates.db`, independently of the selected note profile, using the package's explicit sidecar home convention. It is not copied into `G`, migrated or synced. The hub fences sidecar producers with the note-profile binding. Since agent-mem 0.7.0, `amem review` lists and accepts candidates on a selected generation too, writing accepted notes into that generation; dashboard acceptance is still not added.
|
|
24
24
|
|
|
25
25
|
Source and synthetic fixture checks do not authorize migration of the person's memory home. Before real migration, every hub sharing the memory home must run 0.12.30 or later. Real rollout still needs separately approved installation, matching live package/root/profile/selector bindings, synthetic cross-write and prune readbacks, and a supported quiesce/close/reopen sequence before separately confirmed real migration. Every running hub using this home needs an authenticated receipt: a matching ready binding, or a generation-capable unbound receipt (`disabled` when native memory is off, `unavailable` when opening failed) proving it holds no memory handle. An unbound daemon and zero running hubs are quiesced for memory; a stale bound handle, unreadable registry or unknown runtime is not ready. Real account/device sync, native capture and live memory receipts remain distinct acceptance legs. See [agent-mem migration and prune](https://github.com/STAIxBWLB/agent-mem#generation-migration-and-exact-entry-prune) for the person CLI and recovery limits.
|
|
26
26
|
|
|
@@ -95,12 +95,11 @@ A running hub syncs 5 seconds after start and every `memory.sync_interval_min` m
|
|
|
95
95
|
|
|
96
96
|
- **Task briefs**: when a task is handed to an owner, its brief lists hub memory first (entries one or two hops from
|
|
97
97
|
the task's paths, symbols, issue and task: a linked fail first, then nearer, then newer; superseded entries dropped;
|
|
98
|
-
other projects' entries only when shared), then
|
|
98
|
+
other projects' entries only when shared), then related amem curated notes, `memory.brief_items` lines in all. A peer is never
|
|
99
99
|
shown the same entry twice in one hub run.
|
|
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.
|
|
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
|
-
as `[mem:<8 hex>]
|
|
103
|
-
model's answer never becomes hub memory.
|
|
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.
|
|
104
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:
|
|
105
104
|
|
|
106
105
|
```bash
|
|
@@ -146,8 +145,8 @@ The package owns deterministic import identity, text-only graph extraction, curs
|
|
|
146
145
|
|
|
147
146
|
Whether hub memory helps is measured, not assumed (#258 AC11, with #248):
|
|
148
147
|
|
|
149
|
-
- Each task brief writes a `memory_brief` event with counts only (hub-memory items, fails among them,
|
|
150
|
-
items); research records keep `briefs` (`delivered`, `withFail`), and `ahub research` splits approved tasks into
|
|
148
|
+
- Each task brief writes a `memory_brief` event with counts only (hub-memory items, fails among them, supplemental
|
|
149
|
+
items in the legacy `claudeMem` count field; new events identify `source: "amem"`); research records keep `briefs` (`delivered`, `withFail`), and `ahub research` splits approved tasks into
|
|
151
150
|
"a fail note in its brief", "brief without a fail note" and "no brief recorded" (first pass, rework, check failures).
|
|
152
151
|
- `ahub bench run --memory off|claude-mem|native|both` declares an arm's memory and refuses a hub that runs with other
|
|
153
152
|
settings; compare the arms with `ahub bench compare`.
|
|
@@ -183,6 +182,75 @@ Agents propose a correction with `hub_memory_correct {id, text, reason}`. The ta
|
|
|
183
182
|
|
|
184
183
|
A `changes_requested` review with unmet items can create one fail candidate. Tasks screens the public task title, unmet items and review evidence before the sidecar write; private tasks, private turns, withheld text and failed or unknown screens produce no candidate. Cause and retry conditions are explicitly unknown unless a person supplies them when reviewing.
|
|
185
184
|
|
|
186
|
-
Candidates and rendered/delivered/cited counters live only in agent-mem's machine-local `M/candidates.db` (package retention: 500 candidates/project for 30 days, 10,000 counters/project for 90 days). They are never imported or synced and are not evidence for recall, briefs, ask or graph.
|
|
185
|
+
Candidates and rendered/delivered/cited counters live only in agent-mem's machine-local `M/candidates.db` (package retention: 500 candidates/project for 30 days, 10,000 counters/project for 90 days). They are never imported or synced and are not evidence for recall, briefs, ask or graph. A person reviews them with `amem review` and accepts or rejects through that CLI's TTY and policy checks, on shared v1 and, since agent-mem 0.7.0, on a selected generation. Dashboard acceptance is a later step. Accepted results are ordinary v1 notes under the package's acceptance contract; the hub tool has no acceptance authority.
|
|
187
186
|
|
|
188
187
|
Rendered observations use the final journal payload, including memory lines in prefaces and briefs, with the real delivery id. Delivered observations require the journal's first positive transport acknowledgement (or the pull path's completed read receipt). Failed or uncertain handoffs do not establish delivery; journal-less transports leave it unknown. Citation observations resolve `mem:<id8+>` in a peer message or task completion summary against screened project-visible entries. Citations use explicit `message:<envelope-id>` or `summary:<task>:<history-index>` observation keys when the source delivery is not proven; their rendered/delivered values remain unknown. They do not establish a per-delivery causal ratio. Duplicate observations are idempotent per entry/context/observation, not cumulative usage claims. Unavailable task, turn or session identity and unobserved metrics remain unknown, never zero. No observation is written for a private envelope, PII task or PII turn. These are delivery and citation proxies, not proof that an agent understood or used a note correctly.
|
|
188
|
+
|
|
189
|
+
## In-process recall and capture (agent-mem#13 P4)
|
|
190
|
+
|
|
191
|
+
The exact 0.6.0 pin is the first published package with P3. It was checked directly against the cache-busting npm registry read: gitHead `2a182f2f23b7744a746b6854f9fb920df627a99f`, published 2026-10-23. The hub uses the canonical, alias-resolved project key and the selected profile; caller-written legacy project-chain labels never select a different store. `memory.worker_url` is retained only as ignored legacy configuration, not a network destination. `ahub doctor` reports the library rather than probing a worker.
|
|
192
|
+
|
|
193
|
+
Changes from the worker integration are deliberate:
|
|
194
|
+
|
|
195
|
+
- Task briefs and ask use curated amem entries with authentic `mem:` identifiers. Raw capture is not evidence. The existing native graph-neighbourhood/fails-first path is retained.
|
|
196
|
+
- Recent recall supplements long-term notes with short-term curated notes. It does not recreate claude-mem's model-produced observation summaries. Those historical rows require the explicit person-run import.
|
|
197
|
+
- Local capture deterministically appends SessionStart, tool events, Stop excerpts and SessionEnd. Host session/turn/step/invocation identity distinguishes actual executions even when a provider reuses its tool-call id; a replay of the same observation dedupes. There is no model summarizer, worker, watcher or network call. Published CaptureStore owns bounded excerpts, cap/retention, contention spool and replay. The bound handle is reused; effective R6 config is checked before each append and a change reopens it before writing. Refusal and daemon shutdown close it.
|
|
198
|
+
- The host excludes private turns, PII tasks, private paths, memory/tool bookkeeping and secrets before capture. It screens complete input/output before truncation. Whole-row privacy screening also precedes read selection and rendering.
|
|
199
|
+
- Native task notes are written once. Auxiliary checkpoint and model-answer writes are candidates, not another curated copy; their submitted metadata is retained in the candidate text and their note kind is preserved. The complete candidate must fit the package's 4000-character limit, or the save is unavailable. Model answers are excluded from every curated read projection even if an old import contains one.
|
|
200
|
+
- Supplemental reads use bounded exact-project pages with explicit coverage; see "P4 supplemental read and capture bounds" below. Published SearchIndex supplies ranking after screening, with a term-wise labelled fallback when FTS5 is absent.
|
|
201
|
+
|
|
202
|
+
The integration test uses actual temporary stores with fetch denied and verifies recall, brief and ask without a worker, local-only capture, replay dedupe, privacy before excerpts, project binding and candidate/evidence separation. Older fixture transport tests use a host-only injected FakeMemoryClient; no URL or fixture registry is accepted from production configuration.
|
|
203
|
+
|
|
204
|
+
Historical benchmark memory-arm labels are retained for old records. Their `claude-mem`/`both` names are not evidence of a live worker on this runtime; new comparisons require a new declared condition and an amem-aware benchmark plan. No benchmark collection was run for this integration.
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
Write screening examines both the JSON representation and literal structured keys/values before storage escaping. Authoritative tool input/output JSON is decoded once, from the complete original output before excerpt bounding; arbitrary nested JSON-as-text or other encodings are not interpreted. Host locators such as cwd are not capture content and are not persisted: treating the project root as content would falsely classify every event as mentioning every private subtree. Persisted capture fields and complete tool data remain screened, and complete custom deny paths are checked before token fallback. The test-only old-HTTP projection retains its old 4000-character response cap; production capture delegates its bound to CaptureStore after policy.
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
## P4 supplemental read and capture bounds
|
|
211
|
+
|
|
212
|
+
Session-start supplemental recall is one screened short-term curated block from every author, including the receiving peer. No adapter platform whitelist or "agent-hub" pseudo-source remains. The supplemental view uses the published `readPage` keyset API: at most 16 pages of 256 exact-project candidate bodies, plus one lookahead. It never calls the materializing `list()` path. Newest project candidates are considered first; filtering private or non-short rows may leave fewer displayed rows. A remaining page produces `memory view truncated: newest 4096 project candidates considered` in recall, search and task briefs, including when all considered rows are withheld. The control status `memoryRead` reports the last read's `truncated`, `scanned` live candidate bodies and `limit`; it is not a total corpus count.
|
|
213
|
+
|
|
214
|
+
This supplemental path deliberately excludes other projects' shared rows, which could previously crowd out the project's own short-term notes. Native shared long-term recall, graph evidence and peer read tools retain their existing scope. The package still folds relation metadata during a body page; tier/scope-aware core pages and incremental metadata snapshots are the upgrade path for that remaining cost. No total-cost bound on the core refresh is claimed.
|
|
215
|
+
|
|
216
|
+
Capture reuses one store handle per bound project/profile home and closes it on a binding error and daemon shutdown. Effective published R6 config is checked before each append; changed cap or retention reopens the handle before that append, so person settings retain next-invocation behavior. Every effect still rechecks the current binding and screens complete input/output first. Persisted path metadata contains only project-relative read/write/edit paths; bash commands, git argv and outside-root paths are omitted from that column. Legacy library callers without a host invocation id still deduplicate a native id within a summary interval; live LocalPeer supplies a separate session/turn/step/invocation id. Other hosts must supply that port before claiming cross-turn identity.
|
|
217
|
+
|
|
218
|
+
## Canonical symbol anchors (#684 phase 3)
|
|
219
|
+
|
|
220
|
+
The hub normalizes function signatures with its shared symbol normalizer and uses the existing amem `symbol:<qualified>@<path>` node format. Code-backed completion anchors require explicit file identity; an unknown or name-only resolution never invents a file. Existing non-completion notes retain normalized plan-intent associations, which are not evidence that a symbol exists or changed. Completion notes replace those guesses with verified actual symbols, or none when observations are unknown.
|
|
221
|
+
|
|
222
|
+
At an admitted completion, only the current head's screened host observations are candidates. The optional code-facts `exists` check must positively confirm an unambiguous existing symbol before its node enters the new completion note. Private tasks make no symbol queries and write no memory. Unknown observations write no symbol anchors and the completion note labels their coverage unknown. At most 16 candidates are considered; new checks stop after a shared two-second scheduling budget. An already-started call retains the code-facts boundary's own bounded execution and cleanup, so this is not a strict two-second wall-clock guarantee. Missing evidence never refuses completion.
|
|
223
|
+
|
|
224
|
+
Earlier notes stay immutable. The completion note and earlier notes share the task's reset-safe node. `related(project, [symbol], limit)` reaches the earlier notes at memory hop two; literal graph edges are symbol -> completion note -> task -> earlier note. The pinned package's `graph(..., 2)` stops at the task node. No overlay, code structure, copied note, supersede operation or new storage schema is introduced.
|
|
225
|
+
|
|
226
|
+
ponytail: the symbol -> task bridge expires with the existing short-term completion finding; afterward a long-term note is found by symbol only through its own `about`. The upgrade path is phase 4 (#367), deriving edges from recorded changed symbols at read time without storing them.
|
|
227
|
+
|
|
228
|
+
Qualified-name misses from the current ripwire CLI remain unknown: version 0.6.7 can report `Parser.run` absent while observing `run` in `Parser`. Plain-name negative evidence retains its existing meaning. The shared code-facts boundary can lift this ceiling once a CLI/schema attests qualified lookup.
|
|
229
|
+
|
|
230
|
+
`ahub doctor --stale-anchors [--json]` is an optional read-only inspection of symbol anchors against the current head. It reports bounded coverage and unknown evidence explicitly. A positive absence establishes stale status; unavailable, private or ambiguous evidence does not. It never deletes, edits, promotes or repairs a note. The default doctor checks are unchanged.
|
|
231
|
+
|
|
232
|
+
Inspection considers at most 256 exact-project notes and starts at most 16 code-facts checks, with explicit unknown/truncated coverage. A task-linked anchor also requires a current, public, reset-safe task record in this repository; historical public text cannot override a now-private, foreign or missing task record. A missing/uninitialized selected profile or project shard remains unknown and is not created. Existing core reads may maintain derived indexes and reader leases, but the report changes no authoritative note or candidate record.
|
|
233
|
+
|
|
234
|
+
## Symbol preparation and historical query compatibility (#684 follow-ups)
|
|
235
|
+
|
|
236
|
+
A filled symbol-preparation key (execution root, head and merge base) is reused between the prepare-only and completion passes. Within one pass, identical name lookups share known or unknown results even when several candidate file paths use that name. Completion still rechecks current privacy, repository and head provenance; cached advice never grants authority.
|
|
237
|
+
|
|
238
|
+
Plan-guard and related-memory queries include both an explicit raw symbol input and its normalized form, so an older `symbol:foo(a: string)@x.ts` association remains reachable alongside `symbol:foo@x.ts`. This is query compatibility only; existing notes and their IDs are never rewritten.
|
|
239
|
+
|
|
240
|
+
The stale report leaves unsupported `#`, `->` and `:` lookup spellings unknown. A plain name not found by the index is labelled "absent from indexed definitions", not proof that an imported package API or type was removed. The JSON state/reason fields retain their existing meaning, with an additive label only on final validated absence rows.
|
|
241
|
+
|
|
242
|
+
Code-facts queries can register transient detached Git worktrees. Interrupted cleanup, including Ctrl-C, can leave a prunable registration. Inspection does not auto-prune: inspect `git worktree list --porcelain` and clean only a confirmed abandoned entry. A repeated owned cleanup failure returns neutral unknown coverage; borrowed handles remain caller-owned. Note and candidate bytes are unchanged by inspection.
|
|
243
|
+
|
|
244
|
+
## Read-time code neighbours (#684 phase 4)
|
|
245
|
+
|
|
246
|
+
Select a symbol in the dashboard Memory view and enable Code neighbours to inspect one hop of callers and callees. Dashed `calls` edges are computed through the optional code-facts boundary at the displayed full commit SHA; they are never stored in amem. Attached notes are highlighted, fails first. Turning the toggle off returns the stored graph. An ambiguous seed or name-only relation is withheld; unavailable or partial evidence is explicitly unknown.
|
|
247
|
+
|
|
248
|
+
`hub_memory_graph` also prepares this overlay for symbol starts. Stored and derived nodes share its existing maximum 120 nodes, 240 edges and 6144-byte serialized response budget (default 60 nodes). Omitted nodes/edges contribute to truncation. Authored note associations remain ordinary `about` or `rules_out` edges; only code calls carry the derived SHA stamp.
|
|
249
|
+
|
|
250
|
+
A preparation starts at most four canonical, path-qualified seeds and no new query after two seconds. It retains at most 32 distinct neighbours, 64 call edges and 16 screened attached notes. In-flight work retains the existing code-facts timeout and cleanup bounds. The file:name selector must attest exactly one definition; ambiguous/name-only seeds are unknown even when their relation lists are empty. Queries run outside the synchronous dashboard snapshot Git budget, and current HEAD, profile, policy and caller/task privacy are rechecked across awaits. PII tasks and turns do not query code facts.
|
|
251
|
+
|
|
252
|
+
Task briefs merge direct and neighbour-attached fail/decision notes first, deduplicate IDs and retain the existing combined `brief_items` cap and line excerpts. The cap is snapshotted once before selection; a reload applies to the next brief. The console task detail's related-note line counts this bounded brief selection, not all notes in the store; unknown code coverage remains unknown. This does not persist a code index, extend completion-note TTLs, or change amem's graph API.
|
|
253
|
+
|
|
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
|
+
|
|
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.
|