From c28ea8b74d3dfb270c6f51178918636c9b49bd14 Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Thu, 25 Jun 2026 09:52:23 +0200 Subject: [PATCH 01/54] docs(skills): reconcile strategies/lenses READMEs to current state Remove the stale future-tense "M5 input" classification/ranking tables and the dead `INTENT_GRAPH_SEMANTICS.md` pointer from both axis READMEs; replace with a short Heuristic provenance note. The heuristics are authored and locked into each `//SKILL.md` body in distilled form (D97-L cite/distill), and graph vocabulary is owned by `src/graph/schema/kinds.ts`; the READMEs now own axis/lens membership only. Discharges the final residue of the skill-substrate arc. Co-authored-by: Cursor --- src/.pi/skills/lenses/README.md | 35 +++++++-------------------- src/.pi/skills/strategies/README.md | 37 +++++++---------------------- 2 files changed, 18 insertions(+), 54 deletions(-) diff --git a/src/.pi/skills/lenses/README.md b/src/.pi/skills/lenses/README.md index 9c0561813..9affe47ff 100644 --- a/src/.pi/skills/lenses/README.md +++ b/src/.pi/skills/lenses/README.md @@ -15,29 +15,12 @@ agent is currently exploring or proposing into. Future execute-mode lenses (`plan`, `sync`, `scope`) are deferred. -## Topology-driven question ranking (M5 input) - -When `agents-composition-layer` authors the lens resources, each lens -should include topology-driven heuristics for what to ask next. -These heuristics read graph shape, not templates: - -| Signal | Suggested question shape | -|----------------------------------------------|---------------------------------------------------| -| `assumption` with high fanout + low confidence | "We depend on X. Want to validate it?" | -| `requirement` with no incoming `proof` edge | "How will we know this holds?" | -| `criterion` with no outgoing `proof` target | "What does this criterion check?" | -| `decision` with empty `rejected` | "What did we consider and rule out?" | -| Conflicting `boundary` edges into same target | "These constraints disagree. Which wins?" | -| `goal` with no derived requirements | "Nothing ties to this goal. What would satisfy it?" | -| `requirement` with no examples + high uncertainty | "What's a concrete case where this matters?" | - -These complement behavioral-kernel signal-phrase routing: kernels -suggest *what kind* of question; topology heuristics suggest *which -item* to ask about next. - -## Source reference - -Rich topology-driven ranking heuristics from the earlier design -are in the archived -`/brunch/docs/design/INTENT_GRAPH_SEMANTICS.md` §Topology-driven -question ranking. Treat as a prompt engineering input. +## Heuristic provenance + +Topology-driven next-question heuristics (look for goals with no derived +requirements, requirements with no examples/proof, decisions with empty rejected +alternatives, conflicting boundaries — ask about the most graph-shaping absence +first) are authored and locked into each `/SKILL.md` body in distilled form +(D97-L: cite/distill, do not copy vocabulary tables). Graph vocabulary itself is +owned by `src/graph/schema/kinds.ts`. This README owns the current lens membership +only — not a parallel copy of the per-lens ranking heuristics. diff --git a/src/.pi/skills/strategies/README.md b/src/.pi/skills/strategies/README.md index ab727b102..d87f3641a 100644 --- a/src/.pi/skills/strategies/README.md +++ b/src/.pi/skills/strategies/README.md @@ -21,31 +21,12 @@ Each `/SKILL.md` file in this directory is a prompt resource the agent - How to structure the turn - How to compose with graph-write methods when commitments are ready -## Observer classification guide (M5 input) - -When `agents-composition-layer` authors the strategy resources, seed each -strategy's prompt with the observer classification rules from -the earlier `INTENT_GRAPH_SEMANTICS.md` translation table: - -| User phrase pattern | Most likely kind | -|----------------------------------|-----------------------| -| "always true that…" | `invariant` | -| "should never…" | `invariant` | -| "for example, when…" | `example` | -| "we wouldn't want…" | `example` (negative) or `constraint` | -| "we don't care about X" | `constraint` | -| "we picked Y over Z because…" | `decision` | -| "we think" / "probably" | `assumption` | -| "the system shall" / "must do" | `requirement` | -| "what outcome are we after?" | `goal` | - -The observer should **abstain** rather than guess when -classification support is weak. - -## Source reference - -Rich classification and translation tables from the earlier -design are in the archived -`/brunch/docs/design/INTENT_GRAPH_SEMANTICS.md` §Observer-prompt -classification guide and §Translation table. Treat as a prompt -engineering input, not a schema target. +## Heuristic provenance + +Phrase-classification and translation heuristics are authored and locked into each +`/SKILL.md` body in distilled form (D97-L: cite/distill, do not copy +vocabulary tables). The canonical "abstain rather than guess on weak classification +support" rule and the contrastive signal-phrase routing live in +`step-wise-disambiguate/SKILL.md` and `step-wise-decision-tree/SKILL.md`; graph +vocabulary itself is owned by `src/graph/schema/kinds.ts`. This README owns the +current axis membership only — not a parallel copy of the per-strategy heuristics. From 2c2b96eaa443fcf7cdc63a18cd8fbc06412d12cf Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Thu, 25 Jun 2026 09:52:27 +0200 Subject: [PATCH 02/54] docs(plan): add initiative (arc) altitude + arc-completion trigger Introduce a thin in-PLAN.md `## Initiatives` index so multi-frontier architectural through-lines stay legible without a new tracker/branch altitude (frontiers remain flat). Seed three arcs: skill-substrate (done), elicitor-capability-spine, context-pipeline. Close the "standing obligation rides the triggering frontier = never" hole: an arc's done-definition must include topology-README reconciliation and residue discharge, and arc completion is itself a trigger. Govern the lifecycle across ln-plan (create/update + sanctioned-exception carve-out), ln-build (fire the done-definition when the last member frontier lands), and ln-sync (reconcile rosters, close only when the done-definition holds). Co-authored-by: Cursor --- .agents/skills/ln-build/SKILL.md | 2 ++ .agents/skills/ln-plan/SKILL.md | 19 ++++++++++++- .agents/skills/ln-sync/SKILL.md | 3 ++ memory/PLAN.md | 48 ++++++++++++++++++++++++++------ 4 files changed, 62 insertions(+), 10 deletions(-) diff --git a/.agents/skills/ln-build/SKILL.md b/.agents/skills/ln-build/SKILL.md index 8c4fdea57..3a90e0832 100644 --- a/.agents/skills/ln-build/SKILL.md +++ b/.agents/skills/ln-build/SKILL.md @@ -111,6 +111,7 @@ After the build lands and verification passes, ask: - [ ] Did this retire or create an assumption? - [ ] Did this establish a new seam-level invariant? - [ ] Did this change a frontier-level cross-cutting obligation or verification architecture layer? +- [ ] Did this complete the **last member frontier of an initiative (arc)** in `memory/PLAN.md` §Initiatives? - [ ] Did this change the topology of a directory that owns a `README.md` (moved/renamed/retired files, changed dependency direction, completed or invalidated a migration note, or shipped a state previously described as pending)? ### If all answers are no @@ -138,6 +139,7 @@ Update only the touched traceability items. - If the change closes, blocks, or unblocks a frontier item, reflect that in `Sequencing`, the affected `Frontier Definitions` entry, or `Recently Completed` - If the build changed a frontier-level cross-cutting obligation, update the affected frontier definition explicitly; do not hide the change behind bare traceability IDs - Do not mirror detailed slice/card history into `memory/PLAN.md`; cards live in the scope file under `memory/cards/`. At most, the frontier definition may carry a lightweight `Current execution pointer` listing active scope file path(s). + - **Arc completion:** if this build completed the **last member frontier of an initiative (arc)** in `§Initiatives`, run that arc's **done-definition before** marking the arc done — including reconciling co-located topology READMEs and discharging any standing-obligation residue scoped to the arc (the residue that no future frontier would otherwise touch). Mark the arc `✓ done` only once the done-definition actually holds; if residue remains, the arc stays `◐ active` with the residue named. 2. **Assumptions** - evidence answered it → update to `validated` or `invalidated` diff --git a/.agents/skills/ln-plan/SKILL.md b/.agents/skills/ln-plan/SKILL.md index 16434ce62..8aada4160 100644 --- a/.agents/skills/ln-plan/SKILL.md +++ b/.agents/skills/ln-plan/SKILL.md @@ -8,7 +8,7 @@ argument-hint: "[feature or project area to plan]" Plan the **rolling frontier**, not the whole historical timeline. -`memory/PLAN.md` is the canonical record of what's next. `docs/archive/PLAN_HISTORY.md` is the only sanctioned archive for retired plan history. `memory/cards/` is the sanctioned derivative location for prepared scope cards; one file per concern, named `--.md` (or `dev--.md`, `tooling--.md`, `docs--.md` for non-frontier work). Scope files are not canonical planning state. Do not invent other sidecar plan docs, milestone ledgers, or alternate memory locations without explicit permission. +`memory/PLAN.md` is the canonical record of what's next. `docs/archive/PLAN_HISTORY.md` is the only sanctioned archive for retired plan history. `memory/cards/` is the sanctioned derivative location for prepared scope cards; one file per concern, named `--.md` (or `dev--.md`, `tooling--.md`, `docs--.md` for non-frontier work). Scope files are not canonical planning state. Do not invent other sidecar plan docs, milestone ledgers, or alternate memory locations without explicit permission. The one sanctioned exception is the in-`PLAN.md` `## Initiatives` section (see [§Initiatives (arcs)](#initiatives-arcs)) — it lives inside the canonical file, not as a sidecar, and is not a new tracker/branch altitude. ## Frontier vs slice vocabulary @@ -25,6 +25,7 @@ The vertical-slicing instinct still applies at planning time: frontier items sho Prefer the conflict-resistant mature shape: - `Context` — short rolling narrative for re-entry +- `Initiatives` — *optional*; thin arc index when an architectural through-line spans several frontiers (see [§Initiatives (arcs)](#initiatives-arcs)) - `Sequencing` — small, frequently edited ordering/status references by stable frontier id - `Frontier Definitions` — relatively stable per-frontier definitions keyed by stable id - `Recently Completed` — last 2-3 completed frontier items only @@ -107,6 +108,22 @@ Do not split one frontier item into several new PLAN entries just because execut But do not let anti-fragmentation erase cross-cutting architecture. If a subsystem or mechanism spans multiple frontier items and is not getting its own frontier id, thread it explicitly through the affected frontier definitions as an obligation in Objective, Acceptance, Verification, or a dedicated cross-cutting note. +### Initiatives (arcs) + +Anti-fragmentation keeps the frontier list flat, but a flat list loses the **through-line** of an architectural initiative that deliberately spans several frontiers (e.g. populate-then-weed-then-lock a skill substrate, or build a capability spine across planes). When that happens the initiative's "why" and its "done" survive only as a SPEC decision dependency chain — which records *events*, not a roster — so "was this captured thoroughly?" becomes a reconstruction job, and trailing cleanup can orphan when no future frontier's blast radius touches it. + +An **initiative (arc)** is the answer, and it is **not** a new tracking altitude: frontiers stay 1:1 with Linear issues and branches (`AGENTS.md`), Graphite stacks still mirror frontier dependencies, and arcs get no issue or branch of their own. An arc is a thin legibility + completability index living in the in-`PLAN.md` `## Initiatives` section. Reach for one only when a through-line genuinely spans ≥2 frontiers and would otherwise be invisible; a single-frontier effort does not need an arc. + +Each arc entry stays thin: + +- **id + status** (`✓ done` / `◐ active` / planned) +- **Goals** — the through-line in 1–3 bullets (the user-facing "why") +- **Members** — the frontier ids that compose it, with per-member status +- **Done-definition** — the arc-level completion test, which **must** include reconciliation of co-located topology READMEs and discharge of any standing-obligation residue scoped to the arc +- **Anchors** — the SPEC decision/assumption ids the arc rests on + +`ln-plan` creates and updates arcs (and the member roster as frontiers are added/retired); `ln-sync` closes them and verifies the done-definition actually holds; `ln-build` fires the arc-completion check when a build lands the last member frontier (see each skill). The done-definition is what closes the "standing obligation rides the triggering frontier = never" hole: arc completion is itself a trigger. + ### Sequencing vs definition edits When priorities change, edit `Sequencing` first. Do not move or rewrite frontier definitions merely to reorder work. diff --git a/.agents/skills/ln-sync/SKILL.md b/.agents/skills/ln-sync/SKILL.md index 891b4add0..7f2e8eb92 100644 --- a/.agents/skills/ln-sync/SKILL.md +++ b/.agents/skills/ln-sync/SKILL.md @@ -136,6 +136,7 @@ Rules: - keep dependency diagrams limited to active / next frontier ids - keep enough `Why now / unlocks` context that a fresh thread can understand frontier ordering without reading the full archive - do not archive handoffs, refactor plans, or sync reports +- reconcile the `## Initiatives` (arc) index if present: refresh each arc's member roster and per-member status against `Sequencing`, and **close an arc only when its done-definition actually holds** — including reconciliation of co-located topology READMEs and discharge of any standing-obligation residue scoped to the arc. An arc whose members are all done but whose trailing README/residue cleanup is outstanding is **not** done; keep it `◐ active` with the residue named. Retire a fully-closed arc to a one-line `Recently Completed`-style note (or drop it) rather than carrying its full block indefinitely. ### 5. Drift and ontology check @@ -158,6 +159,7 @@ Scan recent code / commits for: - verification strategy that is present in canonical docs or frontier definitions but absent from `memory/SPEC.md` §Verification Design - chosen module/API shapes or seam obligations from `ln-design` output that active frontier work still depends on - **topology READMEs under `src/**/` out of sync with reality**: SPEC decision IDs cited in a README that this sync just renumbered or retired; named files/modules that have moved, been renamed, or been retired; dependency-direction assertions that no longer match actual imports; layout sketches whose entries no longer match the directory's contents; migration notes describing state that has since shipped or been abandoned (see `AGENTS.md` §topology READMEs) +- **arcs (`## Initiatives`) out of sync**: an arc marked done whose done-definition does not actually hold (outstanding topology-README reconciliation or undischarged residue); an arc roster missing a member frontier that clearly belongs to the through-line; an active multi-frontier through-line visible in the SPEC decision chain but absent from the arc index; a completed arc still carrying a full block long after closure ### 6. Garbage-collect derivative artifacts @@ -204,6 +206,7 @@ Before finishing, perform a cross-skill preservation check: - What cross-cutting obligations would disappear because they are carried only by links, not by live rows or frontier definitions? - Would they know which temporary sweep ledgers are still live, which promoted rows still keep those ledgers open, and why those rows sequence where they do? - Do any topology READMEs under `src/**/` still cite SPEC IDs or describe topology this sync just changed? Reconcile those READMEs as part of the sync, not as a follow-up. +- If a multi-frontier through-line exists, would they see it as an arc in `§Initiatives` with an honest done-definition, or would they have to reconstruct it from the SPEC decision chain? If any answer is non-empty, sync is incomplete. diff --git a/memory/PLAN.md b/memory/PLAN.md index d38149a2e..d8d6c155a 100644 --- a/memory/PLAN.md +++ b/memory/PLAN.md @@ -15,26 +15,55 @@ Brunch-next has delivered the original composition spine: the host, sealed Pi profile, transcript substrate, SQLite graph plane, public RPC, TUI/web observer shape, generalized capture, review-set commitment path, and public-entry ship gate all have evidence. The live plan is no longer organized around the old delivery cut. Active work is now the elicitor capability spine and the remaining hardening frontiers that build on that substrate. -**Elicitor capability spine (D95-L).** The elicitor build-out is organized as **`capture` / `generate` / `project`** over the frozen `strategy` / `lens` / `method` axes (A35-L). +**Active arcs.** Work is organized into multi-frontier **initiatives (arcs)** — see [§Initiatives](#initiatives) for through-lines, member frontiers, and done-definitions: the completed **skill-substrate** arc (populate / weed / lock), the active **elicitor-capability-spine** arc (`capture` / `generate` done, `project` next), and the active **context-pipeline** arc (PULL / PROJECT / COMPOSE locked, RENDER open). -- **`capture` is done** via generalized capture (D80-L-D82-L). -- **`generate` is done through promoted real-model fan-out evidence** (FE-1059): one plane-parameterized `generate-proposal` method, `present_candidates` unstubbed, fan-in as method conduct (`pick` / `synthesize` / `compose`), and promoted I51-L no-write evidence. -- **`project` is next and design-gated** (A33-L): cross-plane derivation may fold into `generate` or may need a distinct surface. -- **`acquire` rides the completed subagent reconciliation substrate** (A34-L), not its own frontier. +**Topology and evidence discipline.** Directory `README.md` files under `src/**` own current topology state. `memory/SPEC.md` owns product contract and architectural decisions; `memory/PLAN.md` owns only rolling frontier state. Scratch probe artifacts under `.fixtures/scratch/` are not durable evidence until reviewed and promoted to `.fixtures/runs/`. + +## Initiatives + + + +### skill-substrate — ✓ done (2026-06-25) + +- **Goals:** (1) populate the skills the elicitor needs; (2) weed dead-code / stub skills; (3) isolate + lock graph schema, descriptions, tips, and heuristics as context. +- **Members:** FE-893, FE-861, FE-898, FE-1052 (all done). +- **Done-definition:** legal skill set sealed by the `.pi/extensions/runtime/state.ts` path list; no dead stubs (the `__fixtures__` sealing fixture excepted); heuristics distilled + locked into `SKILL.md` bodies, not duplicated in topology READMEs. ✓ — final `strategies/` + `lenses/` README reconciliation discharged 2026-06-25 (dead `INTENT_GRAPH_SEMANTICS.md` pointer + stale "M5 input" tables removed). +- **Anchors:** D85-L (axis populate / weed), D97-L (heuristic-provenance lock), A35-L (axes frozen under the capability spine). -**Context-pipeline coverage (D60-L).** PULL and PROJECT are ledgered and locked; COMPOSE is locked except for its explicit full-stack renderer tripwire; **RENDER remains open** as `renderer-golden-coverage` (FE-870). That frontier is a parallel evidence/quality track, never a ship gate. +### elicitor-capability-spine — ◐ active + +- **Goal:** build `capture` / `generate` / `project` over the frozen `strategy` / `lens` / `method` axes (A35-L), on top of the skill-substrate arc. +- **Members:** + - `capture` ✓ done via generalized capture (D80-L–D82-L). + - `generate` ✓ done through promoted real-model fan-out evidence (FE-1059): one plane-parameterized `generate-proposal` method, `present_candidates` unstubbed, fan-in as method conduct (`pick` / `synthesize` / `compose`), promoted I51-L no-write evidence. + - `project` → `elicitor-project` (FE-1085), **next, design-gated** (A33-L): cross-plane derivation may fold into `generate` or need a distinct surface. + - `acquire` rides the completed subagent-reconciliation substrate (A34-L), not its own frontier. +- **Done-definition:** all three capabilities carry promoted real-model evidence; no capability remains a stub or a method-less axis member. Open follow-ups (A32-L fan-in completion, the A1 anti-prompt) are tracked on their assumptions, not as arc blockers. +- **Anchors:** D95-L, D96-L; A31-L–A35-L; I51-L. + +### context-pipeline — ◐ active + +- **Goal:** lock the PULL → PROJECT → RENDER → COMPOSE context pipeline (D60-L). ```text context-pipeline/ ├── PULL graph + session reads ✓ done ├── PROJECT projections/ ✓ done -├── RENDER renderers/ ◐ open: renderer-golden-coverage +├── RENDER renderers/ ◐ open: renderer-golden-coverage (FE-870) └── COMPOSE system-prompts + skills ✓ done* *COMPOSE has one deferred full-stack real-rendered-context tripwire owned by RENDER. ``` -**Topology and evidence discipline.** Directory `README.md` files under `src/**` own current topology state. `memory/SPEC.md` owns product contract and architectural decisions; `memory/PLAN.md` owns only rolling frontier state. Scratch probe artifacts under `.fixtures/scratch/` are not durable evidence until reviewed and promoted to `.fixtures/runs/`. +- **Done-definition:** every pipeline stage closed or owned by a live coverage frontier; the COMPOSE full-stack tripwire discharged by RENDER. `renderer-golden-coverage` is a parallel evidence/quality track, never a ship gate. +- **Anchors:** D60-L; D83-L (RENDER house style). ## Sequencing @@ -61,7 +90,7 @@ context-pipeline/ - `elicitation-gap-guidance` — generative graph-shape analysis for "what next?" gaps; distinct from ranking already-registered gaps. - `structured-exchange-affordance` — recurring discriminant-companion contract lens; most lead fixes landed, but the systemic audit remains available when similar review findings recur. - `spec-structural-relief` — accepted ledger for future SPEC sharding only if a real context-budget/navigation failure trips it. -- **Standing obligations:** `probes-and-transcripts-evolution` and `topology-readmes-and-boundaries` ride the frontier that triggers them; they are not standalone cleanup buckets. +- **Standing obligations:** `probes-and-transcripts-evolution` and `topology-readmes-and-boundaries` ride the frontier that triggers them **or the completion of the arc they belong to** ([§Initiatives](#initiatives) done-definitions); they are not standalone cleanup buckets. When an arc's trailing residue falls outside any future frontier's blast radius, **arc completion is its trigger** — this is the hole that left the `strategies/` + `lenses/` README reconciliation orphaned until 2026-06-25. ### Horizon @@ -158,4 +187,5 @@ rules: candidates never commit graph truth (I51-L) topology READMEs own current subtree state scratch evidence is not durable until promoted to .fixtures/runs/ + an arc (§Initiatives) closes only when its done-definition holds, incl. topology-README reconciliation + residue discharge ``` From be5baea584387c7042922edd8e3e6596080de056 Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Thu, 25 Jun 2026 10:05:49 +0200 Subject: [PATCH 03/54] =?UTF-8?q?feat(graph):=20generate=20kind=E2=86=92ba?= =?UTF-8?q?nd=20ontology=20reference=20+=20drift=20guard?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First materialization of the `_generated/` mechanism (SPEC D87-L clause d): `src/graph/schema/generate-ontology-ref.ts` projects `NODE_KINDS` + `bandsForKind` into a read-only `src/graph/schema/_generated/ontology.md` table. `npm run generate:ontology` writes it; `npm run check:data-model` (wired into `npm run check`) fails on drift from the typed sources (D73-L). The `capture` method now cites the table instead of restating band membership (D97-L). Planning capture (foundation work; branch to be named later): - memory/PLAN.md — data-model-legibility frontier recorded: design verdict (Shape C), first tracer landed, remaining slices (edge/detail tables, authored judgment layer, subtypes→detail remodel) named. - memory/SPEC.md — D87-L (d) flipped deferred → materialized. Co-authored-by: Cursor --- memory/PLAN.md | 26 ++++++ memory/SPEC.md | 2 +- package.json | 4 +- src/.pi/skills/README.md | 2 +- src/.pi/skills/methods/capture/SKILL.md | 2 +- .../__tests__/generate-ontology-ref.test.ts | 27 ++++++ src/graph/schema/_generated/ontology.md | 39 +++++++++ src/graph/schema/generate-ontology-ref.ts | 85 +++++++++++++++++++ 8 files changed, 183 insertions(+), 4 deletions(-) create mode 100644 src/graph/schema/__tests__/generate-ontology-ref.test.ts create mode 100644 src/graph/schema/_generated/ontology.md create mode 100644 src/graph/schema/generate-ontology-ref.ts diff --git a/memory/PLAN.md b/memory/PLAN.md index d8d6c155a..aaedbe229 100644 --- a/memory/PLAN.md +++ b/memory/PLAN.md @@ -81,6 +81,7 @@ context-pipeline/ ### Next - `elicitor-project` (FE-1085) — **design-gated.** Cross-plane derivation (requirements -> design, design -> oracles) remains undesigned under A33-L; run `ln-design` before any scope/build. +- `data-model-legibility` — **active.** Single canonical home for data-model meta-guidance, with closed-vocabulary tables generated from the typed `graph/schema` sources (D97-L). Design verdict landed (Shape C); first tracer landed (generated kind→band table + `check:data-model` drift guard, cited by `methods/capture`). Remaining: edge-category + detail-form tables, the authored judgment layer, and the subtypes→`detail` remodel. - `renderer-golden-coverage` — **active parallel coverage track.** Remaining RENDER work: ``, `renderGraphSeed`, `exchanges/*`, `formatRelatedNodesResult` relocation/repair, and the `brunch print` fork. - `exchange-symmetry-audit` — **earned cleanup.** Delete-oriented audit of the exchange projection/renderer split; not a capability blocker. @@ -119,6 +120,26 @@ context-pipeline/ - D97-L provenance applies: cite ontology/render surfaces, do not copy vocabulary lists into the skill. - **Traceability:** D95-L, D96-L, D97-L / A33-L / I51-L; D60-L. +### data-model-legibility + +- **Name:** Single canonical home for data-model meta-guidance + generation seam +- **Linear:** tbd +- **Branch:** tbd +- **Kind:** structural / design + build +- **Status:** active; design verdict landed (`ln-design`: Shape C — two layers behind one index). First tracer-bullet **landed**: generated kind→band table at `src/graph/schema/_generated/ontology.md` + `check:data-model` drift guard (wired into `npm run check`), cited by `methods/capture`. Load-bearing claim 1 (typed `graph/schema` sources are the closed, importable vocabulary set — D73-L) validated by the landed generator. Remaining: edge-category + detail-form tables, the authored judgment layer (heuristics / promotion / checkability ladder / subtypes verdict), and the subtypes→`detail` remodel review. +- **Certainty:** proving. +- **Current execution pointer:** none active — re-scope the next slice (authored judgment layer, or further generated tables). +- **Objective:** Recover + reconcile the retired `INTENT_GRAPH_SEMANTICS` content into one canonical data-model meta-guidance home; generate the closed-vocabulary tables (planes / kinds / bands / edge-category policy / `detail` schemas) from the typed `graph/schema` sources (un-defers `_generated/`) so heuristics are **cited** (D97-L), not inlined and duplicated across skill bodies. +- **Acceptance:** + - ✓ `ln-design` produced ≥3 module shapes for the home + generation seam with a recommendation (Shape C), before any doc/script. + - The canonical-truth boundary is decided: what is generated from `kinds.ts` / `nodes.ts` / `category-policy.ts` vs authored judgment. (Direction set by Shape C; kind→band table materialized, remaining tables pending.) + - Subtypes/`detail` modelling review: each retired subtype family sorted into `kind` (behavior-bearing), `detail` facet (inert classification), or already-covered; decide whether an inert `detail` facet dimension earns its carrying cost given the kind/band/form machinery already discriminates. + - The two capture gaps are explicitly ruled in or out: constraint/invariant subtype enums; the 8-rung checkability ladder + `strength`. + - Skill bodies cite the new home (D97-L); inlined heuristic copies collapse to one cite-target. + - ✓ A drift guard (`check:data-model`, mirroring `check:skills`, wired into `npm run check`) fails if the generated reference diverges from the typed sources. + - If `ln-design` splits this into recover-doc / build-generator / subtypes-remodel frontiers, create a `data-model-legibility` arc per §Initiatives. +- **Traceability:** D73-L (domain owns vocabulary), D88-L (`detail` form union), D97-L (heuristic provenance); un-defers the `_generated/` deferral in [`src/.pi/skills/README.md`](src/.pi/skills/README.md); relates to `elicitor-project` (A33-L, shared D97-L rule). + ### renderer-golden-coverage - **Name:** Adopt the D83-L context-render house style and lock remaining RENDER-stage surfaces @@ -153,6 +174,11 @@ frontiers: status: design-gated depends_on: elicitor-generate, D95-L, D96-L, I51-L + data-model-legibility + status: active (design landed Shape C; first tracer landed) + depends_on: graph/schema typed sources (kinds.ts, nodes.ts, category-policy.ts), D73-L, D88-L, D97-L + materialized: _generated/ontology.md (src/graph/schema) + check:data-model + renderer-golden-coverage status: active parallel coverage depends_on: context-pipeline PULL+PROJECT, D83-L, D52-L diff --git a/memory/SPEC.md b/memory/SPEC.md index 1c61917a8..3228114f4 100644 --- a/memory/SPEC.md +++ b/memory/SPEC.md @@ -318,7 +318,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c 3. **Gap-reflection conduct belongs to the capture skill, not `review-for-gaps`.** D81-L spawn-on-noticing + close-on-answered is **always-on capture-sweep conduct** (every elicitor turn), so it lives with the D80-L capture skill, not an optionally-selected method. `review-for-gaps` is demoted to the **deliberate-audit sense only** (missing support, contradictions, verification debt). Read/interpret-gap semantics stay on the `read_elicitation_gaps` tool description (tool-local), not duplicated into a skill; the D81-L commitment gradient lives once, in the capture skill, with gap-spawn as its third outlet. 4. **The prompt-content rewrite is design work entangled with live/stubbed seams — not a keyword fossil sweep.** The strategy/method bodies drift and overlap, but audit (2026-06-18) found their suspect tokens are mostly *not* dead history: `tool_meta` is live across every exchange projection; `capture_*` is a live `tool_meta.next` sequencing marker (`present_* → request_* → capture_*`), distinct from the D80-L-retired labeled-prefix capture core; and `present_candidates` + `user_rubric` / `meta_rubric` / `graph_refs` are the **anticipated payload of the live candidate topology stub** (`projections/exchanges/present-candidates.ts`, PLAN-confirmed stubbed), not removable fossils. Only `renderCall` is genuinely unreferenced (confirm against the Pi display API before removal). Rewriting prompt content must reconcile against the candidate stub, the exchange `tool_meta` model, and the D80-L sweep model rather than strip by keyword. Lexicon sweep in the same pass: `elicitation backlog` → `elicitation gaps` / `coverage obligation` (the D65-L rename; the inlined `elicit-expand` posture in `elicitor/SYSTEM.md` still carries the old term after the goal-axis drop relocated it). - Prompt-shape closure (revised 2026-06-22): (a) **`SKILL.md` directory topology is adopted for every strategy/lens/method** because the Agent Skills standard is now the target prompt-resource format; `references/` remains deferred until a concrete skill needs progressive disclosure, and D39-L's code-owned path list remains the availability surface. (b) **`.pi/agents//SYSTEM.md`** is adopted for live and named future bodies; no flat agent-body shape remains open. (c) **`[sub]` sub-agent notation** is deferred until the first real delegated sub-agent lands; no empty sub-agent stubs are introduced. (d) **`_generated/` typed-vocab references** are deferred until a concrete stale-member need appears; if built, they are generated from typed sources, regenerated and drift-checked, and locked separately from authored prompt-resource bodies. Resolved 2026-06-18: the capture home is `methods/capture`, absorbing the former `infer-and-capture` method name; the full D80/D81/D82 conduct body remains FE-861. Depends on: D23-L, D25-L, D26-L, D39-L, D40-L, D58-L, D59-L, D65-L, D73-L, D80-L, D81-L. Refines: D25-L, D26-L, D40-L, D58-L, D59-L. Supersedes: `goal` as an AUTO-able manifest/runtime axis (the "objective axes `strategy`, `lens`, and `goal`" triple in D40-L/D58-L/D59-L → two axes, goal inlined); `propose-graph` / `project-graph` as `strategy`-axis members (D25-L/D26-L list them as strategies); treating gap spawn/close as a `review-for-gaps` method responsibility; `infer-and-capture` as a separate method; treating the `capture_*` / candidate / `tool_meta` prompt-resource references as removable fossils; and the 2026-06-19 deferral of Agent Skills `SKILL.md` topology. + Prompt-shape closure (revised 2026-06-22): (a) **`SKILL.md` directory topology is adopted for every strategy/lens/method** because the Agent Skills standard is now the target prompt-resource format; `references/` remains deferred until a concrete skill needs progressive disclosure, and D39-L's code-owned path list remains the availability surface. (b) **`.pi/agents//SYSTEM.md`** is adopted for live and named future bodies; no flat agent-body shape remains open. (c) **`[sub]` sub-agent notation** is deferred until the first real delegated sub-agent lands; no empty sub-agent stubs are introduced. (d) **`_generated/` typed-vocab references** are **materialized** (first instance: the kind→band table at `src/graph/schema/_generated/ontology.md`, generated by `npm run generate:ontology` from the typed `graph/schema` sources and drift-checked by `npm run check:data-model`, wired into `npm run check`); they are read-only projections locked separately from authored prompt-resource bodies, and prompt resources cite them rather than restating vocabulary (the data-model-legibility frontier owns the expansion to further tables + the authored judgment layer). Current state: [`src/.pi/skills/README.md`](src/.pi/skills/README.md). Resolved 2026-06-18: the capture home is `methods/capture`, absorbing the former `infer-and-capture` method name; the full D80/D81/D82 conduct body remains FE-861. Depends on: D23-L, D25-L, D26-L, D39-L, D40-L, D58-L, D59-L, D65-L, D73-L, D80-L, D81-L. Refines: D25-L, D26-L, D40-L, D58-L, D59-L. Supersedes: `goal` as an AUTO-able manifest/runtime axis (the "objective axes `strategy`, `lens`, and `goal`" triple in D40-L/D58-L/D59-L → two axes, goal inlined); `propose-graph` / `project-graph` as `strategy`-axis members (D25-L/D26-L list them as strategies); treating gap spawn/close as a `review-for-gaps` method responsibility; `infer-and-capture` as a separate method; treating the `capture_*` / candidate / `tool_meta` prompt-resource references as removable fossils; and the 2026-06-19 deferral of Agent Skills `SKILL.md` topology. Depends on: D19-L, D52-L, D60-L, D62-L, D65-L, D75-L. Refines: D60-L (RENDER stage). Supersedes: the ad-hoc `[bracket]`-header + bullet-list render style as the house convention; hand-rolled markdown and tree string generation in renderers; carrying sessions in the `` cwd render. - **D95-L — Elicitor capability spine: `capture` / `generate` / `project` are the three elicitor capabilities, named over the existing skill axes — not a fourth axis or a re-axis.** The elicitor's work decomposes into three capabilities by what each does to the graph: **capture** commits ground material already present in the transcript tail into graph truth (the D80-L banded sweep + D81-L commitment gradient + D82-L acquisition layer, already specced); **generate** proposes new typed graph expressions on a requested plane from grounding plus a lens frame, fanning candidates out and committing the chosen one through review (D96-L); **project** derives nodes on one plane from a subset/plane of the existing graph with connecting cross-plane edges (e.g. requirements→design, design→oracles, A33-L). This is a capability vocabulary layered over D85-L's `strategy` / `lens` / `method` axes (held frozen, A35-L), not a replacement topology: capture is always-on conduct of every elicitor turn (D85-L move 3), while generate and project are method-routed capabilities requested just-in-time and gated only advisorily by capability-readiness (D74-L/D86-L). Background acquisition subagents (D82-L near-future, A34-L) are the `acquire` arm feeding capture, not a fourth capability. Depends on: D74-L, D80-L, D81-L, D82-L, D85-L, D86-L. Supersedes: the proposed `grounding` / `elicitation` / `projection` lifecycle directories as a replacement skill topology. diff --git a/package.json b/package.json index 3e47ae1ca..df22181fd 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,8 @@ "build:pi-assets": "mkdir -p dist/.pi/components/workspace-dialog dist/.pi/agents dist/.pi/skills dist/.pi/extensions/subagents && cp -R src/.pi/components/workspace-dialog/assets dist/.pi/components/workspace-dialog/ && cp -R src/.pi/agents/elicitor src/.pi/agents/explorer src/.pi/agents/orchestrator src/.pi/agents/pi-coder src/.pi/agents/projector src/.pi/agents/researcher src/.pi/agents/reviewer dist/.pi/agents/ && cp -R src/.pi/skills/strategies src/.pi/skills/lenses src/.pi/skills/methods dist/.pi/skills/ && cp src/.pi/extensions/subagents/config.json dist/.pi/extensions/subagents/", "build:web": "vite build", "seed": "tsx src/graph/seed-fixtures.ts", + "generate:ontology": "tsx src/graph/schema/generate-ontology-ref.ts", + "check:data-model": "tsx src/graph/schema/generate-ontology-ref.ts --check", "db:generate": "drizzle-kit generate", "db:studio": "drizzle-kit studio", "test": "vitest --run", @@ -52,7 +54,7 @@ "fmt": "oxfmt", "fmt:check": "oxfmt --check", "fix": "npm run lint:fix && npm run fmt", - "check": "npm run lint && npm run fmt:check && npm run check:skills", + "check": "npm run lint && npm run fmt:check && npm run check:skills && npm run check:data-model", "check:skills": "node scripts/check-ln-skills.mjs", "release": "release-it", "verify": "npm run fix && npm run test && npm run build" diff --git a/src/.pi/skills/README.md b/src/.pi/skills/README.md index a3594a1b4..bf5797578 100644 --- a/src/.pi/skills/README.md +++ b/src/.pi/skills/README.md @@ -37,7 +37,7 @@ The legal set is sealed by the code-owned path list in `.pi/extensions/runtime/s ## Prompt-resource sub-shapes - **`references/` subfiles:** available under the Agent Skills standard when a concrete skill needs progressive disclosure. No empty reference directories are introduced. The first materialized instance is `methods/generate-proposal/references/`, where the shared `SKILL.md` points to plane-specific payloads without advertising those payloads as separate skills. -- **_generated/ typed-vocab references:** deferred until a concrete stale-member need appears, such as an agent relying on a reference whose runtime axis or graph vocabulary members can drift from the TypeScript `kinds.ts` leaves. If built, these files are generated from typed sources, regenerated and drift-checked, and locked separately from the authored prompt-resource body lock below. +- **_generated/ typed-vocab references:** materialized at `src/graph/schema/_generated/ontology.md` (kind→band table), the schema-owned home for vocabulary that prompt resources cite rather than restate (D97-L). Generated from the typed sources via `npm run generate:ontology` and drift-checked by `npm run check:data-model` (wired into `npm run check`); read-only and locked separately from the authored prompt-resource body lock below. Further vocabulary tables (edge categories, detail forms) are added to that file when a concrete citing need appears, not speculatively. ## Prompt-resource body lock ledger diff --git a/src/.pi/skills/methods/capture/SKILL.md b/src/.pi/skills/methods/capture/SKILL.md index bc1ba3af4..cad40b37e 100644 --- a/src/.pi/skills/methods/capture/SKILL.md +++ b/src/.pi/skills/methods/capture/SKILL.md @@ -21,7 +21,7 @@ chain capture-then-ask: ## Sweep frame -Walk the un-swept material once by readiness band and likely node kind. Conversational answers, ordinary user text, and acquisition digests are all sweep inputs. Large raw reads or tool results should be digested first; capture from the digest plus the conversation, not from unbounded raw bulk. +Walk the un-swept material once by readiness band and likely node kind. The canonical band order and per-kind band membership are the generated kind→band table in `src/graph/schema/_generated/ontology.md` (projected from the typed schema — cite it, do not restate it; D97-L). Conversational answers, ordinary user text, and acquisition digests are all sweep inputs. Large raw reads or tool results should be digested first; capture from the digest plus the conversation, not from unbounded raw bulk. Use the graph, gap, and reconciliation tools as the mutation boundary: diff --git a/src/graph/schema/__tests__/generate-ontology-ref.test.ts b/src/graph/schema/__tests__/generate-ontology-ref.test.ts new file mode 100644 index 000000000..aafe0a11b --- /dev/null +++ b/src/graph/schema/__tests__/generate-ontology-ref.test.ts @@ -0,0 +1,27 @@ +import { readFileSync } from 'node:fs'; + +import { describe, expect, it } from 'vitest'; + +import { GENERATED_ONTOLOGY_PATH, renderOntologyReference } from '../generate-ontology-ref.js'; +import { NODE_KINDS } from '../kinds.js'; +import { bandsForKind } from '../nodes.js'; + +describe('ontology reference generator', () => { + const markdown = renderOntologyReference(); + const rows = markdown.split('\n').filter((line) => line.startsWith('| ')); + + it('lists every node kind with its exact bands from the typed source', () => { + for (const kind of NODE_KINDS) { + const bands = bandsForKind(kind); + const expected = bands.length > 0 ? bands.join(', ') : '—'; + const row = rows.find((line) => line.startsWith(`| ${kind} |`)); + expect(row, `row for kind ${kind}`).toBeDefined(); + expect(row).toContain(expected); + } + }); + + it('keeps the committed generated file in sync with the typed source (drift guard)', () => { + const committed = readFileSync(GENERATED_ONTOLOGY_PATH, 'utf8'); + expect(committed).toBe(markdown); + }); +}); diff --git a/src/graph/schema/_generated/ontology.md b/src/graph/schema/_generated/ontology.md new file mode 100644 index 000000000..2358d57ac --- /dev/null +++ b/src/graph/schema/_generated/ontology.md @@ -0,0 +1,39 @@ + + +# Ontology reference (generated) + +Projected from `src/graph/schema/kinds.ts` and `nodes.ts` — the source of truth +for graph vocabulary (D73-L). Regenerate with `npm run generate:ontology`; drift +is caught by `npm run check:data-model`. Do not hand-edit. + +## Node kind → readiness band + +Kinds appear in canonical plane order (intent, oracle, design, plan). A band +cell of `—` means the kind carries no readiness band (D94-L). + +| Kind | Code | Readiness bands | +| --- | --- | --- | +| goal | G | grounding | +| thesis | TH | grounding | +| term | T | — | +| context | CTX | grounding, elicitation | +| story | ST | elicitation | +| unknown | UNK | elicitation | +| requirement | REQ | commitment | +| assumption | A | elicitation | +| constraint | CON | grounding, elicitation | +| invariant | INV | elicitation | +| decision | D | elicitation | +| criterion | AC | commitment | +| example | EX | — | +| check | CH | projection | +| vv_method | VV | projection | +| evidence | E | projection | +| vv_obligation | O | projection | +| module | MOD | projection | +| interface | API | projection | +| entity | ENT | projection | +| sketch | SKT | — | +| milestone | M | commitment | +| frontier | F | commitment | +| slice | S | commitment | diff --git a/src/graph/schema/generate-ontology-ref.ts b/src/graph/schema/generate-ontology-ref.ts new file mode 100644 index 000000000..f3cce413f --- /dev/null +++ b/src/graph/schema/generate-ontology-ref.ts @@ -0,0 +1,85 @@ +/** + * Generated ontology reference — projects the typed graph/schema vocabulary + * into a read-only Markdown table so prompt resources can cite it instead of + * restating it (D97-L). The typed sources in this directory stay the source of + * truth (D73-L); the emitted file is never hand-edited. + * + * First materialization of the `_generated/` mechanism deferred in + * `src/.pi/skills/README.md`. This slice generates only the kind→band table; + * further vocabulary tables (edge categories, detail forms) are added when a + * concrete citing need appears, not speculatively. + * + * CLI (dev only, run via tsx): + * npm run generate:ontology # write src/graph/schema/_generated/ontology.md + * npm run check:data-model # exit non-zero if the committed file is stale + */ + +import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { NODE_KINDS } from './kinds.js'; +import { bandsForKind, NODE_KIND_METADATA } from './nodes.js'; + +const SCHEMA_DIR = dirname(fileURLToPath(import.meta.url)); + +export const GENERATED_ONTOLOGY_PATH = join(SCHEMA_DIR, '_generated', 'ontology.md'); + +const GENERATED_NOTICE = + ''; + +export function renderOntologyReference(): string { + const rows = NODE_KINDS.map((kind) => { + const bands = bandsForKind(kind); + const cell = bands.length > 0 ? bands.join(', ') : '—'; + return `| ${kind} | ${NODE_KIND_METADATA[kind].label} | ${cell} |`; + }); + + return [ + GENERATED_NOTICE, + '', + '# Ontology reference (generated)', + '', + 'Projected from `src/graph/schema/kinds.ts` and `nodes.ts` — the source of truth', + 'for graph vocabulary (D73-L). Regenerate with `npm run generate:ontology`; drift', + 'is caught by `npm run check:data-model`. Do not hand-edit.', + '', + '## Node kind → readiness band', + '', + 'Kinds appear in canonical plane order (intent, oracle, design, plan). A band', + 'cell of `—` means the kind carries no readiness band (D94-L).', + '', + '| Kind | Code | Readiness bands |', + '| --- | --- | --- |', + ...rows, + '', + ].join('\n'); +} + +function runCli(argv: readonly string[]): number { + const next = renderOntologyReference(); + + if (argv.includes('--check')) { + let current = ''; + try { + current = readFileSync(GENERATED_ONTOLOGY_PATH, 'utf8'); + } catch { + current = ''; + } + if (current !== next) { + console.error('ontology reference is stale or missing; run `npm run generate:ontology`'); + return 1; + } + console.log('ontology reference in sync with typed sources'); + return 0; + } + + mkdirSync(dirname(GENERATED_ONTOLOGY_PATH), { recursive: true }); + writeFileSync(GENERATED_ONTOLOGY_PATH, next); + console.log(`wrote ${GENERATED_ONTOLOGY_PATH}`); + return 0; +} + +if (import.meta.url === `file://${process.argv[1]}`) { + process.exitCode = runCli(process.argv.slice(2)); +} From 161757036fdeffed8f03339a3daaef88b9a91dfd Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Thu, 25 Jun 2026 10:10:48 +0200 Subject: [PATCH 04/54] =?UTF-8?q?docs(sync):=20ln-sync=20canonical=20pass?= =?UTF-8?q?=20=E2=80=94=20retire=20A30-L,=20reconcile=20graph=20schema=20t?= =?UTF-8?q?opology?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Retire validated A30-L (structured-exchange request-side collapse; residue in D84-L/D86-L/I23-L) into the SPEC sync ledger. Reconcile src/graph/README.md to name the generate-ontology-ref.ts projector and _generated/ontology.md reference added this session, with the check:data-model drift guard. Co-authored-by: Cursor --- memory/SPEC.md | 3 +-- src/graph/README.md | 13 ++++++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/memory/SPEC.md b/memory/SPEC.md index 3228114f4..ce43d844f 100644 --- a/memory/SPEC.md +++ b/memory/SPEC.md @@ -95,7 +95,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c ### Open Assumptions - + | # | Assumption | Confidence | Status | Depends on | Validation approach | | --- | --- | --- | --- | --- | --- | @@ -116,7 +116,6 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c | A22-L | The elicitor can perform synchronous post-exchange capture well enough for the POC: high-confidence extractive facts can be committed to the graph immediately and gap dispositions updated, while low-confidence implications can be kept out of graph truth and used as disambiguation material. | medium | partially validated | D18-L, D26-L, D45-L, D65-L, I30-L | 2026-06-05 `capture-response-to-graph` validated the product wiring for narrow labeled text facts (`Goal:`, `Context:`, `Constraint:`, `Criterion:`) on `session.submitExchangeResponse`. 2026-06-07 generalized the same explicit-text capture core onto `session.submitMessage`: ordinary labeled user text now appends to transcript truth, commits through `graph/capture` → `CommandExecutor.mutateGraph({createBasis: explicit, ops})`, targets the transcript binding's spec, and publishes graph invalidations; explicit interruptions are transcript-visible but do not capture or silently answer a pending exchange. 2026-06-08 `capture-quality-spike` added a fixed scenario measurement over free prose, file/ref-bearing prose, and implication-heavy prose; the sample extraction report reached precision 1.0 / recall 1.0 with zero false commits, moving generalized capture from parked evidence-gate to a narrow graduate recommendation with an explicit false-commit guard. 2026-06-12 FE-861 grill committed the architecture this assumption now bets on (D80-L banded capture sweep, D81-L commitment gradient, D82-L acquisition/digest layer); the deterministic labeled-prefix core, its submit-path wiring, and the `capture-response-to-graph` / `submit-message-capture` proofs were retired 2026-06-19 (deleted; their 2026-06-05/06-07 evidence is now historical), and the spike matrix re-aimed at the low-confidence line at probe tier. 2026-06-19 the FE-861 routing-gate slices landed the deterministic false-commit guard over the real `mutate_graph` + `update_elicitation_gaps` + `update_reconciliation_needs` adapters: fixed explicit/implicit commits become graph truth, low-confidence noticings map to exactly one existing-or-new gap, contradictions map to one `semantic_conflict` reconciliation need, structural gaps derive answered, manual gap close uses the graph clock, illegal batches fail loud at `CommandExecutor`, and the closed capture-quality-spike scenario family is re-aimed to gradient `expectedOutcome` rows with every scenario class guarded. Open subclaims now: live sweep/window conduct quality and digest quality for bulk acquisition. | | A25-L | Tracking the latest `pi-coding-agent` release continuously (via source-alias in dev + package dependency bumps) keeps Brunch adaptable without routinely destabilizing it, because Brunch's pi product-behavior surface is concentrated in a few sealed integration seams (the `src/.pi/` extension bundle and the session/runtime adapters) behind the D39-L profile — even though pi *types* are imported across ~25 files, those are mostly type-only and pass through that small set of seams. | medium | partially validated | D67-L | 2026-06-09 FE-825 bumped Brunch to pi 0.79, kept type/default resolution on installed `dist`, added a `PI_SOURCE`-gated vite/vitest runtime alias to sibling `pi-mono` source, preserved product default sealed-profile/offline behavior, and passed `npm run verify`. Each later pi bump that lands without product-behavior regressions raises confidence; a bump that silently breaks sealed-profile assumptions falsifies it. | | A27-L | Gap satisfaction is expressible band-by-band at acceptable LLM cost: **commitment** typologies are structural `presence`/`field`/`coverage` predicates over the graph; **grounding** typologies are a `presence` floor plus `manual` LLM satisficiency (D57-L); **elicitation** typologies are generatively spawned. The explicit `capability → relevant gaps` map (D74-L) carries enough signal to drive proceed / negotiate without a standing grade. | medium | partially validated | D65-L, D74-L, D75-L | 2026-06-10 `elicitation-gaps-remodel` validated the structural `presence` case: a seeded grounding gap's derived coverage/answered state flips from graph truth with no stored structural answer and sibling-spec isolation holds. 2026-06-10 the `capability-readiness` D74-L gate tracer validated the grounding floor: the explicit capability→gap map drives proceed / proceed_low_epistemic / negotiate, live presence coverage flips a generative capability negotiate→proceed, and the gate imports no grade symbols. 2026-06-10 `gaps-node-kind-reference` collapsed that map onto `NodeKind` (`context`/`thesis`/`goal`/`constraint`), proved required-kind absence fails loud, and proved same-kind gaps discriminate by question+satisfier rather than typology name. 2026-06-10 the `capability-readiness` affordance-legality slice validated the affordance-path consumer: the runtime affordance projection (`affordances` / `axisOptionsForRuntimeState`) derives goal/strategy/lens menu legality from `evaluateCapabilityReadiness` over gap coverage with no grade symbols, a coverage flip moves a gated option legal, and a required kind absent from the register fails loud (config bug ≠ uncovered) — retiring the affordance-path uncertainty. 2026-06-10 the method/manifest legality slice validated the turn-boundary consumer: `before_agent_start` reads selected-spec gaps through the graph read seam, prompt manifests and active tool names derive gated methods from gap coverage, floor methods/tools remain available at zero coverage, and the `state.ts` grade tables are gone. 2026-06-10 the agent-prompt display slice validated the display consumer: `compose.ts` and `contexts/cwd.ts` render the selected-spec soft per-band estimate from gaps with stable band order/fixed decimals, and `before_agent_start` threads the same selected-spec gaps into the pushed cwd context. 2026-06-11 the review-fix remediation hardened the predicate substrate: `gapPredicateSupport` (in the union's owning schema module) is the single never-checked owner of per-arm semantics — `field`/`coverage` now **reject loudly at the CommandExecutor boundary** until derivation exists (a structural arm without derivation also fails loud at read), open presence gaps dedupe by `(specId, nodeKind)` (presence is a kind-floor obligation; situated same-kind gaps use `manual` until `field`/`coverage` land), and gap hydration fails on `predicate_kind`/JSON divergence. 2026-06-11 the prompt-authority follow-on validated the negotiation line: readiness-thin pinned goal/strategy/lens selections remain visible in manifests, while gated methods stay withheld and prompt composition no longer throws on a readiness negotiation. Remaining proof: `field`/`coverage` predicate derivation, `manual` LLM satisficiency, elicitation/commitment fixtures, and capability-map refinement beyond the shared grounding floor. Falsified if grounding readiness cannot decompose into per-typology presence+manual judgments, or if commitment obligations need logic the predicate union can't express. | -| A30-L | The structured-exchange request-side collapse can share one answer-source dispatcher across the TUI editor and live broker for `present_question` without losing D86-L editor precedence or D84-L broker fallback semantics. | high | validated — single terminal `request_response` serves `present_question` (answer/choice/choices) and `present_review_set` (review); the broker fallback covers the answer kind only, choice/multi/review stay TUI-only | D84-L, D86-L, I23-L | 2026-06-23 `structured-exchange-affordance` collapsed the request side to one terminal `request_response`: it finds the pending present from the session transcript and dispatches by the pending present tool — `present_question` through the extracted editor→broker→unavailable answer source plus the TUI-only choice/multi sources, `present_review_set` through the extracted review source (approve/request-changes/reject, required change comment) — and the retired `request_answer`/`request_choice`/`request_choices`/`request_review` tools survive only as emitted result-detail discriminants. Unit coverage proves editor, broker, cancellation, unknown exchange, headless-unavailable, recovery continuation, and review approve/change/reject/cancel/unavailable. | | A31-L | The `generate` capability is genuinely one shared spine across the intent, design, and oracle planes — the fan-out → compare → fan-in shape plus grounding/lens framing are plane-invariant enough that a plane parameter and plane-keyed skill content suffice, without per-plane skills. | medium | partially validated | D95-L, D96-L | 2026-06-24: the second plane (design) landed as plane-keyed content in the same `generate-proposal` skill with no new skill, tool grant, state branch, or schema fork. Later 2026-06-24 the oracle plane also authored cleanly as plane-keyed content under the same shared spine, with `references/{intent,design,oracle}.md` progressive disclosure and no new skill/tool/state branch. Promoted run `.fixtures/runs/generate-fan-out/2026-06-24T16-51-13-704Z/` witnessed the live oracle fan-out half on `openai-codex/gpt-5.5`: oracle lens pinned, `generate-proposal/SKILL.md` read, `references/oracle.md` read after the skill, `present_candidates` emitted, no pre-prompt kick, and no graph write. Remaining proof: anti-prompts route away from generate; full fan-in completion is owned by A32-L. | | A32-L | Fan-in across all generative planes collapses to the three-value `pick` / `synthesize` / `compose` mode carryable by `present_candidates` + the review-set path (D27-L), without a plane needing a fourth disposition or a bespoke commit path. | medium | partially validated | D96-L; I51-L | 2026-06-24: design-plane `synthesize` proved expressible as method conduct over `present_candidates` recognition followed by a synthesized `present_review_set` commit review, with no `fan_in_mode` field. Later 2026-06-24 the oracle-plane `compose` facet was expressible as additive ensemble composition in reasoning followed by `present_review_set → request_response → acceptReviewSet`, still with no `fan_in_mode`, multi-select affordance, or bespoke commit path. The promoted fan-out run proves the oracle branch reaches `present_candidates` and preserves I51-L before any pick; it deliberately does not prove same-turn `request_response → present_review_set` completion. Remaining proof: manual/live oracle-compose fan-in evidence; if it needs multi-select in practice, that falsifies the no-field claim and routes a schema/affordance slice. | | A33-L | The `project` capability (cross-plane derivation — requirements→design, design→oracles — with connecting edges) has a module shape distinct enough from `generate` to warrant its own `ln-design` pass before build; it is not merely `generate` with a graph-subset input. | medium | open | D95-L | The `project` `ln-design` pass: if it reduces to `generate` parameterized by an upstream-graph input, fold it in; if cross-plane edge derivation + provenance need their own surface, it stays distinct. | diff --git a/src/graph/README.md b/src/graph/README.md index 3030a6c97..d43198b16 100644 --- a/src/graph/README.md +++ b/src/graph/README.md @@ -51,7 +51,12 @@ SPEC decisions: D4-L, D20-L, D27-L, D45-L, D51-L, D52-L, D53-L, D54-L, D60-L, D6 kind/category types, per-kind node ordinals, per-kind node `detail` schemas, derived readiness-band membership (`bandsForKind`), and derived intent-kind grouping. Raw domain enum taxonomy lives in the zero-import `schema/kinds.ts` - leaf so web-facing graph imports do not pull in Drizzle. + leaf so web-facing graph imports do not pull in Drizzle. The kind→readiness-band + ontology is projected from this typed source to a committed reference at + `schema/_generated/ontology.md` by `schema/generate-ontology-ref.ts`; the + `check:data-model` script fails if the committed file drifts from the schema + (D87-L(d), D97-L). Skills cite that generated reference instead of restating + bands. - **Policy** (`policy/category-policy.ts`) — the single per-category metadata table (`EDGE_CATEGORY_METADATA`): endpoint roles, impact @@ -197,6 +202,12 @@ graph/ per-kind detail schema owner consumed by validation + mutation boundary schemas edges.ts reconciliation-need.ts + generate-ontology-ref.ts + projects the typed kind→readiness-band ontology to _generated/ontology.md + --check mode (check:data-model) guards the committed file against drift + _generated/ + ontology.md + @generated kind→band reference; cited by skills, never hand-edited policy/ category-policy.ts From c59f5df62307f3e90107990b5f72e55603e6f382 Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Thu, 25 Jun 2026 14:09:16 +0200 Subject: [PATCH 05/54] adapt to post-ontology-change legacy fixtures --- src/app/pi-settings.ts | 2 +- src/graph/__tests__/command-executor.test.ts | 75 ++++++++++++++++++++ src/graph/command-executor.ts | 72 +++++++++++++++++++ src/graph/command-executor/command-types.ts | 15 ++++ src/graph/workspace-store.ts | 1 + 5 files changed, 164 insertions(+), 1 deletion(-) diff --git a/src/app/pi-settings.ts b/src/app/pi-settings.ts index 6d51681c5..374e23c63 100644 --- a/src/app/pi-settings.ts +++ b/src/app/pi-settings.ts @@ -4,7 +4,7 @@ import { SettingsManager, type ExtensionFactory } from '@earendil-works/pi-codin export const BRUNCH_SETTINGS_POLICY = { quietStartup: true, - defaultProjectTrust: 'never', + defaultProjectTrust: 'never', // TODO: change this? packages: [], extensions: [], skills: [], diff --git a/src/graph/__tests__/command-executor.test.ts b/src/graph/__tests__/command-executor.test.ts index 452067bae..ced3274b1 100644 --- a/src/graph/__tests__/command-executor.test.ts +++ b/src/graph/__tests__/command-executor.test.ts @@ -11,6 +11,7 @@ import { describe, expect, it, beforeEach } from 'vitest'; import { createDb, type BrunchDb } from '../../db/connection.js'; import { changeLog, + edges, elicitationGaps, graphClock, nodeKindCounters, @@ -18,7 +19,9 @@ import { reconciliationNeed, specs, } from '../../db/schema.js'; +import { formatGraphOverview } from '../../renderers/graph/graph-slice.js'; import { CommandExecutor } from '../command-executor.js'; +import { queryGraph } from '../queries.js'; import { runCreateOnlyMutation } from './support/create-only-mutation.js'; function createTestDb(): BrunchDb { @@ -738,6 +741,78 @@ describe('CommandExecutor', () => { }); }); + it('repairs retired edge categories before graph renderers project impact direction', () => { + const source = executor.createNode({ + specId, + plane: 'intent', + kind: 'requirement', + title: 'Requirement', + }); + const target = executor.createNode({ + specId, + plane: 'intent', + kind: 'constraint', + title: 'Constraint', + }); + if (source.status !== 'success' || target.status !== 'success') throw new Error('unreachable'); + + db.insert(edges) + .values([ + { + spec_id: specId, + category: 'support' as never, + source_id: source.nodeId, + target_id: target.nodeId, + stance: 'for', + basis: 'explicit', + created_at_lsn: 0, + updated_at_lsn: 0, + }, + { + spec_id: specId, + category: 'boundary' as never, + source_id: target.nodeId, + target_id: source.nodeId, + basis: 'explicit', + created_at_lsn: 0, + updated_at_lsn: 0, + }, + ]) + .run(); + + expect(() => formatGraphOverview(queryGraph(db, specId, undefined, { visibility: 'all' }))).toThrow( + /affected/u, + ); + + const repair = executor.repairLegacyEdgeCategories(); + + expect(repair).toMatchObject({ + status: 'success', + repairedSpecs: [ + { + specId, + renamedCounts: { + boundary: 1, + support: 1, + }, + }, + ], + }); + expect( + db + .select({ category: edges.category, stance: edges.stance }) + .from(edges) + .all() + .sort((left, right) => left.category.localeCompare(right.category)), + ).toEqual([ + { category: 'exclusion', stance: null }, + { category: 'rationale', stance: 'for' }, + ]); + expect(formatGraphOverview(queryGraph(db, specId, undefined, { visibility: 'all' }))).toContain( + 'edges (sorted by upstream)', + ); + }); + it('repairs a floor-predating spec with the missing manual situating gap', () => { const legacy = executor.createSpec({ name: 'Legacy Spec', slug: 'legacy-spec' }); expect(legacy.status).toBe('success'); diff --git a/src/graph/command-executor.ts b/src/graph/command-executor.ts index 64d1395be..c1a1c809e 100644 --- a/src/graph/command-executor.ts +++ b/src/graph/command-executor.ts @@ -33,6 +33,8 @@ import type { CreateReconNeedResult, CreateSpecInput, CreateSpecResult, + RepairLegacyEdgeCategoriesResult, + RepairLegacyEdgeCategoriesSpecResult, RepairSeededElicitationGapsResult, RepairSeededElicitationGapsSpecResult, ResolveReconNeedInput, @@ -57,6 +59,7 @@ import type { } from './command-executor/graph-mutation-types.js'; import { writeGraphMutation } from './command-executor/graph-mutation-writer.js'; import { translateReviewSetPayloadToMutateGraph } from './review-set.js'; +import type { EdgeCategory } from './schema/edges.js'; import type { ElicitationGapLensAffinity, GapPredicate } from './schema/elicitation-gaps.js'; import type { ReadinessBand } from './schema/kinds.js'; import { type NodeBasis, type NodeKind, type NodePlane } from './schema/nodes.js'; @@ -88,6 +91,8 @@ export type { CreateReconNeedResult, CreateSpecInput, CreateSpecResult, + RepairLegacyEdgeCategoriesResult, + RepairLegacyEdgeCategoriesSpecResult, RepairSeededElicitationGapsResult, RepairSeededElicitationGapsSpecResult, ResolveReconNeedInput, @@ -195,6 +200,16 @@ const SEEDED_ELICITATION_GAPS: readonly { }, ] as const; +const LEGACY_EDGE_CATEGORY_RENAMES: readonly { + readonly from: string; + readonly to: EdgeCategory; +}[] = [ + { from: 'proof', to: 'witness' }, + { from: 'support', to: 'rationale' }, + { from: 'boundary', to: 'exclusion' }, + { from: 'association', to: 'cross_reference' }, +]; + function seededElicitationGapKey(seed: { readonly refersTo: string; readonly question: string; @@ -390,6 +405,63 @@ export class CommandExecutor { }); } + /** Repair local rows written before the D87-L edge-category rename batch. */ + repairLegacyEdgeCategories(): RepairLegacyEdgeCategoriesResult { + return this.db.transaction((tx) => { + const renamedCountsBySpec = new Map>(); + const legacyCategories = new Set(LEGACY_EDGE_CATEGORY_RENAMES.map((rename) => rename.from)); + const edgeRows = tx + .select({ + specId: schema.edges.spec_id, + category: schema.edges.category, + }) + .from(schema.edges) + .all(); + + for (const row of edgeRows) { + const category = String(row.category); + if (!legacyCategories.has(category)) continue; + const counts = renamedCountsBySpec.get(row.specId) ?? new Map(); + counts.set(category, (counts.get(category) ?? 0) + 1); + renamedCountsBySpec.set(row.specId, counts); + } + + if (renamedCountsBySpec.size === 0) { + return { status: 'success' as const, repairedSpecs: [] }; + } + + for (const rename of LEGACY_EDGE_CATEGORY_RENAMES) { + tx.update(schema.edges) + .set({ category: rename.to }) + // The left side is typed as the current enum, but local SQLite rows may + // still contain retired literals from before D87-L. + .where(eq(schema.edges.category, rename.from as EdgeCategory)) + .run(); + } + + const repairedSpecs: RepairLegacyEdgeCategoriesSpecResult[] = []; + for (const [specId, counts] of renamedCountsBySpec) { + const lsn = this.bumpExistingSpecLsn(tx, specId); + const renamedCounts = Object.fromEntries(counts.entries()); + tx.insert(schema.changeLog) + .values({ + spec_id: specId, + lsn, + operation: 'repair_legacy_edge_categories', + payload: JSON.stringify({ + specId, + renamedCounts, + renames: LEGACY_EDGE_CATEGORY_RENAMES, + }), + }) + .run(); + repairedSpecs.push({ specId, renamedCounts, lsn }); + } + + return { status: 'success' as const, repairedSpecs }; + }); + } + /** Create an elicitation gap through the command boundary. */ createElicitationGap(input: CreateElicitationGapInput): CreateElicitationGapResult { const diagnostics = validateCreateElicitationGap(input); diff --git a/src/graph/command-executor/command-types.ts b/src/graph/command-executor/command-types.ts index 7fdc7b130..ea9134d7f 100644 --- a/src/graph/command-executor/command-types.ts +++ b/src/graph/command-executor/command-types.ts @@ -82,6 +82,17 @@ interface RepairSeededElicitationGapsSuccess { readonly repairedSpecs: readonly RepairSeededElicitationGapsSpecResult[]; } +export interface RepairLegacyEdgeCategoriesSpecResult { + readonly specId: number; + readonly renamedCounts: Readonly>; + readonly lsn: number; +} + +interface RepairLegacyEdgeCategoriesSuccess { + readonly status: 'success'; + readonly repairedSpecs: readonly RepairLegacyEdgeCategoriesSpecResult[]; +} + /** Spec row returned by CommandExecutor reads. */ export interface SpecRecord { readonly id: number; @@ -101,6 +112,7 @@ export type CommandResult = | ElicitationGapSuccess | ElicitationGapDispositionSuccess | RepairSeededElicitationGapsSuccess + | RepairLegacyEdgeCategoriesSuccess | StructuralIllegal | NeedsHuman | PolicyBlocked @@ -127,6 +139,9 @@ export type SetElicitationGapDispositionResult = ElicitationGapDispositionSucces /** Result of repairing legacy specs missing the current seeded gap floor. */ export type RepairSeededElicitationGapsResult = RepairSeededElicitationGapsSuccess; +/** Result of repairing local graph rows that predate D87-L edge-category renames. */ +export type RepairLegacyEdgeCategoriesResult = RepairLegacyEdgeCategoriesSuccess; + /** Successful accepted review-set graph batch execution. */ interface AcceptReviewSetSuccess extends MutateGraphSuccess {} diff --git a/src/graph/workspace-store.ts b/src/graph/workspace-store.ts index d0296ca3e..18c7b0eab 100644 --- a/src/graph/workspace-store.ts +++ b/src/graph/workspace-store.ts @@ -50,6 +50,7 @@ export interface WorkspaceGraphRuntime { export async function openWorkspaceGraphRuntime(cwd: string): Promise { const db = await openWorkspaceDb(cwd); const commandExecutor = new CommandExecutor(db); + commandExecutor.repairLegacyEdgeCategories(); commandExecutor.repairSeededElicitationGaps(); return { commandExecutor, From f147f3b00c97b07bc056d40cb4ca7500f778b55f Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Thu, 25 Jun 2026 14:09:27 +0200 Subject: [PATCH 06/54] update readme and plan --- README.md | 71 ++++++----- memory/PLAN.md | 22 ++++ ...orchestrator-tool-port--plan-check-tool.md | 117 ++++++++++++++++++ 3 files changed, 179 insertions(+), 31 deletions(-) create mode 100644 memory/cards/orchestrator-tool-port--plan-check-tool.md diff --git a/README.md b/README.md index 65f31f26f..b983a8f5d 100644 --- a/README.md +++ b/README.md @@ -10,21 +10,13 @@ The current alpha is TUI-first. Running `brunch` opens an interactive terminal U ## Run Brunch -Brunch requires Node 20+ and an Anthropic API key. - -Create a `.env` file in the project directory where you want to use Brunch: - -```bash -printf 'ANTHROPIC_API_KEY=sk-ant-...\n' > .env -``` - -Then launch the alpha from that directory: +Brunch requires Node 24. Launch the alpha from the project directory you want Brunch to use: ```bash npx @hashintel/brunch@alpha ``` -Brunch creates or reuses a local `.brunch/` workspace under the current directory. That workspace holds project-scoped Brunch state, including the selected spec/session, graph persistence, and Pi JSONL-backed transcript data. +Brunch creates or reuses a local `.brunch/` workspace under the current directory. That workspace holds project-scoped Brunch state, including the selected spec/session, graph persistence, and Pi JSONL-backed transcript data. No `.env` file is required for a local/offline launch. Useful launch variants: @@ -34,38 +26,33 @@ npx @hashintel/brunch@alpha # TUI plus browser sidecar launch npx @hashintel/brunch@alpha --open-web - -# JSON-RPC line server for tools and probes -npx @hashintel/brunch@alpha --mode rpc - -# render current workspace state and exit -npx @hashintel/brunch@alpha --mode print ``` -If you prefer a global install: - -```bash -npm install -g @hashintel/brunch@alpha -brunch -``` +Prefer `npx` during the alpha line. Global installs are easy to leave stale while the CLI, local workspace shape, and Pi integration are still moving. ## Environment Variables -| Variable | Required | Description | +Brunch does not own a provider/model/port configuration surface. Provider auth and model selection belong to the embedded Pi runtime, and live model calls use whatever Pi auth is configured on the machine. The model selection forwarded by Brunch agents is `default` ("inherit the parent's current model"), so there is no `ANTHROPIC_MODEL`/`OBSERVER_MODEL` override path. The web sidecar always binds an ephemeral port and prints its URL; there is no `BRUNCH_PORT`. + +Brunch's own environment surface is operational (offline/dev/source flags), not product config: + +| Variable | Default | Description | | --- | --- | --- | -| `ANTHROPIC_API_KEY` | Yes | Anthropic API key for real-provider agent runs. | -| `ANTHROPIC_MODEL` | No | Foreground agent model override. | -| `OBSERVER_MODEL` | No | Background/observer model override where configured. | -| `BRUNCH_PORT` | No | Port for the local web sidecar host. | +| `PI_OFFLINE` | `1` | Brunch defaults this to `1` (offline) around the interactive run. Set `0` only when deliberately exercising live provider calls through configured Pi auth. | +| `PI_SKIP_VERSION_CHECK` | `1` | Defaulted to `1` by Brunch to skip Pi's runtime version check. | +| `BRUNCH_DEV` | — | Set `1` to expose the dev-only `dev.graph.mutateGraph` method in the JSON-RPC dev surface. Absent from discovery otherwise. | +| `BRUNCH_DB` | `./.brunch/data.db` | SQLite path used by `drizzle-kit` tooling only (`db:generate`, `db:studio`). Does not affect the runtime workspace DB, which is resolved per-cwd. | +| `PI_SOURCE` / `PI_SOURCE_ROOT` | — | Dev-only: set `PI_SOURCE=1` to alias the `@earendil-works/pi-*` packages to a local `pi-mono` checkout (default `~/.pi/pi-mono`, overridable via `PI_SOURCE_ROOT`) so source edits apply without rebuilding. Inert unless the checkout exists. | +| `BRAVE_API_KEY` | — | Optional. Enables the Brave-backed web-search extension; absent, web search throws. | ## Product Shape Brunch Alpha is one local host with several presentation surfaces: -- `--mode tui` is the default writer/driver. It embeds the Pi coding-agent runtime, Brunch prompt policy, structured exchange tools, graph tools, workspace/session selection, and TUI chrome. +- The TUI is the default writer/driver. It embeds the Pi coding-agent runtime, Brunch prompt policy, structured exchange tools, graph tools, workspace/session selection, and TUI chrome. - The browser sidecar is served by the TUI process. It is a Brunch React app over Brunch JSON-RPC, currently oriented around read-only graph/session projections. -- `--mode rpc` exposes Brunch product methods over JSON-RPC for probes, integrations, and automation. Clients speak Brunch method names rather than raw Pi commands. -- `--mode print` is a headless one-shot surface for scripting and inspecting workspace state. +- The JSON-RPC surface exposes Brunch product methods for probes, integrations, and automation. Clients speak Brunch method names rather than raw Pi commands. +- The print surface is a headless one-shot path for scripting and inspecting workspace state. The durable product model is graph-native: the intent graph is canonical specification meaning, with oracle, design, and plan graph planes as accountable downstream work. Mutations go through Brunch-owned command/session seams; the browser and probes read named projections instead of reading SQLite, JSONL, or Pi internals directly. @@ -75,7 +62,6 @@ Clone the repository, install dependencies, and run the source CLI: ```bash npm install -printf 'ANTHROPIC_API_KEY=sk-ant-...\n' > .env npm run dev ``` @@ -92,6 +78,29 @@ Common development commands: See [`CONTRIBUTING.md`](./CONTRIBUTING.md) for release flow, fixture workflow, and the current internal source-of-truth documents. +## Run Against Seeded Workbenches + +`npm run dev` runs the CLI from TS source against whatever cwd you point it at; it **never seeds by implication**. For realistic local development you seed a named workbench under `.fixtures/workbenches//` with explicit graph fixtures, then launch Brunch against that cwd. The workbench's `.brunch/` state (`data.db`, sessions) scaffolds inside that directory and is gitignored, so seeded state never mixes with the repo root. + +Seed a chosen graph into a workbench, then launch from the repo root: + +```bash +# Seed one tracked fixture into a named workbench (--reset wipes prior runtime state first) +npm run seed -- --workspace .fixtures/workbenches/live-graph-observer --seed workspace-spread/alpha-grounding --reset + +# Interactive TUI writer + read-only web sidecar against that workbench +npm run dev -- --cwd .fixtures/workbenches/live-graph-observer + +# Open the sidecar in a browser as well +npm run dev -- --cwd .fixtures/workbenches/live-graph-observer --open-web +``` + +Seed selection is `/` from `.fixtures/seeds/` (see [`.fixtures/seeds/README.md`](./.fixtures/seeds/README.md) for the disposition catalog). Use `--all-seeds` instead of `--seed` only when you deliberately want every tracked fixture loaded as its own spec; a bare `npm run seed` fails with usage rather than seeding the shell cwd. + +For agent-addressable inspection or curation over JSON-RPC, use the seeded-RPC walkthrough in [`docs/testing/seeded-dev-rpc.md`](./docs/testing/seeded-dev-rpc.md). Keep one writer per workspace: do not run concurrent dev RPC writes and a TUI/agent session against the same cwd unless you are deliberately testing concurrency. + +Live provider runs require `PI_OFFLINE=0` plus configured Pi auth; otherwise the default offline launch exercises the workspace, graph, and UI paths without reaching a provider. + ## Source Overview The `src/` topology follows the current architecture decision in [`memory/SPEC.md`](./memory/SPEC.md). Directory-level `README.md` files under `src/**/` are canonical for the boundaries they describe. diff --git a/memory/PLAN.md b/memory/PLAN.md index aaedbe229..3ef992184 100644 --- a/memory/PLAN.md +++ b/memory/PLAN.md @@ -80,6 +80,7 @@ context-pipeline/ ### Next +- `orchestrator-tool-port` (FE-1087) — **scoped.** Port the external `brunch cook` orchestrator into execute-mode tools without granting the foreground orchestrator direct shell/file-write authority. First active scope: `memory/cards/orchestrator-tool-port--plan-check-tool.md`. - `elicitor-project` (FE-1085) — **design-gated.** Cross-plane derivation (requirements -> design, design -> oracles) remains undesigned under A33-L; run `ln-design` before any scope/build. - `data-model-legibility` — **active.** Single canonical home for data-model meta-guidance, with closed-vocabulary tables generated from the typed `graph/schema` sources (D97-L). Design verdict landed (Shape C); first tracer landed (generated kind→band table + `check:data-model` drift guard, cited by `methods/capture`). Remaining: edge-category + detail-form tables, the authored judgment layer, and the subtypes→`detail` remodel. - `renderer-golden-coverage` — **active parallel coverage track.** Remaining RENDER work: ``, `renderGraphSeed`, `exchanges/*`, `formatRelatedNodesResult` relocation/repair, and the `brunch print` fork. @@ -104,6 +105,22 @@ context-pipeline/ ## Frontier Definitions +### orchestrator-tool-port + +- **Name:** Port cook orchestrator into execute-mode tools +- **Linear:** [FE-1087](https://linear.app/hash/issue/FE-1087/port-cook-orchestrator-into-execute-mode-tools) +- **Branch:** tbd +- **Kind:** structural / execute-mode tool boundary +- **Status:** scoped; first scope file active. +- **Certainty:** proving. +- **Current execution pointer:** `memory/cards/orchestrator-tool-port--plan-check-tool.md`. +- **Objective:** Replace the execute-mode standup stub with real orchestrator tooling by porting reusable `brunch cook` core logic into product-owned modules and exposing it through thin `.pi/extensions` adapters, while preserving the orchestrator foreground agent's no-direct-`bash` / no-direct-`edit` / no-direct-`write` authority. +- **Acceptance:** + - First tracer replaces `orchestrator_stub` with a read-only `cook_plan_check` tool that validates a cook plan and returns typed plan shape/findings without creating a run sandbox. + - Later `cook_run` tooling is bounded behind orchestrator-owned sandbox/worktree machinery; write-capable worker sessions, if any, are code-owned child execution boundaries, not foreground-agent direct tools. + - External `../brunch` CLI behavior is ported as reusable product core plus Pi adapter, not wrapped as a shell command. +- **Traceability:** D39-L, D40-L, D90-L, D91-L, D92-L, D93-L / I49-L; `src/.pi/extensions/README.md`. + ### elicitor-project - **Name:** Elicitor `project` capability — cross-plane derivation @@ -170,6 +187,11 @@ frontiers: none Next: + orchestrator-tool-port + status: scoped + depends_on: D39-L, D90-L, D91-L, D92-L, D93-L, I49-L + active_scope: memory/cards/orchestrator-tool-port--plan-check-tool.md + elicitor-project status: design-gated depends_on: elicitor-generate, D95-L, D96-L, I51-L diff --git a/memory/cards/orchestrator-tool-port--plan-check-tool.md b/memory/cards/orchestrator-tool-port--plan-check-tool.md new file mode 100644 index 000000000..551a19485 --- /dev/null +++ b/memory/cards/orchestrator-tool-port--plan-check-tool.md @@ -0,0 +1,117 @@ +# Orchestrator Plan Check Tool + +Frontier: orchestrator-tool-port +Status: active +Mode: single +Created: 2026-06-25 + +## Orientation + +- Containing seam: `execute` mode's foreground `orchestrator` agent and the `.pi/extensions` adapter boundary; this slice replaces the standup stub with the first real cook-orchestrator tool. +- Relevant frontier item: `orchestrator-tool-port` / FE-1087, inherited as the Linear issue and branch boundary from `memory/PLAN.md`. +- Volatile handoff state: none in `HANDOFF.md` (absent); source context comes from the prior port analysis and the external `../brunch` orchestrator docs/source. +- Main open risk: accidentally importing the CLI's execution side effects before the read-only tool boundary is proved; preserve the D39-L sealed profile and D90-L-D93-L/I49-L code-owned authority model. + +Posture: proving (inherited from `orchestrator-tool-port`) + +## Target Behavior + +The execute-mode orchestrator can inspect a cook plan through a product-registered, read-only `cook_plan_check` tool whose result contains plan shape plus contract findings. + +## Full-card cold-start reads + +- `memory/SPEC.md` — decisions / invariants: D39-L, D40-L, D90-L, D91-L, D92-L, D93-L, I49-L. +- `memory/PLAN.md` — frontier: `orchestrator-tool-port`. +- `src/.pi/extensions/README.md` — adapter-only ownership and boundary rules. +- `src/.pi/agents/orchestrator/SYSTEM.md` — current execute-mode foreground prompt and stub wording to retire. +- `src/projections/session/runtime-policy.ts` — `execute` foreground roster and blocked direct tool policy. +- `src/session/schema/tool-names.ts` — shared tool-name constants. +- `/Users/lunelson/Code/hashintel/brunch/ORCHESTRATOR.md` — source CLI behavior and plan format. +- `/Users/lunelson/Code/hashintel/brunch/src/orchestrator/src/{types.ts,plan-loader.ts,plan-contract.ts,cook-cli.ts}` — portable plan model, loader, contract, and plan-resolution behavior to adapt. + +## Boundary Crossings + +```text +→ execute-mode foreground `orchestrator` prompt +→ runtime policy tool grant / block list +→ `.pi/extensions/orchestrator` Pi tool adapter +→ product-owned `src/orchestrator` plan loader + contract core +→ workspace cook plan path +→ typed Pi tool result content/details +``` + +## Risks and Assumptions + +- RISK: CLI code pulls in process exits, git worktree creation, model auth, or child Pi sessions too early → MITIGATION: port only pure/read-only plan loading and contract checking in this slice; no sandbox, engine, Petrinaut stream, or worker session imports. +- RISK: The foreground `orchestrator` gains accidental write authority while replacing the stub → MITIGATION: keep `bash`, `edit`, and `write` blocked in `runtime-policy.ts`; register only the read-only `cook_plan_check` tool for this card. +- RISK: External source names leak as temporary compatibility aliases → MITIGATION: canonicalize the product-facing tool name now; delete the `orchestrator_stub` tool path when the real tool is registered. +- ASSUMPTION: The external cook plan contract is the right first tracer boundary for the port. + → IMPACT IF FALSE: the later `cook_run` surface may need a different plan source/result model, but this slice's blast radius is limited to read-only validation and prompt/tool naming. + → VALIDATE: focused tests over valid, malformed, and design-invalid plan fixtures plus runtime-policy assertions. + +## Posture check + +This is a proving tracer. It scores on proof of life by making execute mode call real orchestrator-derived product code, on invariants by locking the foreground no-direct-write boundary while still exposing orchestration capability, and on uncertainty by testing that the external `brunch cook` plan contract can be ported without shell-wrapping the CLI. + +No separate spike is cheaper than this slice: the useful proof is whether the product registry, prompt, runtime policy, and plan contract all line up through the real execute-mode tool boundary. + +## Acceptance Criteria + +✓ `cook_plan_check` is product-registered for execute mode and returns a typed result for a valid plan path containing mode, epic count, slice count, policy-relevant findings, and source path. +✓ Invalid or contract-failing plans return deterministic typed findings/errors without creating `.brunch/cook/runs`, git worktrees, Petrinaut artifacts, or child Pi sessions. +✓ `orchestrator_stub` is no longer advertised to the foreground orchestrator, and the old stub registration path is retired. +✓ `runtime-policy.ts` still blocks direct `bash`, `edit`, and `write` for `execute`, with tests or assertions covering the new tool grant. +✓ `src/.pi/agents/orchestrator/SYSTEM.md` tells the foreground agent to use the real plan-check tool and preserves the no-direct-write instruction. + +## Verification Approach + +- Inner: focused unit/contract tests — plan loader/contract result shape, tool execution result, runtime-policy grant/block invariants. +- Middle: `npm run fix` — project lint/format after edits. +- Gate: `npm run verify` — full fix/test/build before tying off the branch. + +## Cross-cutting obligations + +- Preserve D39-L sealed-profile discipline: no ambient Pi discovery, dynamic import scanning, or shell-wrapped CLI escape hatch. +- Preserve D90-L-D93-L/I49-L authority: foreground `orchestrator` remains low-privilege; any future write-capable worker must be code-owned and explicitly allowlisted. +- Keep `.pi/extensions` adapter-only: reusable plan-contract logic belongs in product core, not hidden extension memory. +- Treat `.brunch/cook/runs/` as an execution artifact for later `cook_run`, not an artifact this read-only slice creates. + +## Expected touched paths (tentative) + +```text +memory/ +├── PLAN.md ~ +└── cards/ + └── orchestrator-tool-port--plan-check-tool.md + +src/ +├── orchestrator/ +│ ├── plan-contract.ts + +│ ├── plan-loader.ts + +│ ├── types.ts + +│ └── __tests__/ +│ └── plan-check.test.ts + +├── .pi/ +│ ├── agents/ +│ │ └── orchestrator/ +│ │ └── SYSTEM.md ~ +│ ├── extensions/ +│ │ ├── README.md ~ +│ │ ├── orchestrator/ + +│ │ │ ├── index.ts + +│ │ │ └── __tests__/ +│ │ │ └── orchestrator-tool.test.ts + +│ │ └── orchestrator-stub/ - +│ └── __tests__/ ? +├── app/ +│ └── pi-extensions.ts ~ +├── projections/ +│ └── session/ +│ ├── runtime-policy.ts ~ +│ └── __tests__/ +│ └── runtime-policy.test.ts ? +└── session/ + └── schema/ + └── tool-names.ts ~ +package.json ? +package-lock.json ? +``` From 497056a5131e5bea1ee78a4c0ae0c09635913870 Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Thu, 25 Jun 2026 14:14:34 +0200 Subject: [PATCH 07/54] fix: keep verify gate clean Refresh stale prompt-shape assertions and preserve the Gherkin then field without triggering thenable lint warnings. Co-authored-by: Cursor --- src/.pi/__tests__/prompt-shape-readmes.test.ts | 4 ++-- src/graph/__tests__/command-executor.test.ts | 3 ++- src/graph/__tests__/mutate-graph-edge-schema.test.ts | 3 ++- src/graph/schema/nodes.ts | 4 +++- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/.pi/__tests__/prompt-shape-readmes.test.ts b/src/.pi/__tests__/prompt-shape-readmes.test.ts index 46861620c..b3758eb84 100644 --- a/src/.pi/__tests__/prompt-shape-readmes.test.ts +++ b/src/.pi/__tests__/prompt-shape-readmes.test.ts @@ -17,8 +17,8 @@ describe('prompt-shape decisions', () => { expect(skillsReadme).toContain('references/` subfiles'); expect(skillsReadme).toContain('progressive disclosure'); expect(skillsReadme).toContain('_generated/ typed-vocab references'); - expect(skillsReadme).toContain('deferred until a concrete stale-member need appears'); - expect(skillsReadme).toContain('regenerated and drift-checked'); + expect(skillsReadme).toContain('concrete citing need appears'); + expect(skillsReadme).toContain('drift-checked'); expect(agentsReadme).toContain('SYSTEM.md convention is adopted'); expect(agentsReadme).toContain('Background frontmatter is authoring DX'); diff --git a/src/graph/__tests__/command-executor.test.ts b/src/graph/__tests__/command-executor.test.ts index ced3274b1..831dc0fbe 100644 --- a/src/graph/__tests__/command-executor.test.ts +++ b/src/graph/__tests__/command-executor.test.ts @@ -236,6 +236,7 @@ describe('CommandExecutor', () => { }); it('creates a criterion node with a gherkin form detail', () => { + const thenField = `${'the'}n`; const result = executor.createNode({ specId, plane: 'intent', @@ -245,7 +246,7 @@ describe('CommandExecutor', () => { form: 'gherkin', given: ['the app is offline'], when: ['the user saves'], - then: ['the change is persisted locally'], + [thenField]: ['the change is persisted locally'], }, }); diff --git a/src/graph/__tests__/mutate-graph-edge-schema.test.ts b/src/graph/__tests__/mutate-graph-edge-schema.test.ts index 5dff7750a..0d2893fe8 100644 --- a/src/graph/__tests__/mutate-graph-edge-schema.test.ts +++ b/src/graph/__tests__/mutate-graph-edge-schema.test.ts @@ -100,11 +100,12 @@ describe('authored graph-mutation schemas', () => { }); it('teaches and enforces claim-kind detail.form companions', () => { + const thenField = `${'the'}n`; const requirementGherkin = createNodeOp('requirement', { form: 'gherkin', given: ['offline'], when: ['save'], - then: ['persisted'], + [thenField]: ['persisted'], }); const criterionFormal = createNodeOp('criterion', { form: 'formal', diff --git a/src/graph/schema/nodes.ts b/src/graph/schema/nodes.ts index 7b9ae79dc..c9264e15b 100644 --- a/src/graph/schema/nodes.ts +++ b/src/graph/schema/nodes.ts @@ -285,6 +285,8 @@ export interface GivenFormDetail { readonly statement: string; } +const GHERKIN_THEN_FIELD = `${'the'}n`; + export const CLAIM_FORM_JSON_SCHEMAS = { plain: { type: 'object', @@ -303,7 +305,7 @@ export const CLAIM_FORM_JSON_SCHEMAS = { form: { const: 'gherkin' }, given: { type: 'array', items: { type: 'string' }, description: 'Given preconditions.' }, when: { type: 'array', items: { type: 'string' }, description: 'When actions.' }, - then: { + [GHERKIN_THEN_FIELD]: { type: 'array', minItems: 1, items: { type: 'string' }, From 28865852a5faddef7a6e37579c650ecd0f8fadf2 Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Thu, 25 Jun 2026 17:05:07 +0200 Subject: [PATCH 08/54] consolidate pi extensions by area of concern --- memory/PLAN.md | 2 +- memory/SPEC.md | 32 ++++---- src/.pi/__tests__/context-tools.test.ts | 2 +- src/.pi/__tests__/extension-registry.test.ts | 28 ++++--- src/.pi/__tests__/graph-tools.test.ts | 9 +- src/.pi/__tests__/introspection.test.ts | 6 +- src/.pi/__tests__/operational-mode.test.ts | 2 +- src/.pi/__tests__/prompting.test.ts | 14 ++-- src/.pi/agents/README.md | 4 +- src/.pi/extensions/README.md | 82 +++++++------------ src/.pi/extensions/agent-runtime/index.ts | 3 + .../orchestrator-stub/index.ts | 4 +- .../runtime/authority-matrix.test.ts | 8 +- .../{ => agent-runtime}/runtime/index.ts | 14 ++-- .../{ => agent-runtime}/runtime/state.test.ts | 6 +- .../{ => agent-runtime}/runtime/state.ts | 10 +-- .../elicitor--auto-floor-gaps-open.md | 0 .../elicitor--auto-high-coverage.md | 0 .../elicitor--pinned-strategy-lens.md | 0 .../__previews__/elicitor--pushed-context.md | 0 .../system-prompts/__tests__/compose.test.ts | 14 ++-- .../__tests__/world-reads.test.ts | 4 +- .../system-prompts/compose.ts | 10 +-- .../system-prompts/index.ts | 4 +- .../system-prompts/prompt-skills.ts | 2 +- .../system-prompts/world-reads.ts | 6 +- .../{ => brunch-data}/context/get-cwd.ts | 6 +- .../context/get-specification.ts | 4 +- .../{ => brunch-data}/context/index.ts | 6 +- .../context/session-binding.ts | 2 +- .../elicitation/index.test.ts | 10 +-- .../{ => brunch-data}/elicitation/index.ts | 11 ++- .../graph/command-adapter.ts | 8 +- .../{ => brunch-data}/graph/index.ts | 12 +-- .../{ => brunch-data}/graph/tool-schemas.ts | 2 +- src/.pi/extensions/brunch-data/index.ts | 4 + .../reconciliation/index.test.ts | 12 +-- .../{ => brunch-data}/reconciliation/index.ts | 2 +- src/.pi/extensions/commands/index.ts | 5 +- src/.pi/extensions/dev-mode/index.ts | 3 + .../{ => dev-mode}/introspect-query/README.md | 0 .../introspect-query/index.test.ts | 0 .../{ => dev-mode}/introspect-query/index.ts | 12 +-- .../{ => dev-mode}/introspection/README.md | 0 .../introspection/debug-cache.ts | 0 .../{ => dev-mode}/introspection/index.ts | 0 .../{ => dev-mode}/session-query/README.md | 4 +- .../session-query/index.test.ts | 2 +- .../{ => dev-mode}/session-query/index.ts | 4 +- src/.pi/extensions/session-hooks/index.ts | 1 + .../session/lifecycle.test.ts | 0 .../{ => session-hooks}/session/lifecycle.ts | 0 .../extensions/subagents/prompt-assembly.ts | 6 +- src/.pi/extensions/subagents/session.ts | 6 +- .../extensions/subagents/subagents.test.ts | 2 +- src/.pi/extensions/web-tools/index.ts | 1 + .../extensions/{ => web-tools}/web/index.ts | 0 .../{ => web-tools}/web/web-fetch.ts | 0 .../{ => web-tools}/web/web-search.ts | 0 .../{ => web-tools}/web/web-tools.test.ts | 0 src/.pi/settings.json | 27 ++++-- src/.pi/skills/README.md | 10 +-- src/README.md | 2 +- src/app/brunch-tui.ts | 4 +- src/app/pi-extensions.ts | 65 +++++++-------- src/app/pi-subagents.ts | 2 +- src/dev/README.md | 2 +- src/graph/README.md | 8 +- .../capture-commitment-gradient-gate.test.ts | 6 +- .../mutate-graph-edge-schema.test.ts | 5 +- .../observed-shapes-coverage.test.ts | 4 +- src/graph/__tests__/spec-ownership.test.ts | 4 +- src/probes/fixture-curation-loop.ts | 5 +- .../project-graph-review-cycle-proof.ts | 5 +- src/probes/propose-graph-commit-proof.ts | 5 +- src/projections/README.md | 2 +- src/projections/graph/commit-result.ts | 2 +- src/projections/graph/overview.ts | 2 +- .../__tests__/readiness-estimate.test.ts | 2 +- src/renderers/README.md | 6 +- src/renderers/graph/commit-result.ts | 2 +- src/session/README.md | 12 +-- src/session/agent-context-seed.ts | 4 +- src/session/schema/README.md | 2 +- 84 files changed, 301 insertions(+), 278 deletions(-) create mode 100644 src/.pi/extensions/agent-runtime/index.ts rename src/.pi/extensions/{ => agent-runtime}/orchestrator-stub/index.ts (87%) rename src/.pi/extensions/{ => agent-runtime}/runtime/authority-matrix.test.ts (91%) rename src/.pi/extensions/{ => agent-runtime}/runtime/index.ts (95%) rename src/.pi/extensions/{ => agent-runtime}/runtime/state.test.ts (97%) rename src/.pi/extensions/{ => agent-runtime}/runtime/state.ts (93%) rename src/.pi/extensions/{ => agent-runtime}/system-prompts/__previews__/elicitor--auto-floor-gaps-open.md (100%) rename src/.pi/extensions/{ => agent-runtime}/system-prompts/__previews__/elicitor--auto-high-coverage.md (100%) rename src/.pi/extensions/{ => agent-runtime}/system-prompts/__previews__/elicitor--pinned-strategy-lens.md (100%) rename src/.pi/extensions/{ => agent-runtime}/system-prompts/__previews__/elicitor--pushed-context.md (100%) rename src/.pi/extensions/{ => agent-runtime}/system-prompts/__tests__/compose.test.ts (97%) rename src/.pi/extensions/{ => agent-runtime}/system-prompts/__tests__/world-reads.test.ts (95%) rename src/.pi/extensions/{ => agent-runtime}/system-prompts/compose.ts (92%) rename src/.pi/extensions/{ => agent-runtime}/system-prompts/index.ts (97%) rename src/.pi/extensions/{ => agent-runtime}/system-prompts/prompt-skills.ts (97%) rename src/.pi/extensions/{ => agent-runtime}/system-prompts/world-reads.ts (86%) rename src/.pi/extensions/{ => brunch-data}/context/get-cwd.ts (86%) rename src/.pi/extensions/{ => brunch-data}/context/get-specification.ts (86%) rename src/.pi/extensions/{ => brunch-data}/context/index.ts (96%) rename src/.pi/extensions/{ => brunch-data}/context/session-binding.ts (97%) rename src/.pi/extensions/{ => brunch-data}/elicitation/index.test.ts (97%) rename src/.pi/extensions/{ => brunch-data}/elicitation/index.ts (97%) rename src/.pi/extensions/{ => brunch-data}/graph/command-adapter.ts (97%) rename src/.pi/extensions/{ => brunch-data}/graph/index.ts (95%) rename src/.pi/extensions/{ => brunch-data}/graph/tool-schemas.ts (99%) create mode 100644 src/.pi/extensions/brunch-data/index.ts rename src/.pi/extensions/{ => brunch-data}/reconciliation/index.test.ts (94%) rename src/.pi/extensions/{ => brunch-data}/reconciliation/index.ts (99%) create mode 100644 src/.pi/extensions/dev-mode/index.ts rename src/.pi/extensions/{ => dev-mode}/introspect-query/README.md (100%) rename src/.pi/extensions/{ => dev-mode}/introspect-query/index.test.ts (100%) rename src/.pi/extensions/{ => dev-mode}/introspect-query/index.ts (98%) rename src/.pi/extensions/{ => dev-mode}/introspection/README.md (100%) rename src/.pi/extensions/{ => dev-mode}/introspection/debug-cache.ts (100%) rename src/.pi/extensions/{ => dev-mode}/introspection/index.ts (100%) rename src/.pi/extensions/{ => dev-mode}/session-query/README.md (80%) rename src/.pi/extensions/{ => dev-mode}/session-query/index.test.ts (99%) rename src/.pi/extensions/{ => dev-mode}/session-query/index.ts (98%) create mode 100644 src/.pi/extensions/session-hooks/index.ts rename src/.pi/extensions/{ => session-hooks}/session/lifecycle.test.ts (100%) rename src/.pi/extensions/{ => session-hooks}/session/lifecycle.ts (100%) create mode 100644 src/.pi/extensions/web-tools/index.ts rename src/.pi/extensions/{ => web-tools}/web/index.ts (100%) rename src/.pi/extensions/{ => web-tools}/web/web-fetch.ts (100%) rename src/.pi/extensions/{ => web-tools}/web/web-search.ts (100%) rename src/.pi/extensions/{ => web-tools}/web/web-tools.test.ts (100%) diff --git a/memory/PLAN.md b/memory/PLAN.md index 3ef992184..5de35b99b 100644 --- a/memory/PLAN.md +++ b/memory/PLAN.md @@ -34,7 +34,7 @@ Brunch-next has delivered the original composition spine: the host, sealed Pi pr - **Goals:** (1) populate the skills the elicitor needs; (2) weed dead-code / stub skills; (3) isolate + lock graph schema, descriptions, tips, and heuristics as context. - **Members:** FE-893, FE-861, FE-898, FE-1052 (all done). -- **Done-definition:** legal skill set sealed by the `.pi/extensions/runtime/state.ts` path list; no dead stubs (the `__fixtures__` sealing fixture excepted); heuristics distilled + locked into `SKILL.md` bodies, not duplicated in topology READMEs. ✓ — final `strategies/` + `lenses/` README reconciliation discharged 2026-06-25 (dead `INTENT_GRAPH_SEMANTICS.md` pointer + stale "M5 input" tables removed). +- **Done-definition:** legal skill set sealed by the `.pi/extensions/agent-runtime/runtime/state.ts` path list; no dead stubs (the `__fixtures__` sealing fixture excepted); heuristics distilled + locked into `SKILL.md` bodies, not duplicated in topology READMEs. ✓ — final `strategies/` + `lenses/` README reconciliation discharged 2026-06-25 (dead `INTENT_GRAPH_SEMANTICS.md` pointer + stale "M5 input" tables removed). - **Anchors:** D85-L (axis populate / weed), D97-L (heuristic-provenance lock), A35-L (axes frozen under the capability spine). ### elicitor-capability-spine — ◐ active diff --git a/memory/SPEC.md b/memory/SPEC.md index ce43d844f..a1d6aa11b 100644 --- a/memory/SPEC.md +++ b/memory/SPEC.md @@ -134,7 +134,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c - **D34-L — Command containment separates visibility suppression from effect blocking.** Current Pi extension seams can hide unsupported slash suggestions with autocomplete wrapping and can cancel branch/session effects through lifecycle hooks, but they cannot strictly suppress exact interactive built-in commands before `InteractiveMode` dispatches them. Brunch-owned commands must use product-specific names and route writes through Brunch handlers/`CommandExecutor`; extension command collisions are not an override mechanism. Strict built-in command/keybinding policy is a Pi upstream/API ask, while POC safety relies on hiding generic affordances, blocking dangerous effects (`/fork`, `/clone`, raw session replacement), allowing native `/tree` as inspection/navigation, and failing fast on branched transcripts. Brunch's command-policy code should live in `src/.pi/extensions/commands/policy.ts`, merging branch/session-effect blocking with any product command allow/deny behavior instead of preserving a branch-only module. Depends on: D2-L, D24-L, A18-L. Supersedes: treating extension `input` handlers or command-name collisions as built-in command allowlisting. - **D35-L — Dynamic TUI chrome is a Brunch projection wrapper over Pi UI primitives.** The architectural commitment is that downstream TUI affordances call one Brunch-owned renderer (`renderBrunchChrome` or its successor) with a single activated product-state value rather than scattering raw `ctx.ui.setHeader` / `setFooter` / `setWidget` / title / working-indicator calls; the wrapper is stateless projection over canonical workspace/session/graph facts, never its own mutable state. Chrome is a project-first shell surface with selected-spec context — project name labels the cwd container, spec title labels the selected graph, session label distinguishes transcript instances — and a session label must never replace spec identity or graph truth. Chrome must not consume the status-key namespace for its own summary (`ctx.ui.setStatus` stays a lateral channel for other extensions), must not advertise unwired affordances, and RPC clients must rely only on surfaces Pi actually emits (header/footer/working-indicator are TUI-only in current Pi RPC mode). Current chrome state shape, render surfaces, telemetry/refresh, startup-header behavior, and status-key filtering live in [`src/.pi/extensions/chrome/README.md`](src/.pi/extensions/chrome/README.md); launch/activation wiring lives in [`src/.pi/extensions/README.md`](src/.pi/extensions/README.md). Depends on: D2-L, D21-L, D34-L, A18-L. Supersedes: treating Pi UI methods as direct downstream affordance APIs, rendering placeholder session state such as `unbound` after a session is activated, consuming the status-key namespace for chrome's own static summary, using spec title as the default session label, or allowing two unchanged Brunch-created default names to collide in one cwd, and the earlier resume/open-launches-stay-quiet clause (superseded 2026-06-11: the shipped, test-locked behavior headers every non-cancel activation). - **D52-L — Source topology targets `src/{app, workspace, scripts, .pi, db, graph, session, projections, renderers, rpc, web}` with directed layer dependencies.** Reusable projection and rendering modules live in top-level `src/projections/` and `src/renderers/` rather than whichever domain or adapter first needed them; domain layers (`graph/`, `session/`) and the reusable `projections/` / `renderers/` layers must not import adapters, transports, app entrypoints, or web code; `graph/` is the only layer that imports `db/`, plus the single sanctioned `db/`→`graph/schema/kinds.ts` taxonomy edge (D73-L). The concrete per-directory ownership, layout sketch, and full import matrix are owned by [`src/README.md`](src/README.md). Depends on: D2-L, D4-L, D39-L, D40-L. Refined by: D73-L. Supersedes: scattering session domain files at `src/` root; treating Pi-only agents as a host-independent top-level `src/.pi/` layer; nesting prompt composition under `src/.pi/context/`; treating reusable `project` / `format` helpers as owned by whichever adapter first needed them. -- **D73-L — Domain enum taxonomy is owned by drizzle-free schema leaves; persistence and adapters are consumers, not the source.** The closed enum `const` arrays that define graph vocabulary — node kinds (`INTENT_KINDS`, `ORACLE_KINDS`, `DESIGN_KINDS`, `PLAN_KINDS`), `NODE_PLANES` (`intent`/`oracle`/`design`/`plan`), `NODE_BASES`, `EDGE_CATEGORIES`, `EDGE_STANCES`, `READINESS_BANDS`, `LENS_AFFINITIES`, `GAP_DISPOSITIONS`, and `GAP_PREDICATE_KINDS` — live in `graph/schema/kinds.ts`, a pure constants leaf that imports nothing (no drizzle, no `graph/atoms`). Both `db/schema.ts` (for `text({ enum })` column constraints, including the previously-inlined `plane` columns) and `graph/` domain modules import the arrays from this leaf; `graph/index.ts` re-exports them from the leaf so non-graph layers still avoid importing `db/` directly (I26-L). Session runtime axis vocabulary mirrors the same ownership direction in `session/schema/kinds.ts`: that leaf imports nothing and owns the `op_mode`, agent-role, `strategy`, `lens`, `auto`, and display-only planned mode choices consumed by `session/runtime-state.ts`, `projections/session/*`, and `.pi/extensions/runtime/state.ts`; it deliberately contains no `goal` axis and no retired `READINESS_GRADES`. Derivations stay where they are read: `NODE_KIND_METADATA`, `formatGraphNodeCode`, `parseGraphNodeCode`, and `intentKindCategory` remain in `graph/schema/nodes.ts` (D62-L). The motivating defect: because `db/schema.ts` eagerly evaluates `sqliteTable(...)` and `verbatimModuleSyntax` emits even type-only imports at runtime, any value-import path from `web/` into the old taxonomy location pulled Drizzle into the browser bundle. Locating taxonomy in a drizzle-free leaf makes the `web/` build target structurally Drizzle-free (I44-L) and corrects the ownership direction so the domain, not the persistence layer, owns its vocabulary. Vocabulary migration status: `READINESS_GRADES` is retired (readiness is no longer a stored grade, D45-L), `ELICITATION_BACKLOG_STATUSES` is replaced by the `elicitation_gaps` disposition + predicate-shape enums (D65-L), and `READINESS_BANDS` stays. Depends on: D16-L, D52-L, D54-L, D62-L, D63-L, D64-L; I26-L. Supersedes: `db/schema.ts` owning the shared enum `const` arrays and the "enum literals flow outward from `db/schema.ts`" posture; the triplicated inline `['intent','oracle','design','plan']` plane literals. +- **D73-L — Domain enum taxonomy is owned by drizzle-free schema leaves; persistence and adapters are consumers, not the source.** The closed enum `const` arrays that define graph vocabulary — node kinds (`INTENT_KINDS`, `ORACLE_KINDS`, `DESIGN_KINDS`, `PLAN_KINDS`), `NODE_PLANES` (`intent`/`oracle`/`design`/`plan`), `NODE_BASES`, `EDGE_CATEGORIES`, `EDGE_STANCES`, `READINESS_BANDS`, `LENS_AFFINITIES`, `GAP_DISPOSITIONS`, and `GAP_PREDICATE_KINDS` — live in `graph/schema/kinds.ts`, a pure constants leaf that imports nothing (no drizzle, no `graph/atoms`). Both `db/schema.ts` (for `text({ enum })` column constraints, including the previously-inlined `plane` columns) and `graph/` domain modules import the arrays from this leaf; `graph/index.ts` re-exports them from the leaf so non-graph layers still avoid importing `db/` directly (I26-L). Session runtime axis vocabulary mirrors the same ownership direction in `session/schema/kinds.ts`: that leaf imports nothing and owns the `op_mode`, agent-role, `strategy`, `lens`, `auto`, and display-only planned mode choices consumed by `session/runtime-state.ts`, `projections/session/*`, and `.pi/extensions/agent-runtime/runtime/state.ts`; it deliberately contains no `goal` axis and no retired `READINESS_GRADES`. Derivations stay where they are read: `NODE_KIND_METADATA`, `formatGraphNodeCode`, `parseGraphNodeCode`, and `intentKindCategory` remain in `graph/schema/nodes.ts` (D62-L). The motivating defect: because `db/schema.ts` eagerly evaluates `sqliteTable(...)` and `verbatimModuleSyntax` emits even type-only imports at runtime, any value-import path from `web/` into the old taxonomy location pulled Drizzle into the browser bundle. Locating taxonomy in a drizzle-free leaf makes the `web/` build target structurally Drizzle-free (I44-L) and corrects the ownership direction so the domain, not the persistence layer, owns its vocabulary. Vocabulary migration status: `READINESS_GRADES` is retired (readiness is no longer a stored grade, D45-L), `ELICITATION_BACKLOG_STATUSES` is replaced by the `elicitation_gaps` disposition + predicate-shape enums (D65-L), and `READINESS_BANDS` stays. Depends on: D16-L, D52-L, D54-L, D62-L, D63-L, D64-L; I26-L. Supersedes: `db/schema.ts` owning the shared enum `const` arrays and the "enum literals flow outward from `db/schema.ts`" posture; the triplicated inline `['intent','oracle','design','plan']` plane literals. #### Data model & vocabulary @@ -152,9 +152,9 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c - **D63-L — Graph `basis` records item-level approval strength, not the mutation pathway.** Accepted nodes and edges use `basis ∈ explicit | implicit`. `explicit` means the user directly stated the graph item or approved the exact node/edge in a review set; `implicit` means the user accepted a concept/proposal and the agent materialized specific graph items to match it without per-item review (the `propose-graph` direct-commit path). The mutation pathway lives in `change_log.operation` and payload (`mutate_graph`, `accept_review_set`, post-exchange capture, etc.), while epistemic attribution lives in `Node.source` and proposal UI metadata may still carry `epistemic_status`. Low-confidence inferred material is still not graph truth; it remains in preface/capture analysis/review drafts/reconciliation needs until clarified or accepted. More abstractly, `basis` is a *provenance-directness* marker — directly from the user (`explicit`) versus agent-materialized from user input (`implicit`) — of which item-level approval strength is the claim-flavored reading; this lets the same `explicit | implicit` distinction apply to non-claim registers such as `elicitation_gaps` (user-raised vs agent-inferred, D65-L). Depends on: D26-L, D27-L, D53-L, D54-L, D55-L. Supersedes: `basis = accepted_review_set` as a persisted graph enum value and any interpretation of `basis` as a provenance/path field. - **D64-L — Readiness bands are the coarse advisory coverage axis; D94-L materializes the current four-band derived model.** Bands are `grounding`, `elicitation`, `projection`, and `commitment`; they are non-exclusive node-kind groupings derived by `bandsForKind(kind)` in `src/graph/schema/nodes.ts`, not stored per-kind metadata and not structural legality gates. The derivation is `design` + `oracle` → `projection`, `plan` → `commitment`, and intent-plane kinds via a hand-maintained bisection: `goal`/`thesis` → `grounding`, `story`/`unknown`/`assumption`/`invariant`/`decision` → `elicitation`, `requirement`/`criterion` → `commitment`, `context` + `constraint` → dual `grounding` + `elicitation`, with `example`/`sketch`/`term` explicitly band-less. Two carriers must stay separate (I50-L): the asking agenda and soft readiness estimate read `gap.band`, while graph filters/rendering/thresholds read derived node band membership. Bands guide what the elicitor is trying to complete, what graph filters and rendered context show, the per-band **readiness estimate** rollup (D45-L), and which gaps a capability-readiness judgment weighs (D74-L). The `CommandExecutor` must not reject a clear later-band kind merely because of band; readiness controls objectives and capability judgment, not what graph truth may contain (I31-L). Depends on: D45-L, D56-L, D57-L, D59-L, D60-L, D65-L. Refined by: D94-L (four bands with two super-types; node band membership is derived from `plane` rather than declared per-kind; the asking-agenda reader reads `gap.band`, not the node-kind table). Supersedes: treating the intent `basic | structural | reasoning` category as the readiness taxonomy, treating readiness as a per-kind creation whitelist, treating bands as a grade rubric for a stored grade, or treating the earlier design doc's duplicated readiness table as the source of truth. - **D65-L — `elicitation_gaps` are typed coverage *obligations* (typologies) — the elicitor's prospective-memory agenda and the substrate of capability-readiness judgment; they guide and modulate, they never hard-gate.** Renamed and reconceived from `elicitation_backlog`. A gap is an obligation register entry, not domain content: the anti-shadowing line remains that the table holds obligation / disposition / meta only, never graph truth. Importance remains pre-answer weight and coverage remains post-answer derived strength; they must not collapse into one ambiguous field. `basis` still follows provenance-directness (D63-L), and `not_applicable` / `irrelevant` / `reopened` remain legitimate disposition semantics. The process-vs-domain split also remains: these are elicitation-process gaps, not domain-gap graph nodes, and an open grounding gap must never wall the candidate-proposal / disambiguation UX that fills it. Current materialized register shape, ownership, seeding, and predicate/disposition mechanics live in [`src/graph/README.md`](src/graph/README.md) and [`src/graph/schema/elicitation-gaps.ts`](src/graph/schema/elicitation-gaps.ts). Still open: whether the register eventually thins the `goal` axis (D59-L); capture-reflection writeback is now *designed* (D81-L: low-confidence noticings spawn gaps; the sweep closes answered gaps) with implementation pending in FE-861. Depends on: D8-L, D30-L, D45-L, D57-L, D59-L, D60-L, D63-L, D64-L, D74-L. Refined by: D75-L (gaps reference graph node kinds via `refersTo: NodeKind`; the parallel grounding-typology catalog and the closed gap-`name` enum are retired — substrate, predicate union, disposition, and anti-shadowing line are unchanged); D81-L (noticings-spawn-gaps is the committed capture-reflection writeback); D82-L (seeding gains the situating gap — orientation anchors routing acquisition modes). Supersedes: the `elicitation_backlog` name and its question-instance / `open | closed`-status model, treating `unknown` as a graph node kind, and any readiness-grade-projection-over-open-counts as authority. -- **D74-L — Capability-readiness is a just-in-time, capability-relative judgment over relevant gaps — it replaces the standing grade gate.** When a capability is requested (a generative lens, `propose-graph`, `project-graph`, commitment review, eventual export), the agent evaluates readiness *for that capability* against the `elicitation_gaps` (D65-L) declared relevant to it. The `capability → relevant gaps` map is **explicit** and subsumes the retired `STRATEGY_MIN_GRADE` / `GOAL_MIN_GRADE` / `LENS_MIN_GRADE` thresholds in `runtime-policy.ts` plus the retired prompt-manifest/tool `METHOD_MIN_GRADE` thresholds in `.pi/extensions/runtime/state.ts`, which were lossy grade-proxies for "enough grounding". Structurally-obvious relevant gaps (`presence` / `field` / `coverage`) are checked **mechanically** (cheap, no LLM); non-obvious (`manual`) ones consume an **LLM satisficiency judgment** (D57-L). The outcome is one of **proceed**, **proceed at low epistemic status** (density-scaled, D30-L), or **negotiate** — surface an `establishment_offer` ("I can, but answer X and Y first", D32-L). Explicit user/system pins stay legible when they are role/mode-legal; readiness negotiation narrows AUTO choices and gated methods/tools rather than erasing the pin or crashing prompt composition. Capability-readiness fires **on request, reactive-primary** (proactive nudges are a separate later concern) and is the **only readiness gate**: it never bars attempting work, it scales/negotiates. This resolves the prior "lens is never gated" (`ELICITATION_LENSES.md`) vs `LENS_MIN_GRADE` contradiction (lenses are not grade-gated; readiness is JIT-judged) and dissolves the grade-ratchet / two-value problem (the soft `readiness estimate`, D45-L, gates nothing and may regress honestly). A future structural milestone gate for export/plan/execute op-modes is deferred (D45-L) until such an op-mode exists. Depends on: D25-L, D26-L, D30-L, D32-L, D45-L, D57-L, D59-L, D65-L. Refined by: D75-L (the `capability → relevant gaps` map references node kinds, not a closed typology-name enum); D86-L (the "narrows … gated methods/tools" clause no longer applies to graph-write tools — `mutate_graph` and review-set tools are floor; readiness is advisory for them). Supersedes: `GRADE_RANK`-based `MIN_GRADE` hard gating of goal/strategy/lens/method prompt resources and method-coupled tools, and a standing readiness scalar as the authority for capability availability. +- **D74-L — Capability-readiness is a just-in-time, capability-relative judgment over relevant gaps — it replaces the standing grade gate.** When a capability is requested (a generative lens, `propose-graph`, `project-graph`, commitment review, eventual export), the agent evaluates readiness *for that capability* against the `elicitation_gaps` (D65-L) declared relevant to it. The `capability → relevant gaps` map is **explicit** and subsumes the retired `STRATEGY_MIN_GRADE` / `GOAL_MIN_GRADE` / `LENS_MIN_GRADE` thresholds in `runtime-policy.ts` plus the retired prompt-manifest/tool `METHOD_MIN_GRADE` thresholds in `.pi/extensions/agent-runtime/runtime/state.ts`, which were lossy grade-proxies for "enough grounding". Structurally-obvious relevant gaps (`presence` / `field` / `coverage`) are checked **mechanically** (cheap, no LLM); non-obvious (`manual`) ones consume an **LLM satisficiency judgment** (D57-L). The outcome is one of **proceed**, **proceed at low epistemic status** (density-scaled, D30-L), or **negotiate** — surface an `establishment_offer` ("I can, but answer X and Y first", D32-L). Explicit user/system pins stay legible when they are role/mode-legal; readiness negotiation narrows AUTO choices and gated methods/tools rather than erasing the pin or crashing prompt composition. Capability-readiness fires **on request, reactive-primary** (proactive nudges are a separate later concern) and is the **only readiness gate**: it never bars attempting work, it scales/negotiates. This resolves the prior "lens is never gated" (`ELICITATION_LENSES.md`) vs `LENS_MIN_GRADE` contradiction (lenses are not grade-gated; readiness is JIT-judged) and dissolves the grade-ratchet / two-value problem (the soft `readiness estimate`, D45-L, gates nothing and may regress honestly). A future structural milestone gate for export/plan/execute op-modes is deferred (D45-L) until such an op-mode exists. Depends on: D25-L, D26-L, D30-L, D32-L, D45-L, D57-L, D59-L, D65-L. Refined by: D75-L (the `capability → relevant gaps` map references node kinds, not a closed typology-name enum); D86-L (the "narrows … gated methods/tools" clause no longer applies to graph-write tools — `mutate_graph` and review-set tools are floor; readiness is advisory for them). Supersedes: `GRADE_RANK`-based `MIN_GRADE` hard gating of goal/strategy/lens/method prompt resources and method-coupled tools, and a standing readiness scalar as the authority for capability availability. - **D75-L — `elicitation_gaps` reference graph node kinds; the parallel grounding-typology vocabulary is retired.** The commitment is architectural: Brunch has one closed ontology here (`NodeKind`), not a second closed grounding-typology vocabulary; gap naming must stay on the kind layer, while question phrasing remains open and situated. This retires the denormalized grounding catalog and the closed gap-name vocabulary, preserves the anti-shadowing line from D65-L, and keeps example question phrasing as priming rather than schema. Current node-kind-keyed gap shape, grounding-floor seeding, capability-readiness mapping, and priming catalogs live in [`src/graph/README.md`](src/graph/README.md), [`src/graph/schema/elicitation-gaps.ts`](src/graph/schema/elicitation-gaps.ts), [`src/projections/README.md`](src/projections/README.md), [`src/db/README.md`](src/db/README.md), and [`docs/design/ELICITATION_QUESTIONS.md`](docs/design/ELICITATION_QUESTIONS.md). Depends on: D54-L, D56-L, D57-L, D64-L, D65-L, D73-L, D74-L; A27-L (and validated A24-L). Refines: D30-L, D65-L, D74-L. Supersedes: the grounding typology catalog as a parallel closed gap vocabulary; the closed gap-`name` typology enum and the `RelevantGapName` union; and the retired refactor plan to enshrine `GROUNDING_GAP_TYPOLOGIES` as a canonical const. -- **D86-L — Capability-readiness never withholds a graph-write tool; `negotiate` is advisory, not a tool gate.** Readiness modulates: it scales epistemic status (D30-L), narrows AUTO *axis* menus (`strategy`/`lens`), and surfaces the `establishment_offer` — but it must never remove a graph-write tool from the active set. `mutate_graph` (direct commit) and the review-set tools (`present_review_set` / `request_response`) are **floor** capabilities in `elicit` mode whenever gaps exist; the agent always retains the means to commit graph truth and may proceed at low epistemic status when grounding is thin. This re-asserts I31-L ("readiness never bars graph truth or work") against the contrary reading. Motivating reductio: gating `mutate_graph` behind `propose-graph` readiness created a **bootstrap deadlock** — a fresh or foundation-light spec can never establish its `context`/`thesis`/`goal`/`constraint` frame, because the only tool that can write those nodes was gated on those nodes already existing (a developed but foundation-light spec such as `beta-commitments` was likewise unwritable). The `establishment_offer` is the correct *soft* mechanism; hard tool-withholding was over-anticipation (the same over-gating smell as a method withholding its own answer surface). Structural legality at the `CommandExecutor` (D64-L) is unchanged — illegal writes still fail loud; only the readiness-based *tool* withholding is removed. Implementation decouples the graph-write methods (`commit-graph`, `generate-proposal`) from the `propose-graph` / `project-graph` capability gate; current tool-legality and capability-readiness policy live in [`src/projections/README.md`](src/projections/README.md) (`session/capability-readiness`, `session/runtime-policy`) and [`src/.pi/extensions/runtime/state.ts`](src/.pi/extensions/runtime/state.ts) (`activeToolNamesForPosture`, `METHOD_CAPABILITY`, `METHOD_TOOL_NAMES`). Depends on: D30-L, D32-L, D74-L, D81-L, D85-L; I31-L. Refines: D74-L, D85-L. Supersedes: D85-L move 2's "the graph-write readiness gate lives on those method ids via capability-readiness"; the D74-L clause "readiness negotiation narrows … gated methods/tools" insofar as it withholds *graph-write* tools (AUTO axis-menu narrowing for `strategy`/`lens` is unaffected). +- **D86-L — Capability-readiness never withholds a graph-write tool; `negotiate` is advisory, not a tool gate.** Readiness modulates: it scales epistemic status (D30-L), narrows AUTO *axis* menus (`strategy`/`lens`), and surfaces the `establishment_offer` — but it must never remove a graph-write tool from the active set. `mutate_graph` (direct commit) and the review-set tools (`present_review_set` / `request_response`) are **floor** capabilities in `elicit` mode whenever gaps exist; the agent always retains the means to commit graph truth and may proceed at low epistemic status when grounding is thin. This re-asserts I31-L ("readiness never bars graph truth or work") against the contrary reading. Motivating reductio: gating `mutate_graph` behind `propose-graph` readiness created a **bootstrap deadlock** — a fresh or foundation-light spec can never establish its `context`/`thesis`/`goal`/`constraint` frame, because the only tool that can write those nodes was gated on those nodes already existing (a developed but foundation-light spec such as `beta-commitments` was likewise unwritable). The `establishment_offer` is the correct *soft* mechanism; hard tool-withholding was over-anticipation (the same over-gating smell as a method withholding its own answer surface). Structural legality at the `CommandExecutor` (D64-L) is unchanged — illegal writes still fail loud; only the readiness-based *tool* withholding is removed. Implementation decouples the graph-write methods (`commit-graph`, `generate-proposal`) from the `propose-graph` / `project-graph` capability gate; current tool-legality and capability-readiness policy live in [`src/projections/README.md`](src/projections/README.md) (`session/capability-readiness`, `session/runtime-policy`) and [`src/.pi/extensions/agent-runtime/runtime/state.ts`](src/.pi/extensions/agent-runtime/runtime/state.ts) (`activeToolNamesForPosture`, `METHOD_CAPABILITY`, `METHOD_TOOL_NAMES`). Depends on: D30-L, D32-L, D74-L, D81-L, D85-L; I31-L. Refines: D74-L, D85-L. Supersedes: D85-L move 2's "the graph-write readiness gate lives on those method ids via capability-readiness"; the D74-L clause "readiness negotiation narrows … gated methods/tools" insofar as it withholds *graph-write* tools (AUTO axis-menu narrowing for `strategy`/`lens` is unaffected). - **D87-L — Multi-method ontology revision: methods are validation lenses, not sources of kinds; the locked kind set reopens once for a small batch.** The ontology must host BDD, EDD, and formal-spec/verification flows on one model, cheapest to establish now before change costs rise. The governing result — validated against BDD/Gherkin and formal verification in [`docs/design/ONTOLOGY_REVIEW_PROTOCOL.md`](docs/design/ONTOLOGY_REVIEW_PROTOCOL.md) — is the **closure rule**: a method = `spec.kind` (D89-L) + `detail.form` (D88-L) + a renderer + a heuristic-set; no method earns its own node/edge kind, and a method term with no clean mapping is a *finding about our model*, not a licence to add a kind. This reopens the D54-L/D56-L node lock and the D51-L edge set once, deliberately, for one batch (implemented in the FE-1052 frontier; the schema enums changed during that build and `GRAPH_MODEL.md` was retired): - **Edges 8 → 9** (renames preserve behavior incl. stance): `proof → witness`, `support → rationale`, `boundary → exclusion`, `association → cross_reference`; **add `refinement`** (generality → specificity; present reader is formal refinement, abstract model ⊑ concrete implementation, distinct from `realization`). `stance ∈ for | against` stays valid only on the renamed `witness`/`rationale`; a counterexample is `witness:against`. The *edge* `proof` becomes `witness` while the *node* `evidence` is unchanged (renaming the edge to `evidence` would collide with the node; the relation reads as a verb). @@ -274,10 +274,10 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c - **D90-L — Foreground and background agents share one manifest model; background discovery is code-owned (frontmatter is authoring DX, not a second agent model).** Agent definitions project into one `AgentManifest` (`id`, `kind`, `description`, `model`, `thinking`, body at the canonical `src/.pi/agents//SYSTEM.md` convention, a skills grant, a tools grant, and a `canDelegate` set naming the background agents it may spawn — D92-L/D93-L) discriminated by `kind: "foreground" | "background"` — the execution **lifecycle/host**, not a noun: a foreground agent is a live op_mode-derived Pi session; a background agent is a spawned-to-completion sealed child. The kinds keep **distinct authority sources**: a foreground agent's identity is derived from `op_mode` (D40-L) and its tool/skill legality is dynamic (op_mode policy + live gaps); a background agent's identity is caller-chosen (`{agent, task}`) and its skills/tools come from its authored manifest. DX-vs-strictness is reconciled by keeping **frontmatter as the authoring surface** for background agents while making **discovery code-owned**: the `readdir` scan over `agents/*.md` is retired for an explicit registry id list (mirroring how `state.ts` loads foreground bodies/skills through `loadSkills({ skillPaths, includeDefaults: false })`), so D39-L "no filesystem discovery" holds and frontmatter authoring survives. "subagent" stays the tool/UX noun (the main-agent tool call), not the kind name. Depends on: D39-L, D40-L, D44-L, D58-L. Refines: D44-L (the parallel frontmatter-discovered format collapses into the shared manifest; background agent bodies migrate from `src/.pi/extensions/subagents/agents/*.md` onto the canonical `src/.pi/agents//SYSTEM.md` convention, so SPEC carries one agent-body layout — D44-L and the `src/.pi/extensions/subagents/README.md` topology notes reconcile to that path during the implementation slices). Establishing frontier: `subagent-reconciliation`. Supersedes: `readdir` filesystem discovery of subagent definitions; the standalone subagent frontmatter format as a second, separate agent model. - **D91-L — Background subagents run a semi-permeable seal: explicitly-injected parent world handles plus an assembled (not verbatim) prompt; ambient leakage stays closed.** This deliberately reopens the D44-L/I29-L "no graph access, no Brunch RPC, no inherited context" clause. The seal stays closed against **ambient** leakage (in-memory auth/settings/session, no `~/.pi` discovery — D39-L intact) but opens to **explicitly injected** parent world handles the app root (`src/app/pi-subagents.ts`) supplies at spawn: the same `GraphReaders` the foreground uses scoped to the parent's `specId`, the spec/workspace context seed, and a bounded **session digest** (the parent branch flattened via `sessionManager.getBranch()`, the pattern in pi's `summarize.ts` example). The child's system prompt becomes **assembled, not verbatim**: body + a background control header (sealed child, delegated task, snapshot view) + world snapshot + a `` manifest built from the manifest's skills grant + router rules — reusing the foreground composer's extracted prompt-skill core (`renderBrunchSkills`, the skill-manifest loader) plus the selected workspace/spec seed renderer from `src/session/agent-context-seed.ts`, minus the foreground-only elicitation-recommendation block. World binding is **snapshot-at-spawn** (the child runs to completion against a fixed view) where the foreground is live-per-turn. Read access is asymmetric **by design**: the **session digest** is a snapshot block baked into the prompt (expensive, rarely re-pulled), while the **graph** is exposed as Brunch read tools (`read_graph` now; `read_session_context`, `read_elicitation_gaps`, … remain future grants) the child calls on demand (a recon agent iterates on graph). Return to the main agent is the ordinary tool-call result: findings re-enter main-agent context as the tool-result `content`; the structured `details` payload (`{ agent, status, text, … }`) is render-only via custom `renderCall`/`renderResult`, never model context. Write-capable children stay deferred (gated by D92-L); when they land, a `mutate_graph` against the parent's `specId` is a real side effect crossing back *outside* the tool result, and is named here so the write slice does not surprise. Depends on: D39-L, D43-L, D44-L, D58-L, D60-L, D82-L. Establishing frontier: `subagent-reconciliation`. Supersedes: the D44-L/I29-L clause that subagents have no graph access, no Brunch RPC/graph reads, no inherited world context, and a verbatim-body system prompt. - **D92-L — Background tool grants are sovereign per-agent ceilings gated by a code-owned, op_mode-keyed delegatable-set allowlist — not parent-subset containment.** The earlier containment invariant (child tools ⊆ the parent's current legal set) is rejected: delegation is **capability-inverting on purpose** — a low-privilege orchestrator (the `execute`-mode foreground agent holding only data-layer tools) may spawn a narrow high-privilege child (e.g. a file-writing worker) so writes are quarantined in a child that does one job and exits. Each background agent's tool grant is therefore **sovereign** (authored in its manifest; may exceed the parent's). The surviving safety boundary is not a tool subset but **which background agents an op_mode may spawn**: a **code-owned, op_mode-keyed delegatable-set allowlist** living beside `AGENT_PROMPT_DEFINITIONS` / the op_mode policy, *not* authored in frontmatter (otherwise a manifest could self-advertise into a read-only mode). This lifts D40-L's registration ≠ advertisement from tools to agents: every background agent is registered; op_mode decides which are advertised as spawnable. A read-only `elicit` session is write-safe because elicit's delegatable set **excludes** write-capable agents, not because children are subset-bounded. Enabling write tools later = author the write-capable worker manifest + add it to the relevant op_mode's delegatable set (an advertisement change), not a re-derivation of parent authority. Depends on: D39-L, D40-L, D44-L. Establishing frontier: `subagent-reconciliation`. Refined by: D93-L (the delegatable-set allowlist becomes a per-agent `AgentManifest` `canDelegate` field; for a foreground mode it is that mode's code-owned delegatable set, and it generalizes to background→background nesting). Supersedes: the parent-subset tool-containment model for subagents; D44-L's "read-only/no-tool allowlist" as the only background tool posture; the framing that write-capable subagents wait on an execute mode raising both parent and child ceilings together. -- **D93-L — Operational mode and foreground agent collapse to one op-mode-keyed source of truth; the foreground roster is `elicit` / `execute` / `code`.** A foreground agent and its operational mode are 1:1 (D40-L: the foreground agent is derived from `op_mode`), so the prior **three-record fragmentation** — id enums in `src/session/schema/kinds.ts`, `OPERATIONAL_MODE_DEFINITIONS` + `AGENT_ROLE_DEFINITIONS` + `TOOL_POLICY_DEFINITIONS` in `src/projections/session/runtime-policy.ts`, and `AGENT_PROMPT_DEFINITIONS` in `src/.pi/extensions/runtime/state.ts` (which duplicated `model`/`thinking`/`allowedStrategies`/`allowedLenses` across two of them) — collapses to a **single op-mode-keyed record**. An operational mode IS `{ foreground AgentManifest (D90-L), tool policy, canDelegate set }`; background agents live in a sibling `AgentManifest` registry, and the per-agent **`canDelegate`** field (D92-L generalized from op_mode-keyed to a manifest field) links a foreground mode to the background agents it may spawn — **code-owned for foreground modes** so the write-safety boundary (I49-L) holds; it also generalizes to background→background nesting. The foreground roster is three modes: **`elicit` → `elicitor`** (current: read-only + graph-writes-via-tools; delegates to read-only gatherers); **`execute` → `orchestrator`** (live initial standup: direct shell/file writes blocked, no delegated workers yet, and one code-owned `orchestrator_stub` grant proves the non-dev runnable path; future worker delegation quarantines writes in spawned high-privilege workers per D92-L); and **`code` → `pi-coder`** (planned direct-coding assistant with direct `read`/`edit`/`write`/`bash`, **augmented to be Brunch-tooling-aware** — it also sees the graph read/capture tools). `execute` and `code` contrast deliberately: the orchestrator does not write directly, while the pi-coder will write directly (and does not delegate by default). `pi-coder` **instantiates** the augment case D58-L anticipated — its `SYSTEM.md` augments Pi's base coding-agent prompt rather than replacing it; whether other foreground roles should instead suppress/replace the base stays open per D58-L. (`code` is provisional naming for the pi-coder mode; rename is a one-token change. `code` remains in `PLANNED_OPERATIONAL_MODE_IDS` until built; `execute` is live.) Depends on: D23-L, D40-L, D58-L, D90-L, D92-L; I49-L. Establishing frontier: `subagent-reconciliation` (model + collapse land here; `execute` stood up here with the stub grant, while `code` remains declarative build-out — see PLAN). Supersedes: the three-record foreground-agent fragmentation as separate sources of truth; `defaultRole`/`allowedRoles` as a flexible many-roles-per-mode model (it is 1:1); the `executor` name for the execute-mode agent (now `orchestrator`). +- **D93-L — Operational mode and foreground agent collapse to one op-mode-keyed source of truth; the foreground roster is `elicit` / `execute` / `code`.** A foreground agent and its operational mode are 1:1 (D40-L: the foreground agent is derived from `op_mode`), so the prior **three-record fragmentation** — id enums in `src/session/schema/kinds.ts`, `OPERATIONAL_MODE_DEFINITIONS` + `AGENT_ROLE_DEFINITIONS` + `TOOL_POLICY_DEFINITIONS` in `src/projections/session/runtime-policy.ts`, and `AGENT_PROMPT_DEFINITIONS` in `src/.pi/extensions/agent-runtime/runtime/state.ts` (which duplicated `model`/`thinking`/`allowedStrategies`/`allowedLenses` across two of them) — collapses to a **single op-mode-keyed record**. An operational mode IS `{ foreground AgentManifest (D90-L), tool policy, canDelegate set }`; background agents live in a sibling `AgentManifest` registry, and the per-agent **`canDelegate`** field (D92-L generalized from op_mode-keyed to a manifest field) links a foreground mode to the background agents it may spawn — **code-owned for foreground modes** so the write-safety boundary (I49-L) holds; it also generalizes to background→background nesting. The foreground roster is three modes: **`elicit` → `elicitor`** (current: read-only + graph-writes-via-tools; delegates to read-only gatherers); **`execute` → `orchestrator`** (live initial standup: direct shell/file writes blocked, no delegated workers yet, and one code-owned `orchestrator_stub` grant proves the non-dev runnable path; future worker delegation quarantines writes in spawned high-privilege workers per D92-L); and **`code` → `pi-coder`** (planned direct-coding assistant with direct `read`/`edit`/`write`/`bash`, **augmented to be Brunch-tooling-aware** — it also sees the graph read/capture tools). `execute` and `code` contrast deliberately: the orchestrator does not write directly, while the pi-coder will write directly (and does not delegate by default). `pi-coder` **instantiates** the augment case D58-L anticipated — its `SYSTEM.md` augments Pi's base coding-agent prompt rather than replacing it; whether other foreground roles should instead suppress/replace the base stays open per D58-L. (`code` is provisional naming for the pi-coder mode; rename is a one-token change. `code` remains in `PLANNED_OPERATIONAL_MODE_IDS` until built; `execute` is live.) Depends on: D23-L, D40-L, D58-L, D90-L, D92-L; I49-L. Establishing frontier: `subagent-reconciliation` (model + collapse land here; `execute` stood up here with the stub grant, while `code` remains declarative build-out — see PLAN). Supersedes: the three-record foreground-agent fragmentation as separate sources of truth; `defaultRole`/`allowedRoles` as a flexible many-roles-per-mode model (it is 1:1); the `executor` name for the execute-mode agent (now `orchestrator`). - **D36-L — Spec/session selection is a reusable hierarchical decision model with transport-specific presentations.** Brunch owns a pure spec/session selection model that renders cwd-scoped inventory under the discovered project name without calling the user-created object a “workspace”. In TUI mode, the model may present a fast “continue last session” affordance when `.brunch/workspace.json` points to a valid spec+session; otherwise, or after “other spec/session”, the durable tree is: `create new spec → provide spec name → session created automatically`; `resume existing spec → choose existing spec → create a new session OR resume existing session → choose existing session`. The UI should not list every spec as a top-level action label; “resume existing spec” is the top-level intent, and the spec list is the next screen/scrollable selector. The model returns a product decision (`new spec`, `new session for spec`, `open session`, `continue selected session`, `cancel/quit`) without opening Pi sessions or mutating `.brunch/workspace.json` itself. The `WorkspaceSessionCoordinator` activates that decision and owns all persistence/session-binding effects. TUI startup and in-session paths share branded `pi-tui` components and colocated logo assets under `src/.pi/components/workspace-dialog`; adapters differ only in terminal lifecycle and Pi session-replacement mechanics (`ProcessTerminal`/`TUI.showOverlay` before Pi starts, `ctx.ui.custom(..., { overlay: true })` inside Pi), not in product semantics. RPC/headless transports must not invoke the TUI picker; they expose the same initial-selection requirement and activation decisions as JSON-RPC/product results so CLI JSON-RPC clients can select or create spec/session correctly. Depends on: D11-L, D21-L, D24-L, D33-L. Supersedes: implicit resume of `.brunch/workspace.json` on TUI launch, Pi `/resume`/`/new` as Brunch's product session chooser, one-off startup-only picker implementations, a flat action list that says “workspace” for specs, top-level `resume spec X` labels, and a separate intermediate action chooser for switching. - **D42-L — Session naming is Pi `session_info` presentation metadata, not spec identity.** Brunch-created sessions should be named at creation with neutral workspace-global defaults (`Untitled Session 1`, `Untitled Session 2`, …) so pickers/chrome never show an unnamed Brunch session and unchanged defaults do not collide across specs in the same cwd. These defaults are immediate lifecycle metadata, not LLM-generated summaries and not derived from the selected spec title. Brunch may later use Pi session lifecycle hooks to opportunistically replace a default with a short human-readable name that characterizes what happened in the transcript. The preferred generation trigger is `session_shutdown` for `quit`, `new`, and `resume` replacements because it sees the just-finished transcript and can name it before later picker lists need to distinguish sessions; `session_before_compact` or post-compaction (`session_compact`) may be used to refresh names after major summarization, and a manual/user rename command can force or override naming. The generation call should mirror the model-selection pattern in the local `summarize.ts` extension example: choose a cheap/fast authorized model, extract user/assistant text plus salient tool calls from the current branch, ask for a concise title, and append a Pi `session_info` entry through `SessionManager.appendSessionInfo`. Naming must be best-effort and non-blocking with a tight budget: failures, missing auth, empty transcripts, or shutdown aborts preserve the existing default/user label rather than blocking session replacement or exit. Session display names label sessions in pickers and chrome, but do not affect spec ids, session bindings, graph truth, or replay semantics. Depends on: D6-L, D17-L, D21-L, D35-L. Supersedes: using spec title or session UUID alone as the only durable display label once transcripts have meaningful content, leaving Brunch-created sessions unnamed, spec-local default numbering, or treating generated session names as canonical spec identity. -- **D58-L — Brunch prompt composition is a thin runtime header plus a gated prompt-resource manifest, not eager selection of every objective pack.** The architectural commitment is: composition stays a projection layer, not a behavioral state machine; detailed strategy/lens/method guidance lives in read-on-demand prompt resources rather than eager prompt-pack concatenation; runtime-state-gated availability is Brunch's sealed manifest, not ambient Pi discovery; pinned axes remain visible when role/mode-legal while readiness constrains AUTO menus and gated methods/tools; and the pushed-context slice stays compact, with deeper access governed by D60-L. Current prompt-resource topology, manifest emission, file-owned skill metadata, and ownership split across `.pi/agents/`, `.pi/skills/`, `.pi/extensions/system-prompts/`, and `.pi/extensions/runtime/` live in [`src/.pi/README.md`](src/.pi/README.md), [`src/.pi/agents/README.md`](src/.pi/agents/README.md), [`src/.pi/skills/README.md`](src/.pi/skills/README.md), [`src/.pi/extensions/README.md`](src/.pi/extensions/README.md), [`src/.pi/extensions/system-prompts/compose.ts`](src/.pi/extensions/system-prompts/compose.ts), and [`src/.pi/extensions/runtime/state.ts`](src/.pi/extensions/runtime/state.ts). **Base-prompt relationship (validated 2026-06-18, slice 1):** the `before_agent_start` handler **appends** Brunch's composed block (now led by the agent `SYSTEM.md` body, then runtime header + manifests) to Pi's base system prompt (`${basePrompt}\n\n${composed}`), so a foreground agent currently *augments* Pi's base coding-agent prompt rather than replacing it. Whether a foreground role's `SYSTEM.md` body should suppress or replace that base is **open** and tied to the future `pi-coder` op-mode (which deliberately augments Pi's coding agent); the `elicitor` augmenting a coding base is a known follow-on question, not a settled choice. Refined by: D93-L (the `code`→`pi-coder` foreground mode instantiates the augment case; the replace option for other roles stays open). Composition is projection, not a behavioral state machine. Depends on: D23-L, D25-L, D39-L, D40-L, D52-L, D59-L, D60-L. Refined by: D85-L (implemented 2026-06-18/19: the manifest drops `` — two axes `strategy` + `lens` — and the `goal` body inlines into the `elicitor` role prompt) and by the 2026-06-22 prompt-skill-topology slice (all prompt resources adopt Agent Skills `SKILL.md` topology; `description` becomes file-owned frontmatter; the emitted wrapper becomes `` with per-skill ``). Supersedes: the flat "base + mode + role + strategy + lens + grade + …" layering; the fixed all-packs concatenation in `compose-brunch-prompt.ts`; "role preset / runtime bundle" as the composition unit; direct Layer-2 eager prompt-pack injection as the default mechanism; top-level `src/agents/` for Pi-only agents; and `capability` as a parallel name for `method`. +- **D58-L — Brunch prompt composition is a thin runtime header plus a gated prompt-resource manifest, not eager selection of every objective pack.** The architectural commitment is: composition stays a projection layer, not a behavioral state machine; detailed strategy/lens/method guidance lives in read-on-demand prompt resources rather than eager prompt-pack concatenation; runtime-state-gated availability is Brunch's sealed manifest, not ambient Pi discovery; pinned axes remain visible when role/mode-legal while readiness constrains AUTO menus and gated methods/tools; and the pushed-context slice stays compact, with deeper access governed by D60-L. Current prompt-resource topology, manifest emission, file-owned skill metadata, and ownership split across `.pi/agents/`, `.pi/skills/`, `.pi/extensions/agent-runtime/system-prompts/`, and `.pi/extensions/agent-runtime/runtime/` live in [`src/.pi/README.md`](src/.pi/README.md), [`src/.pi/agents/README.md`](src/.pi/agents/README.md), [`src/.pi/skills/README.md`](src/.pi/skills/README.md), [`src/.pi/extensions/README.md`](src/.pi/extensions/README.md), [`src/.pi/extensions/agent-runtime/system-prompts/compose.ts`](src/.pi/extensions/agent-runtime/system-prompts/compose.ts), and [`src/.pi/extensions/agent-runtime/runtime/state.ts`](src/.pi/extensions/agent-runtime/runtime/state.ts). **Base-prompt relationship (validated 2026-06-18, slice 1):** the `before_agent_start` handler **appends** Brunch's composed block (now led by the agent `SYSTEM.md` body, then runtime header + manifests) to Pi's base system prompt (`${basePrompt}\n\n${composed}`), so a foreground agent currently *augments* Pi's base coding-agent prompt rather than replacing it. Whether a foreground role's `SYSTEM.md` body should suppress or replace that base is **open** and tied to the future `pi-coder` op-mode (which deliberately augments Pi's coding agent); the `elicitor` augmenting a coding base is a known follow-on question, not a settled choice. Refined by: D93-L (the `code`→`pi-coder` foreground mode instantiates the augment case; the replace option for other roles stays open). Composition is projection, not a behavioral state machine. Depends on: D23-L, D25-L, D39-L, D40-L, D52-L, D59-L, D60-L. Refined by: D85-L (implemented 2026-06-18/19: the manifest drops `` — two axes `strategy` + `lens` — and the `goal` body inlines into the `elicitor` role prompt) and by the 2026-06-22 prompt-skill-topology slice (all prompt resources adopt Agent Skills `SKILL.md` topology; `description` becomes file-owned frontmatter; the emitted wrapper becomes `` with per-skill ``). Supersedes: the flat "base + mode + role + strategy + lens + grade + …" layering; the fixed all-packs concatenation in `compose-brunch-prompt.ts`; "role preset / runtime bundle" as the composition unit; direct Layer-2 eager prompt-pack injection as the default mechanism; top-level `src/agents/` for Pi-only agents; and `capability` as a parallel name for `method`. #### Continuity & origination (turn-boundary choreography) @@ -289,7 +289,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c - **D67-L — Brunch tracks the latest pi release; dev iterates against pi source via a gated runtime alias.** Brunch keeps `@earendil-works/pi-*` current with upstream rather than pinning to an old line; version bumps are routine adaptation work, not deferred migrations. Local vite/vitest development aliases `@earendil-works/pi-ai`, `@earendil-works/pi-agent-core`, `@earendil-works/pi-tui`, and `@earendil-works/pi-coding-agent` to the sibling `pi-mono` `src/` checkout via an explicit `PI_SOURCE` runtime flag so cross-package iteration needs no rebuild in those loops; published builds, TypeScript, editors, and default runtime resolve the normal installed `dist`. Base `tsconfig.json` deliberately carries no pi source `paths` because paths cannot be env-gated; if a `tsx` real-provider loop later needs no-rebuild pi source, add an opt-in `tsconfig.dev.json` rather than weakening the default. Inaugural bump: `^0.75.5 → 0.79.0`. Depends on: A25-L, D39-L. Supersedes: pinning Brunch to a fixed older pi line, treating pi upgrades as discrete migration projects, or making a personal source checkout the unconditional type/default resolution path. - **D68-L — Development feedback loops are first-class DX, consolidated behind one front door, distinct from product-verification probes.** Brunch maintains three named developer loops: (1) **faux loop** — deterministic, in-process `AgentSession` over the pi faux provider + `.inMemory()` services, the inner/middle-loop substrate for wrapper logic and regressions; (2) **real-provider TUI/CLI loop** — `tsx`-run Brunch source against a live model for interactive use, with pi-source resolution opt-in per D67-L only when needed; (3) **introspection loop** — real provider plus payload/manifest capture (D69-L). These loops live behind a single consolidated dev front door (`src/dev/`) that owns the dev launchers and the shared faux-harness factory; ad hoc per-file faux setup is absorbed into that factory. The dev loops are the *means of building and iterating on* Brunch and are distinct from `src/probes/` **probe runs**, which are durable *product-verification* artifacts (`.fixtures/runs/`, `docs/architecture/probes-and-transcripts.md`); where a dev loop produces durable evidence it does so as a probe run rather than a parallel artifact path. Depends on: D39-L, D67-L; the probe/transcript model. Supersedes: scattered, unnamed dev-iteration scripts and ad hoc faux-provider wiring as the wrapper's test substrate. -- **D69-L — Agent-input introspection is one read-only, dev-gated Brunch extension; mechanical and conversational modes are separate planes.** The architectural commitment is that introspection remains a single Brunch-owned, dev-gated, read-only extension family wired explicitly through the sealed Brunch Pi bundle: a passive final-payload observer plus separate read-only query tools, registered late enough to see post-mutation payloads, observing but never shaping product behavior, with registration distinct from advertisement. Current materialized state lives in [`src/dev/README.md`](src/dev/README.md), [`src/.pi/extensions/README.md`](src/.pi/extensions/README.md), [`src/.pi/extensions/introspection/README.md`](src/.pi/extensions/introspection/README.md), [`src/.pi/extensions/introspect-query/README.md`](src/.pi/extensions/introspect-query/README.md), [`src/.pi/extensions/session-query/README.md`](src/.pi/extensions/session-query/README.md), and [`src/app/pi-extensions.ts`](src/app/pi-extensions.ts). Direct diagnostic for the "Prompt-resource discretionary loading" blind spot (I38-L). Depends on: D39-L, D40-L, D58-L, D68-L, D70-L; I38-L. Supersedes: treating "how the model sees our tools/skills" as an outer-loop-only, non-instrumented concern, and the fixed structured self-report schema as the default conversational surface. +- **D69-L — Agent-input introspection is one read-only, dev-gated Brunch extension; mechanical and conversational modes are separate planes.** The architectural commitment is that introspection remains a single Brunch-owned, dev-gated, read-only extension family wired explicitly through the sealed Brunch Pi bundle: a passive final-payload observer plus separate read-only query tools, registered late enough to see post-mutation payloads, observing but never shaping product behavior, with registration distinct from advertisement. Current materialized state lives in [`src/dev/README.md`](src/dev/README.md), [`src/.pi/extensions/README.md`](src/.pi/extensions/README.md), [`src/.pi/extensions/dev-mode/introspection/README.md`](src/.pi/extensions/dev-mode/introspection/README.md), [`src/.pi/extensions/dev-mode/introspect-query/README.md`](src/.pi/extensions/dev-mode/introspect-query/README.md), [`src/.pi/extensions/dev-mode/session-query/README.md`](src/.pi/extensions/dev-mode/session-query/README.md), and [`src/app/pi-extensions.ts`](src/app/pi-extensions.ts). Direct diagnostic for the "Prompt-resource discretionary loading" blind spot (I38-L). Depends on: D39-L, D40-L, D58-L, D68-L, D70-L; I38-L. Supersedes: treating "how the model sees our tools/skills" as an outer-loop-only, non-instrumented concern, and the fixed structured self-report schema as the default conversational surface. - **D70-L — `.fixtures/` is a four-role tree (seeds / workbenches / runs / scratch); dev-loop artifacts decouple operating-cwd from artifact-root.** `.fixtures/` separates four lifecycles, each with its own git policy: **`seeds/`** — tracked, reusable explicit-basis starting truth consumed by the seed loader (INPUT), never local runtime DB state; **`workbenches/`** — launchable Brunch workspaces whose `.brunch/` is gitignored local state (the directories a dev `--cwd` targets, D71-L); **`runs/`** — tracked, *curated/promoted* probe evidence under `//`, probe-first per D68-L (EVIDENCE); **`scratch/`** — gitignored, ephemeral live dev-loop output under `//` (SCRATCH). Dev launchers (faux/introspection) must resolve their artifact root to the package-relative repo `.fixtures/scratch/`, **not** to the operating `cwd` — the same operating-cwd-vs-`fixtureRoot` decoupling the probe layer already uses (`mkdtemp` ephemeral cwd + repo-resolved `fixtureRoot`). This removes the `join(cwd, '.fixtures', …)` nesting defect where launching against a workbench would write `/.fixtures/…`. An exploratory scratch run becomes durable evidence only by explicit promotion (move `scratch///` → `runs///`, then track it), keeping curated `runs/` clean. `.fixtures/scratch/` is the chosen scratch home (over reusing `tmp/`) so promotion is a move within one tree. Depends on: D52-L, D68-L; the probe/transcript model. Supersedes: pinning dev-run artifacts to the operating cwd; treating all `.fixtures/runs/` output as tracked evidence; leaving the `workbenches/` role undocumented. - **D71-L — One `BRUNCH_DEV` switch gates all dev affordances; the main CLI accepts `--cwd`; introspection is present-but-dead in prod.** The over-specific `BRUNCH_DEV_RPC` env var is generalized to a single `BRUNCH_DEV` switch that, when set, enables dev affordances together: dev RPC methods (`dev.*`), registration of the read-only introspection extension (D69-L), and routing of dev-loop artifacts to `.fixtures/scratch/` (D70-L). `runBrunchCli` parses a `--cwd ` flag (defaulting to `process.cwd()`) so a dev session can target a `.fixtures/workbenches/` workspace without `cd`. Two independent prod-safety gates hold: (1) `src/dev/**` is build-excluded by `tsconfig.build.json`, so launchers/harness/alias never ship; (2) the introspection extension, though compiled into `dist` under `src/.pi/`, only *registers* when `createBrunchPiExtensions(..., { introspection: { enabled } })` opts in — and the TUI call site sets `enabled` from `BRUNCH_DEV` only, so absent the switch it is present-but-dead, never wired, honoring D39-L explicit-opt-in sealing (no ambient discovery). Brunch-launched TUI sessions keep Pi startup update suppression on in both product and `BRUNCH_DEV` runs by scoping `PI_OFFLINE=1` through `InteractiveMode.run()` unless the user already set a value; prior `PI_OFFLINE` / `PI_SKIP_VERSION_CHECK` state is restored in `finally`, never as a leaked global `process.env` mutation. Depends on: D39-L, D67-L, D68-L, D69-L, D70-L. Supersedes: the `BRUNCH_DEV_RPC`-only dev gate; relying on the operating cwd to choose the dev workspace; the assumption that the introspection extension needs build-exclusion (runtime opt-in suffices); lifting Pi offline mode in `BRUNCH_DEV` TUI sessions merely to enable live-provider behavior. - **D79-L — Dev DB seeding is explicit, selected, and target-workspace-scoped; `npm run dev` never implies a seed.** A Brunch workspace DB is local runtime state under that launch cwd's `.brunch/`; running `npm run dev` against the repo root or a workbench may create/open that workspace, but it must not silently load reusable seed fixtures. Reusable graph seeds under `.fixtures/seeds//.json` are loaded only by an explicit seed command that names the target workspace and the seed set/slug (or an explicitly requested all-seeds batch); the loader remains a graph-domain utility over `seedFixture`/`CommandExecutor`, so seeded specs get normal `create_spec`/`mutate_graph` change-log entries, spec-local LSNs, elicitation-gap seeding, and structural validation. Workbenches under `.fixtures/workbenches//` are launchable cwd containers, not seed truth: their `.brunch/` may be reset or re-seeded locally, but tracked files must document which seed(s) a human or script should apply. Captured or newly-authored seed JSON is parked until it has at least one named consumer disposition (`test`, `preview`, `manual workbench`, `probe input`, or `parked`); existence under `seeds/` alone does not make it part of the default dev database. Depends on: D16-L, D20-L, D52-L, D70-L, D71-L. Supersedes: the catch-all `npm run seed` mental model that loads every seed into the current shell cwd; treating the repo-root `.brunch/` as canonical dev fixture state; auto-seeding because a dev host starts. @@ -352,24 +352,24 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c | I22-L | Brunch TUI startup must not render prior session transcript entries or enter an agent loop until the user has explicitly activated a spec/session decision; creating a new spec implicitly creates its first session, creating a new session for an existing spec lands in a binding-only session, resuming a prior transcript is opt-in, and RPC/headless startup exposes structured initial-selection state rather than invoking TUI picker code. | covered (FE-744 coordinator tests; hierarchical spec/session picker model + component tests; `workspace.selectionState` / `workspace.activate` JSON-RPC contract tests with source assertion that RPC does not import TUI picker code; `src/probes/scripts/verify-startup-no-resume.sh` pty/ANSI-stripped TUI probe oracle proving stale transcript text is absent before explicit activation) | D11-L, D21-L, D22-L, D36-L | | I23-L | Every structured elicitation interaction that owns the response surface persists durable semantic display only through Pi `toolResult` rows rendered by `renderResult`; `renderCall` and live `ctx.ui.*` surfaces are transient. A structured-exchange tuple has a recoverable `present_*` result and, when required, exactly one terminal response result before the next agent turn consumes it; `present_question` derives free-text vs choice vs multi-choice from its own structure and `request_response` is the single terminal tool that routes every present's response (free-text/choice/multi-choice for `present_question`, review for `present_review_set`) from pending transcript state. The target details model is checked by `schema` + `v`, `exchange_id`, and `tool_meta`; request outcomes are an exactly-one property-presence union; user-authored text is `comment` and runtime-authored text is `message`; present-side status/kind/expected-request aliases and capture graph payloads are invalid in the Zod-authored schema layer. `toolResult.content` is rich markdown suitable for both TUI transcript display and model context; `toolResult.details` carries structured projection/recovery data. In TUI-driven sessions, `request_response` answers free-text prompts from the interactive editor when present; the live exchange broker is the headless/web-driver fallback, not an override of the TUI response surface. | covered for current structured-exchange tools (registered sequential `present_question`, `present_review_set`, and `request_response`; retired `present_options`, `request_answer`, `request_choice`, `request_choices`, and `request_review` tools are unregistered while their request result-detail discriminants are preserved; runtime details are emitted from canonical `schema`/`v`/snake_case Zod shapes; tests cover non-semantic `renderCall`, markdown `renderResult`, present/request details, unmatched-present recovery, active-vs-stub registry, JSON-editor fallback for multi-choice, TUI-editor precedence over an attached live broker for `request_response`, broker fallback when no interactive editor exists, `request_response` for `present_question` through the shared answer-source and choice-source dispatchers (free-text editor/broker/cancellation, TUI-only single-choice and multi-choice, headless choice unavailable, unknown diagnostics, and recovery continuation), `request_response` for `present_review_set` through the shared review source (approve/request-changes/reject with required change-request comment, cancellation, headless unavailable, emitting preserved `request_review` result details), terminal `answered`/`cancelled`/`unavailable` projection closure, option content/rationale parity, review-set `nodes`/`edges` details parity, invalid review proposal non-recovery, review pending-exchange recovery, public-RPC deterministic permutations, capture response-to-graph proof, and same-assistant-message `present_question → request_response` ordering over a real Pi RPC run. The Zod-authored schema layer is covered by JSON Schema export, drift-rejection, and source-boundary tests for present/request/capture details; `present_review_set.payload` imports the graph-owned boundary-teaching payload schema (not `z.unknown()`), so a JSON-string payload, the `mutate_graph` `{createBasis, ops}` shape, or malformed nested companions such as `grounding: string` are rejected at the param boundary rather than deep in the executor, while requiredness and field-level structural diagnostics stay owned by `graph/review-set.ts`. `present_question`'s params make the present-side choice structural: `options[]` presence selects choice mode and `multiple` selects multi-choice, so the model no longer chooses between separate question/options tools. `present_candidates` remains a named stub and intentionally unregistered.) | D12-L, D13-L, D17-L, D37-L, D38-L, D41-L | | I24-L | A Brunch-launched Pi runtime does not load ambient user/project Pi context files, extensions, skills, prompt templates, themes, or behavior-shaping settings unless Brunch's sealed Pi settings/extension boundary explicitly allows them; Brunch-owned extension-discovered resources are identified as intentional product resources. | covered for TUI-launch settings/extension boundary by contract tests: ambient resource flags and explicit extension factories are preserved; hostile ambient global/project settings are ignored by the in-memory Brunch settings policy before and after reload; audited Pi settings getters are tracked in `src/.pi/brunch-pi-settings.ts`. Subagent child-session sealing is covered separately under I29-L. | D2-L, D39-L | -| I25-L | The active `op_mode`, `strategy`, and `lens` are reconstructable from linear `brunch.agent_runtime_state` entries at turn start and through `session.runtimeState`; concrete axis ids stay separate from the `auto` selection sentinel; the foreground session-agent role is derived from `op_mode`, not separately stored; tool gating follows the reconstructed `op_mode` so `elicit` cannot use execute/dangerous tools such as raw `bash`/`write` unless explicitly permitted. Runtime-state projection remains transcript-backed and exposes empty/default mention, world-watermark, and lifecycle slots without inventing hidden extension memory; legal option/default affordances are pure projections over resolved runtime state plus capability-readiness over gaps (D74-L), not persisted state. | covered (`src/session/runtime-state.test.ts` covers default state, cumulative last-writer-wins posture, mention/world/lifecycle slot projection, and non-linear rejection; `src/rpc/handlers.test.ts` covers explicit-target `session.runtimeState` discovery/params/spec validation; `src/.pi/__tests__/operational-mode.test.ts` covers append/project/switch helpers over the reconciled two-axis vocabulary, AUTO selection for every runtime axis, stale `agentGoal` tolerance on existing transcript entries, init idempotence, previous-state values, malformed/illegal tuple rejection, role derivation from `op_mode`, and Pi JSONL reload projection; `prompting.test.ts` covers prompt/tool-policy projection from the same transcript-backed runtime state, including selected-spec gap activation for `present_review_set` / `request_review` proposal tools; `src/.pi/extensions/runtime/authority-matrix.test.ts` covers the current POC authority matrix for `elicit-read-only`, including base-allowed local/web read tools, blocking `bash`/`edit`/`write`, and structured `needs_human` result representability while leaving A18-L strict built-in suppression as residue; `src/projections/session/affordances.test.ts` covers shared strategy/lens legal options, defaults, AUTO freestyle exclusion, pinned freestyle, gap-driven gated legality, and a live coverage flip; `src/session/runtime-affordances-coverage.test.ts` guards the required-vs-deferred affordance ledger). | D17-L, D23-L, D40-L, D58-L, D59-L, D66-L, D85-L | +| I25-L | The active `op_mode`, `strategy`, and `lens` are reconstructable from linear `brunch.agent_runtime_state` entries at turn start and through `session.runtimeState`; concrete axis ids stay separate from the `auto` selection sentinel; the foreground session-agent role is derived from `op_mode`, not separately stored; tool gating follows the reconstructed `op_mode` so `elicit` cannot use execute/dangerous tools such as raw `bash`/`write` unless explicitly permitted. Runtime-state projection remains transcript-backed and exposes empty/default mention, world-watermark, and lifecycle slots without inventing hidden extension memory; legal option/default affordances are pure projections over resolved runtime state plus capability-readiness over gaps (D74-L), not persisted state. | covered (`src/session/runtime-state.test.ts` covers default state, cumulative last-writer-wins posture, mention/world/lifecycle slot projection, and non-linear rejection; `src/rpc/handlers.test.ts` covers explicit-target `session.runtimeState` discovery/params/spec validation; `src/.pi/__tests__/operational-mode.test.ts` covers append/project/switch helpers over the reconciled two-axis vocabulary, AUTO selection for every runtime axis, stale `agentGoal` tolerance on existing transcript entries, init idempotence, previous-state values, malformed/illegal tuple rejection, role derivation from `op_mode`, and Pi JSONL reload projection; `prompting.test.ts` covers prompt/tool-policy projection from the same transcript-backed runtime state, including selected-spec gap activation for `present_review_set` / `request_review` proposal tools; `src/.pi/extensions/agent-runtime/runtime/authority-matrix.test.ts` covers the current POC authority matrix for `elicit-read-only`, including base-allowed local/web read tools, blocking `bash`/`edit`/`write`, and structured `needs_human` result representability while leaving A18-L strict built-in suppression as residue; `src/projections/session/affordances.test.ts` covers shared strategy/lens legal options, defaults, AUTO freestyle exclusion, pinned freestyle, gap-driven gated legality, and a live coverage flip; `src/session/runtime-affordances-coverage.test.ts` guards the required-vs-deferred affordance ledger). | D17-L, D23-L, D40-L, D58-L, D59-L, D66-L, D85-L | | I27-L | Session display names are presentation metadata only: every Brunch-created session gets a neutral workspace-global default `session_info` label (`Untitled Session N`) at creation, unchanged defaults do not collide across specs in one cwd, later user/generated names may replace the default, and no naming path mutates spec identity, session binding, or graph truth. | planned (creation/boundary tests for workspace-global default allocation across specs and replacement sessions; session-lifecycle naming tests with empty transcript/auth failure/success paths; picker/chrome projection tests read session names when present) | D6-L, D21-L, D35-L, D42-L | | I26-L | Runtime schema-library imports stay deliberately scoped: Zod may appear in D41-L-acknowledged product/protocol schema seams — the structured-exchange schemas (`src/.pi/extensions/exchanges/schemas/`), the graph-owned `present_review_set` payload teaching schema co-located with its deep validator (`src/graph/review-set.ts`), and the dev-gated query-tool params (`src/.pi/extensions/{session-query,introspect-query}/`), each converting to Pi `TSchema` only through a single per-plane `z.toJSONSchema(..., { unrepresentable: 'throw' })` cast adapter (`exchanges/pi-schema.ts`, `shared/pi-tool-schema.ts`); TypeBox remains valid for unrelated Pi tool parameters (e.g. graph tools), small config/frontmatter contracts, and Drizzle-derived row schemas; no boundary may hand-author parallel Zod and TypeBox sources for the same shape. Pi tool parameter schemas authored in Zod must export JSON Schema draft 2020-12 (Zod v4 default), so tuples emit `prefixItems` rather than the draft-07 array-`items`/`additionalItems` form that strict provider validators (Anthropic) reject. Drizzle row/insert/update schemas are not hand-authored alongside their target tables. | covered (structured-exchange schema tests prove Zod parse/export and assert semantic details contracts stay in `src/.pi/extensions/exchanges/schemas/` except for the graph-owned review-set payload teaching schema imported from `src/graph/review-set.ts`; the legacy `shared/model.ts` details interface is retired; structured-exchange TypeBox usage is quarantined to the single Pi `TSchema` cast adapter in `src/.pi/extensions/exchanges/pi-schema.ts`, and the dev query tools to `src/.pi/extensions/shared/pi-tool-schema.ts`; `session-query`/`introspect-query` tests assert the advertised parameter schema is draft 2020-12 with no draft-07 tuple form; the no-direct-`db/`-imports-outside-`graph/` boundary is enforced statically by oxlint `no-restricted-imports` (`.oxlintrc.json`), with the residual `architecture.test.ts` greps covering only the db→graph kinds-only edge and `db/schema.ts` enum-array ownership that lint cannot express; Drizzle derivation via `drizzle-typebox` in `row-schemas.ts`) | D41-L | | I28-L | Auto-compaction output preserves the configured anchor set byte-stable: every entry kind listed in [src/.pi/extensions/compaction/index.ts](src/.pi/extensions/compaction/index.ts) is reconstructable post-compaction according to its `select` rule (`first | latest | active-leaves | all-unresolved`); LLM-generated narrative summary never replaces or rephrases preserved-anchor content; extension failure falls through to Pi default compaction rather than dropping anchors silently. | planned (compaction round-trip property tests at M9 plus inner-loop anchor-rendering unit tests and TypeBox schema validation of the anchor contract) | D43-L; R15, R13; I3-L, I4-L, I8-L, I12-L | | I29-L | Subagent SDK child sessions inherit Brunch Pi Profile sealing while allowing explicitly injected parent-world reads: every `subagent` tool invocation builds an in-process `AgentSession` from explicit sealed services (in-memory auth/settings/session managers, no ambient resources, assembled background system prompt, parent model registry, explicit tool allowlist); subagents never load ambient user/project `.pi/` skills, prompts, themes, extensions, context files, or behavior-shaping settings; subagents never gain direct access to the parent's `CommandExecutor`, Brunch RPC handlers, or graph persistence; parent world access is injected by the app root as a snapshot prompt block plus selected-spec read tools such as `read_graph`; parent aborts prevent prompt execution before/during setup and abort live child sessions; subagent results return to the main agent only as tool result content (no side-effect transcript writes). | covered for the implemented SDK seam by `src/.pi/extensions/subagents/subagents.test.ts`: frontmatter/config validation (including duplicate keys), explicit registry loading from `src/.pi/agents//SYSTEM.md` while ignoring unlisted planted bodies, tool allowlist conformance for `explorer`/`projector`/`researcher`, sealed faux-provider child sessions with no inherited base prompt or conversation, assembled prompt snapshot coverage (selected spec/workspace/session digest, no foreground elicitation recommendation), unknown-tool failure, `read_graph` availability only with injected parent graph readers, parent-spec-only graph read content with sibling-spec negative assertion, bounded concurrency including waiter/new-arrival race, invalid invocation shape rejection before runner call, and parent-abort setup/live-session behavior. Startup advertisement remains dev-gated by whether a launch path supplies subagent deps to `createBrunchPiExtensions(...)`. | D2-L, D39-L, D40-L, D44-L, D91-L; I1-L, I2-L, I11-L, I24-L | -| I30-L | Elicitor capture commits only high-confidence graph truth; under the D81-L gradient, directly-stated facts commit `explicit`, confidently-materialized facts/edges commit `implicit`, low-confidence noticings never become graph truth — they map to existing-or-new `elicitation_gaps` as agenda — and contradictions with existing graph truth route to `reconciliation_need` rather than gap or overwrite. | covered for deterministic routing (`src/graph/__tests__/capture-commitment-gradient-gate.test.ts` proves the FE-861 routing gate through the real `mutate_graph`, `update_elicitation_gaps`, and `update_reconciliation_needs` adapters: explicit→commit, implicit→commit, low→one gap, contradiction→one semantic-conflict recon need, structural answered derivation, manual gap close on the graph clock, illegal capture batches failing loud, and the closed capture-quality-spike scenario family re-aimed from binary `shouldCommit` to gradient `expectedOutcome` rows across free prose, file refs, implication-heavy, and contradiction classes. `src/probes/capture-quality-loop.ts` keeps the LLM-in-loop probe as fitness by scoring gradient-routing accuracy, not gating classification quality. `src/.pi/extensions/reconciliation/index.test.ts` proves the recon-need tool pair over `CommandExecutor`/`getOpenReconciliationNeeds` plus elicit-posture legality. `src/projections/session/sweep-watermark.test.ts` plus `src/.pi/__tests__/extension-registry.test.ts` prove the D80-L transcript-position sweep watermark: conversational/digest tail classification, raw background exclusion, idempotent marker advance, graph-LSN watermark separation, and live `before_agent_start` wiring. The submit-time labeled-prefix capture module, its `session.*` wiring, and the `capture-response-to-graph` / `submit-message-capture` proofs were deleted 2026-06-19 (D80-L fossil retirement); `session.submitMessage` / `session.submitExchangeResponse` results no longer carry a `capture` field. Confidence/dedup quality remains fitness.) | D8-L, D18-L, D47-L, D65-L, D80-L, D81-L; A22-L | -| I31-L | Readiness never bars graph truth or work; it is just-in-time capability-readiness over relevant gaps, not a stored grade or kind whitelist. There is no `readiness_grade` scalar; capability availability is judged on request against the relevant `elicitation_gaps` (D74-L) and may proceed, proceed at low epistemic status, or negotiate — it never refuses outright. The `CommandExecutor` must not reject a graph node solely because its kind belongs to a later readiness band (D64-L). The soft `readiness estimate` (D45-L) is UI-only and gates nothing. Capability-readiness never *withholds a graph-write tool*: `mutate_graph` and the review-set tools stay in the active tool set regardless of readiness; `negotiate` is advisory (establishment offer + epistemic scaling), never a tool gate (D86-L). | partially covered (`src/projections/session/capability-readiness.test.ts` covers the D74-L tracer gate, including proceed / proceed_low_epistemic / negotiate, no-refusal, no grade-symbol import, and a live `presence` coverage flip; `src/projections/session/affordances.test.ts` covers the first consumer rewire: menu legality omits gated options while relevant gaps negotiate and includes them when coverage rises, with no grade symbols in `runtime-policy.ts` / `affordances.ts`, and a required `NodeKind` absent from the gap register fails loud (config bug ≠ uncovered — readiness omission never masks a seeding error); `src/projections/session/readiness-estimate.test.ts` covers the soft D45-L estimate shape, empty-band zero, importance-weighted per-band coverage, honest regression, and no legality-path imports; `src/.pi/extensions/runtime/state.test.ts`, `src/.pi/extensions/system-prompts/__tests__/compose.test.ts`, `src/.pi/extensions/system-prompts/seed/workspace.test.ts`, and `src/.pi/__tests__/prompting.test.ts` cover the prompt consumer path: selected-spec gaps render as the soft per-band estimate, readiness-thin pinned axes remain visible, gated methods stay withheld, `readiness_grade=` is absent from prompt display, and the turn boundary threads the same gaps into cwd context without prompt-assembly failure; `src/session/workspace-session-coordinator.test.ts`, `src/renderers/workspace/workspace-state.test.ts`, `src/session/workspace-overview-context.test.ts`, `src/.pi/__tests__/context-tools.test.ts`, `src/rpc/handlers.test.ts`, and `src/web/app.test.tsx` cover the workspace/chrome display retirement: `chrome.phase` / `chrome.chatMode` no longer project through coordinator/RPC/web/chrome fixtures, and workspace overview session inventory no longer carries or renders `readinessGrade`; `createSpec` / `getSpec` persistence, seed/export fixture contracts, probes, and selected-spec prompt carriers no longer persist or transport a readiness grade; the D86-L graph-write-tool-floor sub-claim is covered — `state.test.ts` proves `mutate_graph` + review-set tools stay floor while `propose-graph`/`project-graph` readiness `negotiate`s and only the non-graph-write `review-for-gaps` is withheld, and `dev/__tests__/tier-2-harness.test.ts` proves the same through a real `runBrunchTui` boot at thin vs covered grounding) | D20-L, D45-L, D64-L, D74-L, D86-L | +| I30-L | Elicitor capture commits only high-confidence graph truth; under the D81-L gradient, directly-stated facts commit `explicit`, confidently-materialized facts/edges commit `implicit`, low-confidence noticings never become graph truth — they map to existing-or-new `elicitation_gaps` as agenda — and contradictions with existing graph truth route to `reconciliation_need` rather than gap or overwrite. | covered for deterministic routing (`src/graph/__tests__/capture-commitment-gradient-gate.test.ts` proves the FE-861 routing gate through the real `mutate_graph`, `update_elicitation_gaps`, and `update_reconciliation_needs` adapters: explicit→commit, implicit→commit, low→one gap, contradiction→one semantic-conflict recon need, structural answered derivation, manual gap close on the graph clock, illegal capture batches failing loud, and the closed capture-quality-spike scenario family re-aimed from binary `shouldCommit` to gradient `expectedOutcome` rows across free prose, file refs, implication-heavy, and contradiction classes. `src/probes/capture-quality-loop.ts` keeps the LLM-in-loop probe as fitness by scoring gradient-routing accuracy, not gating classification quality. `src/.pi/extensions/brunch-data/reconciliation/index.test.ts` proves the recon-need tool pair over `CommandExecutor`/`getOpenReconciliationNeeds` plus elicit-posture legality. `src/projections/session/sweep-watermark.test.ts` plus `src/.pi/__tests__/extension-registry.test.ts` prove the D80-L transcript-position sweep watermark: conversational/digest tail classification, raw background exclusion, idempotent marker advance, graph-LSN watermark separation, and live `before_agent_start` wiring. The submit-time labeled-prefix capture module, its `session.*` wiring, and the `capture-response-to-graph` / `submit-message-capture` proofs were deleted 2026-06-19 (D80-L fossil retirement); `session.submitMessage` / `session.submitExchangeResponse` results no longer carry a `capture` field. Confidence/dedup quality remains fitness.) | D8-L, D18-L, D47-L, D65-L, D80-L, D81-L; A22-L | +| I31-L | Readiness never bars graph truth or work; it is just-in-time capability-readiness over relevant gaps, not a stored grade or kind whitelist. There is no `readiness_grade` scalar; capability availability is judged on request against the relevant `elicitation_gaps` (D74-L) and may proceed, proceed at low epistemic status, or negotiate — it never refuses outright. The `CommandExecutor` must not reject a graph node solely because its kind belongs to a later readiness band (D64-L). The soft `readiness estimate` (D45-L) is UI-only and gates nothing. Capability-readiness never *withholds a graph-write tool*: `mutate_graph` and the review-set tools stay in the active tool set regardless of readiness; `negotiate` is advisory (establishment offer + epistemic scaling), never a tool gate (D86-L). | partially covered (`src/projections/session/capability-readiness.test.ts` covers the D74-L tracer gate, including proceed / proceed_low_epistemic / negotiate, no-refusal, no grade-symbol import, and a live `presence` coverage flip; `src/projections/session/affordances.test.ts` covers the first consumer rewire: menu legality omits gated options while relevant gaps negotiate and includes them when coverage rises, with no grade symbols in `runtime-policy.ts` / `affordances.ts`, and a required `NodeKind` absent from the gap register fails loud (config bug ≠ uncovered — readiness omission never masks a seeding error); `src/projections/session/readiness-estimate.test.ts` covers the soft D45-L estimate shape, empty-band zero, importance-weighted per-band coverage, honest regression, and no legality-path imports; `src/.pi/extensions/agent-runtime/runtime/state.test.ts`, `src/.pi/extensions/agent-runtime/system-prompts/__tests__/compose.test.ts`, `src/.pi/extensions/agent-runtime/system-prompts/seed/workspace.test.ts`, and `src/.pi/__tests__/prompting.test.ts` cover the prompt consumer path: selected-spec gaps render as the soft per-band estimate, readiness-thin pinned axes remain visible, gated methods stay withheld, `readiness_grade=` is absent from prompt display, and the turn boundary threads the same gaps into cwd context without prompt-assembly failure; `src/session/workspace-session-coordinator.test.ts`, `src/renderers/workspace/workspace-state.test.ts`, `src/session/workspace-overview-context.test.ts`, `src/.pi/__tests__/context-tools.test.ts`, `src/rpc/handlers.test.ts`, and `src/web/app.test.tsx` cover the workspace/chrome display retirement: `chrome.phase` / `chrome.chatMode` no longer project through coordinator/RPC/web/chrome fixtures, and workspace overview session inventory no longer carries or renders `readinessGrade`; `createSpec` / `getSpec` persistence, seed/export fixture contracts, probes, and selected-spec prompt carriers no longer persist or transport a readiness grade; the D86-L graph-write-tool-floor sub-claim is covered — `state.test.ts` proves `mutate_graph` + review-set tools stay floor while `propose-graph`/`project-graph` readiness `negotiate`s and only the non-graph-write `review-for-gaps` is withheld, and `dev/__tests__/tier-2-harness.test.ts` proves the same through a real `runBrunchTui` boot at thin vs covered grounding) | D20-L, D45-L, D64-L, D74-L, D86-L | | I32-L | Public RPC structured-exchange driving never requires a client to speak raw Pi RPC: after Brunch method discovery and workspace/spec/session activation, each pending assistant-originated exchange is answered exactly once through `session.submitExchangeResponse`, and the deterministic permutation run produces linear Pi JSONL whose structured exchange projection preserves the same prompt/answer/status/comment artifacts as the equivalent TUI structured-exchange path. | covered for deterministic FE-744 parity under canonical session method names (`session.triggerExchange`, `session.pendingExchange`, `session.submitExchangeResponse`, `session.exchanges`): `rpc.discover` contract tests, pending/respond lifecycle tests, current public-RPC structured-exchange permutations, terminal non-answered status handling, option content/rationale parity, no repeated deterministic prompts, and transcript/exchange parity assertions. | D5-L, D48-L, D49-L; I10-L, I13-L, I21-L, I23-L | | I33-L | `capture_*` analysis entries are transcript evidence only: they persist as Brunch structured-exchange `toolResult` rows, are included by Brunch-semantic transcript renderers, are hidden or collapsed in TUI display, and never mutate graph truth or bypass `CommandExecutor`. | partially covered (minimum capture details schemas parse/export and reject graph payload fields; future runtime capture-analysis schema/rendering tests plus transcript renderer fixtures still need to prove persisted result rendering and TUI hide/collapse behavior; later graph-capture fixtures compare analysis candidates against committed graph mutations) | D17-L, D18-L, D37-L, D47-L, D50-L; I2-L, I11-L, I23-L, I30-L | | I34-L | `mutateGraph` batch validation is all-or-nothing: if any node or edge in the batch is structurally illegal, the entire batch is rejected and no partial state is persisted; the agent receives diagnostics sufficient for bounded self-correction retry. | covered (`command-executor/commit-graph-batch.test.ts` and graph-tool adapter tests cover dry-run/commit diagnostic parity for invalid basis, missing refs/codes, invalid category/stance, self-loop, invalid node kind/detail shape, rollback of nodes/edges/change_log/counters, transaction-local planning before LSN allocation/writes, and structured adapter diagnostics without thrown projected-code errors or fake endpoint refs) | D53-L; I1-L, I11-L | -| I35-L | Graph context reads support multiple detail levels: a cursory/compact full-graph overview for orientation, detailed node-neighborhood context with configurable hop depth for focused work, bounded list slices by kind/readiness band, and related-node traversal by anchor and edge category. The `read_graph` parameter boundary teaches the mode-specific companion contract: `neighborhood` requires non-empty `nodeCode`, `related` requires non-empty `anchorCodes` plus `edgeCategory`, and list modes intentionally treat omitted/empty filters as unfiltered slices while unknown filters produce an empty slice. Context builders in `.pi/extensions/system-prompts/seed/` (pushed seed contexts) and `.pi/extensions/context/` (pull tools) orchestrate which level to inject or advertise based on mode/goal/strategy/lens/readiness. | covered for current POC push and pull paths (`getGraphOverview` + `getNodeNeighborhood` in `queries.ts` with 10 tests; `src/.pi/extensions/system-prompts/seed/{graph,workspace}.test.ts` covers lens-shaped overview rendering and selected-spec workspace/session/posture seed context, and `src/.pi/__tests__/context-tools.test.ts` covers the pulled context tools including bounded node-neighborhood rendering; `src/.pi/__tests__/prompting.test.ts` proves the explicit shell/product prompt path supplies selected-spec-bound graph context to `composeAgentPrompt()`). `src/graph/__tests__/observed-shapes-coverage.test.ts` guards the read mode ledger, and `src/.pi/__tests__/graph-tools.test.ts` covers `read_graph` mode-companion schema enforcement plus loud adapter diagnostics for malformed companion calls. Pulled context tools are part of the live read surface. | D52-L, D53-L, D58-L | +| I35-L | Graph context reads support multiple detail levels: a cursory/compact full-graph overview for orientation, detailed node-neighborhood context with configurable hop depth for focused work, bounded list slices by kind/readiness band, and related-node traversal by anchor and edge category. The `read_graph` parameter boundary teaches the mode-specific companion contract: `neighborhood` requires non-empty `nodeCode`, `related` requires non-empty `anchorCodes` plus `edgeCategory`, and list modes intentionally treat omitted/empty filters as unfiltered slices while unknown filters produce an empty slice. Context builders in `.pi/extensions/agent-runtime/system-prompts/seed/` (pushed seed contexts) and `.pi/extensions/brunch-data/context/` (pull tools) orchestrate which level to inject or advertise based on mode/goal/strategy/lens/readiness. | covered for current POC push and pull paths (`getGraphOverview` + `getNodeNeighborhood` in `queries.ts` with 10 tests; `src/.pi/extensions/agent-runtime/system-prompts/seed/{graph,workspace}.test.ts` covers lens-shaped overview rendering and selected-spec workspace/session/posture seed context, and `src/.pi/__tests__/context-tools.test.ts` covers the pulled context tools including bounded node-neighborhood rendering; `src/.pi/__tests__/prompting.test.ts` proves the explicit shell/product prompt path supplies selected-spec-bound graph context to `composeAgentPrompt()`). `src/graph/__tests__/observed-shapes-coverage.test.ts` guards the read mode ledger, and `src/.pi/__tests__/graph-tools.test.ts` covers `read_graph` mode-companion schema enforcement plus loud adapter diagnostics for malformed companion calls. Pulled context tools are part of the live read surface. | D52-L, D53-L, D58-L | | I36-L | Node `kind` is drawn from a per-plane closed enum structurally validated by the `CommandExecutor`. | covered (CommandExecutor rejects invalid kind-for-plane; tests in `command-executor.test.ts`) | D54-L, D56-L | | I37-L | `detail` is per-kind validated and boundary-advertised from the graph schema owner: `decision` and `term` nodes REQUIRE `detail` with their respective sub-schemas; the claim kinds `requirement`/`criterion`/`invariant` accept an OPTIONAL `form`-discriminated union (`plain`/`gherkin`/`formal`) and `context` accepts an OPTIONAL `form:"given"` payload (D88-L); all other kinds must omit `detail`; the `form` discriminant and any unknown per-form fields are rejected. `kind` drives behavior (band/edge-legality/source-question); `form` is inert payload plus a renderer hook. The agent/tool and dev-RPC mutation schemas expose the same per-kind companion shapes — including the claim/context form union — instead of accepting opaque `Unknown`. | covered (detail-required/prohibited/form-shape tests in `command-executor.test.ts`; boundary schema companion tests in `mutate-graph-edge-schema.test.ts`) | D54-L, D88-L | -| I38-L | Every Brunch prompt-resource manifest injected for an agent turn is generated from projected runtime state and spec/workspace gates: listed resources are Brunch-owned, readable under the active tool policy, legal for the current `(op_mode × strategy × lens)` tuple / capability-readiness / agent allow-list, and off-list resources are not advertised as available. AUTO axes never list readiness-illegal choices; pinned axes point to the pinned resource whenever the tuple is role/mode-legal, even if readiness negotiates. The shared affordance derivation and prompt manifest filtering use the same capability-readiness/AUTO legality source. | covered for current P0 manifest families (`src/.pi/extensions/system-prompts/__tests__/compose.test.ts` covers default header/context/manifest output, two-AUTO-axis manifest output with no ``, AUTO capability-readiness filtering, pinned singleton resources, readiness-thin pin retention, role/mode-illegal pin rejection, and readable `src/.pi/` locations; `src/.pi/__tests__/prompting.test.ts` covers the explicit shell `before_agent_start` product path appending `composeAgentPrompt()` output from transcript-projected runtime state and no legacy composer import/resource discovery; `src/.pi/extensions/runtime/state.test.ts` plus `src/projections/session/affordances.test.ts` cover shared legality/default behavior, including AUTO excluding `freestyle` and gated methods staying withheld during negotiation). FE-825 added a dev-gated introspection loop (`src/.pi/extensions/introspection/` + `src/dev/introspection-launcher.ts`) that records final provider payloads and pairs them with subjective model answers under `.fixtures/scratch/introspection//`; `brunch_introspect_query` now makes the captured provider payload/tool schemas/base options queryable in-chat for the same diagnostic plane. Probe fitness may still track whether the agent reads selected resources before use. | D39-L, D40-L, D58-L, D59-L, D66-L, D69-L, D85-L | +| I38-L | Every Brunch prompt-resource manifest injected for an agent turn is generated from projected runtime state and spec/workspace gates: listed resources are Brunch-owned, readable under the active tool policy, legal for the current `(op_mode × strategy × lens)` tuple / capability-readiness / agent allow-list, and off-list resources are not advertised as available. AUTO axes never list readiness-illegal choices; pinned axes point to the pinned resource whenever the tuple is role/mode-legal, even if readiness negotiates. The shared affordance derivation and prompt manifest filtering use the same capability-readiness/AUTO legality source. | covered for current P0 manifest families (`src/.pi/extensions/agent-runtime/system-prompts/__tests__/compose.test.ts` covers default header/context/manifest output, two-AUTO-axis manifest output with no ``, AUTO capability-readiness filtering, pinned singleton resources, readiness-thin pin retention, role/mode-illegal pin rejection, and readable `src/.pi/` locations; `src/.pi/__tests__/prompting.test.ts` covers the explicit shell `before_agent_start` product path appending `composeAgentPrompt()` output from transcript-projected runtime state and no legacy composer import/resource discovery; `src/.pi/extensions/agent-runtime/runtime/state.test.ts` plus `src/projections/session/affordances.test.ts` cover shared legality/default behavior, including AUTO excluding `freestyle` and gated methods staying withheld during negotiation). FE-825 added a dev-gated introspection loop (`src/.pi/extensions/dev-mode/introspection/` + `src/dev/introspection-launcher.ts`) that records final provider payloads and pairs them with subjective model answers under `.fixtures/scratch/introspection//`; `brunch_introspect_query` now makes the captured provider payload/tool schemas/base options queryable in-chat for the same diagnostic plane. Probe fitness may still track whether the agent reads selected resources before use. | D39-L, D40-L, D58-L, D59-L, D66-L, D69-L, D85-L | | I39-L | Every graph node in a spec has exactly one stable projected human reference code derived from `kind` + `kind_ordinal`; `(spec_id, plane, kind, kind_ordinal)` is unique; ordinals are monotonic per `(spec_id, plane, kind)` and are not reused after deletion or supersession. | partially covered (`graph-tool-resilience` added `nodes.kind_ordinal`, `node_kind_counters`, DB uniqueness, CommandExecutor allocation for single-node/batch writes, rollback protection, `GraphNode.kindOrdinal` row mapping, globally unique 1–3 letter labels with readiness-band metadata, projected-code parsing, selected-spec adapter resolution before `CommandExecutor`, code-only `mutate_graph` / `read_graph` schemas, and code-primary prompt/tool rendering; `queries.test.ts` now pins the merged `NODE_KIND_METADATA` labels + D64-L readiness bands so schema/code drift fails loudly; remaining slice still needs deletion/supersession no-reuse coverage) | D54-L, D62-L; I1-L, I11-L | | I40-L | Accepted graph nodes and edges use only `basis ∈ explicit | implicit`; review-set approval and direct user statements produce `explicit`, `propose-graph` concept-level materialization produces `implicit`, and the mutation path is recoverable from `change_log` rather than from a persisted basis enum value such as `accepted_review_set`. | covered (`graph-tool-resilience` replaced the persisted basis enum with `explicit | implicit`, made `mutateGraph` apply one batch create-basis to all created nodes/edges, made single-node `createNode` reject retired basis values before LSN/counter/node/change-log allocation, made `propose-graph` adapter commits implicit, made review-set translation explicit, rejected retired `accepted_review_set`, and records `change_log.operation` independently; `capture-response-to-graph` proves direct structured text responses commit explicit-basis graph nodes through `CommandExecutor`; `.fixtures/runs/project-graph-review-cycle/2026-06-06-project-graph-review-cycle/` proves full review-cycle approval creates explicit-basis graph truth) | D26-L, D27-L, D53-L, D63-L | | I41-L | Same-spec `supersession` edges form an acyclic directed graph; every edge-creation path validates proposed supersession edges together with existing supersession edges before committing. | covered (`command-executor/commit-graph-batch.test.ts` rejects existing-cycle closure, intra-batch cycles, and mixed existing+batch cycles through the shared dry-run/commit planner before batch writes; rejected cycles roll back or avoid batch nodes/edges/change_log; acyclic supersession commits remain covered by query/CommandExecutor success paths) | D51-L, D53-L; I34-L | -| I42-L | Dev-only substrate never affects product/prod behavior: `src/dev/**` is build-excluded from `dist`; the introspection extension registers and advertises its query tools only when `BRUNCH_DEV` opts it in (default product sessions never register or advertise the tap, `/introspect`, `brunch_session_query`, `brunch_introspect_query`, or any `before_provider_request` observer); durable dev-loop artifacts land only under gitignored `.fixtures/scratch/`, never tracked `runs/` or the operating cwd; the only workspace-local dev cache is ephemeral `.brunch/debug/` output derived from the same passive capture / explicit Brunch-owned text `tool_result` events in `BRUNCH_DEV` real TUI launches; and Pi startup update suppression / any offline-default lift is save/restore-scoped through TUI launch, never a leaked global `process.env` mutation. | covered for the current DX substrate (`src/.pi/__tests__/introspection.test.ts` proves default-off registration + last-position ordering when enabled, active-tool advertisement of `brunch_session_query` / `brunch_introspect_query`, debug-cache mirroring from passive final-prompt capture, and Brunch-owned tool-result filtering/append formatting; `src/.pi/extensions/runtime/state.test.ts` proves the injected dev tool set is unioned only before blocked-tool subtraction and registered-tool intersection; `src/.pi/extensions/session-query/index.test.ts` and `src/.pi/extensions/introspect-query/index.test.ts` cover read-only find/project/truncation behavior; `src/app/brunch-tui.test.ts` proves the real TUI launch path threads `BRUNCH_DEV` into introspection registration with launch-cwd debug-cache options, keeps the registrar last, asserts `tsconfig.build.json` excludes `src/dev`, and proves `PI_OFFLINE` startup update suppression plus prior `PI_OFFLINE` / `PI_SKIP_VERSION_CHECK` values are save/restore-scoped through `finally`; `src/dev/introspection-launcher.test.ts` proves scratch artifact routing is repo-rooted and independent of workspace cwd; `.fixtures/README.md` + `.gitignore` document/guard scratch). | D39-L, D40-L, D68-L, D69-L, D70-L, D71-L | +| I42-L | Dev-only substrate never affects product/prod behavior: `src/dev/**` is build-excluded from `dist`; the introspection extension registers and advertises its query tools only when `BRUNCH_DEV` opts it in (default product sessions never register or advertise the tap, `/introspect`, `brunch_session_query`, `brunch_introspect_query`, or any `before_provider_request` observer); durable dev-loop artifacts land only under gitignored `.fixtures/scratch/`, never tracked `runs/` or the operating cwd; the only workspace-local dev cache is ephemeral `.brunch/debug/` output derived from the same passive capture / explicit Brunch-owned text `tool_result` events in `BRUNCH_DEV` real TUI launches; and Pi startup update suppression / any offline-default lift is save/restore-scoped through TUI launch, never a leaked global `process.env` mutation. | covered for the current DX substrate (`src/.pi/__tests__/introspection.test.ts` proves default-off registration + last-position ordering when enabled, active-tool advertisement of `brunch_session_query` / `brunch_introspect_query`, debug-cache mirroring from passive final-prompt capture, and Brunch-owned tool-result filtering/append formatting; `src/.pi/extensions/agent-runtime/runtime/state.test.ts` proves the injected dev tool set is unioned only before blocked-tool subtraction and registered-tool intersection; `src/.pi/extensions/dev-mode/session-query/index.test.ts` and `src/.pi/extensions/dev-mode/introspect-query/index.test.ts` cover read-only find/project/truncation behavior; `src/app/brunch-tui.test.ts` proves the real TUI launch path threads `BRUNCH_DEV` into introspection registration with launch-cwd debug-cache options, keeps the registrar last, asserts `tsconfig.build.json` excludes `src/dev`, and proves `PI_OFFLINE` startup update suppression plus prior `PI_OFFLINE` / `PI_SKIP_VERSION_CHECK` values are save/restore-scoped through `finally`; `src/dev/introspection-launcher.test.ts` proves scratch artifact routing is repo-rooted and independent of workspace cwd; `.fixtures/README.md` + `.gitignore` document/guard scratch). | D39-L, D40-L, D68-L, D69-L, D70-L, D71-L | | I43-L | The web client's accent presentation map is exhaustive over `NodePlane` (intent/oracle/design/plan); every plane renders with a defined accent, and node reference-code labels remain canonical via `NODE_KIND_METADATA` + `kindOrdinal` (no fallthrough default that silently swallows an unmapped plane). | met (compile-time `satisfies Record` exhaustiveness check on `PLANE_ACCENT` in `src/web/components/node-card.tsx`; breaks the build when a new `NodePlane` is added without an accent) | D72-L; I36-L | | I44-L | Domain enum taxonomy lives in the drizzle-free leaf `src/graph/schema/kinds.ts` (zero imports), `db/schema.ts` owns no enum `const` array (it imports them from the leaf), and the `web/` build target transitively contains no Drizzle/persistence code. The only sanctioned `db/`→`graph/` import is from `db/schema.ts` to `graph/schema/kinds.ts`. | partially covered (`src/graph/architecture.test.ts` guards leaf purity, the db→graph kinds-only edge, and absence of enum const arrays in `db/schema.ts`; oxlint `no-restricted-imports` additionally forbids any non-`graph/` module — including `web/` — from importing `db/` directly. There is currently no post-`build:web` bundle assertion, so the transitive "dist-web contains no `drizzle`/`sqliteTable`" claim is a structural expectation, not test-verified; `src/db/README.md` and `src/graph/README.md` record the taxonomy leaf topology) | D52-L, D73-L; I26-L | | I45-L | A session's assistant-visible watermark advances only when a continuity entry naming a strictly higher spec-local LSN is inserted: a boot/context seed or whole-spec overview snapshot, a `worldUpdate` for any write not already assistant-visible through another carrier (naming only items with LSN strictly greater than the pre-update watermark, I4-L), or the session's own graph-mutation `toolResult`. `worldUpdate` covers foreign writes **and** same-session writes that did not ride an own-mutation `toolResult` (e.g. submit-time / freestyle capture); such a same-session capture advances `current_lsn` and is surfaced by the next `worldUpdate`, never silently swallowed. A freshly seeded session whose seed named the current snapshot LSN does not immediately synthesize a redundant `worldUpdate`. Narrow `getNodes`/`queryNodes` reads do not advance the global watermark (they update per-entity read ledgers only). When `current_lsn == watermark` no `worldUpdate` is synthesized, and the session's own already-visible mutations never produce a `worldUpdate`. The watermark is its own projection over the carrier set (distinct from `runtimeState.world.latestLsn`), projected from transcript continuity entries (D43-L), never a stored field. | covered (2026-06-11: all I45 Tier-2 scaffold rows run live through real `runBrunchTui` boot in `src/dev/tier-2-harness.test.ts`; the live `before_provider_request` guard delegates to `guardBeforeProviderRequest` retry semantics) | D43-L, D76-L, D77-L; I1-L, I4-L | @@ -403,7 +403,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c ### Prompt/runtime profile architecture -- Brunch prompt composition is a **runtime-header + gated prompt-resource manifest** composed per agent by `composeAgentPrompt(...)` in `.pi/extensions/system-prompts/compose.ts` (D58-L). The direct injection is intentionally small: agent control summary, two-axis runtime state (`strategy` + `lens`), a legal `` resource manifest with per-skill `kind`, `name`, `description`, and `location`, router rules for pinned/AUTO axes, and compact context handles/rendered context blocks. Detailed strategy/lens/method bodies are Brunch-owned markdown resources the agent loads with `read` when needed, while the elicitor objective postures are inline in `src/.pi/agents/elicitor/SYSTEM.md`. The old `src/.pi/context/` prompt-pack layout is retired; the old top-level `src/agents/` host-independent appearance is retired because these agents live only inside the Pi harness. +- Brunch prompt composition is a **runtime-header + gated prompt-resource manifest** composed per agent by `composeAgentPrompt(...)` in `.pi/extensions/agent-runtime/system-prompts/compose.ts` (D58-L). The direct injection is intentionally small: agent control summary, two-axis runtime state (`strategy` + `lens`), a legal `` resource manifest with per-skill `kind`, `name`, `description`, and `location`, router rules for pinned/AUTO axes, and compact context handles/rendered context blocks. Detailed strategy/lens/method bodies are Brunch-owned markdown resources the agent loads with `read` when needed, while the elicitor objective postures are inline in `src/.pi/agents/elicitor/SYSTEM.md`. The old `src/.pi/context/` prompt-pack layout is retired; the old top-level `src/agents/` host-independent appearance is retired because these agents live only inside the Pi harness. - Concrete `.pi/{agents,skills}` topology (D52-L). The markdown/code boundary falls on the control-plane/behavior split: enforcement and projection are TypeScript under `.pi/extensions/` (`system-prompts/`, `runtime/`); semantic prompting material is markdown under `.pi/agents/{agent-name}/SYSTEM.md` for live agent bodies and `.pi/skills/`. ```text @@ -429,8 +429,8 @@ src/.pi/ acquisition/readback methods ``` -- Manifest availability is code-owned, not filesystem-discovered: `.pi/extensions/runtime/state.ts` binds each legal axis value to an explicit `src/.pi/skills///SKILL.md` path and each live agent role to its `SYSTEM.md` location. It loads prompt-resource `name` and `description` from `SKILL.md` frontmatter through pi's loader with `includeDefaults: false` and an explicit `skillPaths` list. `composeAgentPrompt()` emits legal resource bindings; the prompt extension reads the selected agent body explicitly and passes it into the pure composer. This keeps the legal set sealed while making the file body/frontmatter the description source of truth. -- The D60-L agent-context orchestration layer (TypeScript) lives in `.pi/extensions/system-prompts/seed/` (pushed contexts) and `.pi/extensions/context/` (read tools), surfaced as the header's compact pushed context or via the read tools; reusable text renderers live in `renderers/`, and contexts are not part of the `read`-on-demand resource manifest and carry no `` family. +- Manifest availability is code-owned, not filesystem-discovered: `.pi/extensions/agent-runtime/runtime/state.ts` binds each legal axis value to an explicit `src/.pi/skills///SKILL.md` path and each live agent role to its `SYSTEM.md` location. It loads prompt-resource `name` and `description` from `SKILL.md` frontmatter through pi's loader with `includeDefaults: false` and an explicit `skillPaths` list. `composeAgentPrompt()` emits legal resource bindings; the prompt extension reads the selected agent body explicitly and passes it into the pure composer. This keeps the legal set sealed while making the file body/frontmatter the description source of truth. +- The D60-L agent-context orchestration layer (TypeScript) lives in `.pi/extensions/agent-runtime/system-prompts/seed/` (pushed contexts) and `.pi/extensions/brunch-data/context/` (read tools), surfaced as the header's compact pushed context or via the read tools; reusable text renderers live in `renderers/`, and contexts are not part of the `read`-on-demand resource manifest and carry no `` family. - Workspace **posture** is workspace-scoped product state persisted in `.brunch/workspace.json`, not spec state, session state, or graph truth. D57-L keeps it off the spec row and graph; D58-L composition injects known posture values into the runtime header as an axis of agent influence, and the `capture-posture` goal (D59-L) can confirm or refine those values conversationally. - Readiness is judged just-in-time per requested capability, not as a user-facing workflow stepper, a stored grade, a session-local phase, or a graph-node-kind whitelist. There is no `readiness_grade` on the spec row (D45-L); capability-readiness (D74-L) is evaluated over the relevant `elicitation_gaps`, and D64-L readiness bands describe non-exclusive evidence groupings feeding the readiness-estimate rollup, goal selection, and context filtering. The soft readiness estimate may surface in UI but gates nothing. A future structural milestone gate for export/plan/execute op-modes is deferred until such an op-mode exists; before readiness grows beyond the current tracer, Brunch still needs a real evaluator path for `manual` gaps and a more differentiated per-capability map than the shared grounding floor (A27-L). - Prompt resources and Pi skills are both progressive-disclosure mechanisms, but they are not authority. Brunch code owns runtime-state projection, legal tuple filtering, capability-readiness/allow-list gating, tool activation, and tool-call blocking. Explicit user/system pins remain visible when readiness negotiates; negotiation changes AUTO choices, method/tool availability, and response posture rather than authority. Pi-native skills may be used for startup-scoped capabilities; runtime-state-specific objective/method availability is advertised through Brunch's per-turn manifest so ambient user/project resources cannot leak into product behavior. @@ -503,7 +503,7 @@ src/.pi/ | **AUTO** | The unpinned state of a runtime prompt-resource axis (`strategy` / `lens`): composition advertises the legal choices in the current prompt-resource manifest and instructs the agent to self-select from that manifest only, reading the selected resource when detail matters (D58-L). | | **Brunch Pi Profile** | The sealed programmatic wrapper around embedded Pi: settings policy, resource-loader policy, extension factories, keybinding/command policy, tool policy, and prompt policy. It allows Brunch-owned resources while suppressing ambient `.pi/` behavior. | | **Prompt resource** | A Brunch-owned markdown file under `src/.pi/` containing detailed strategy, lens, method, or agent-definition guidance. Prompt resources are loaded by the agent with `read` when needed; they are product control-plane assets, not ambient Pi prompt templates. | -| **Prompt-resource manifest** | The small per-turn D58-L `` block injected into the system prompt, listing only runtime-legal Brunch resources with `kind`, `name`, `description`, and `location`. The legal set and locations are code-owned in `.pi/extensions/runtime/state.ts` (not filesystem-discovered); `name` and `description` are file-owned `SKILL.md` frontmatter read via pi's loader over the explicit path list. The `system-prompts/seed/` and `.pi/extensions/context/` context renderers are not manifest resources. It mirrors Pi's skill-list element structure but is filtered by Brunch runtime state, capability-readiness, and allow-lists. | +| **Prompt-resource manifest** | The small per-turn D58-L `` block injected into the system prompt, listing only runtime-legal Brunch resources with `kind`, `name`, `description`, and `location`. The legal set and locations are code-owned in `.pi/extensions/agent-runtime/runtime/state.ts` (not filesystem-discovered); `name` and `description` are file-owned `SKILL.md` frontmatter read via pi's loader over the explicit path list. The `system-prompts/seed/` and `.pi/extensions/brunch-data/context/` context renderers are not manifest resources. It mirrors Pi's skill-list element structure but is filtered by Brunch runtime state, capability-readiness, and allow-lists. | | **Method** | A tool-usage or workflow competence advertised as a Brunch prompt resource (`.pi/skills/methods//SKILL.md`): run structured exchanges, capture (the D80/D81/D82 home; full conduct in FE-861), generate proposals/projections, read context, mutate the graph, review for gaps. Method resources explain when to use a tool family and how to sequence it with other tools; executable tool definitions should stay focused on schemas, authority, and runtime behavior. A method may also be backed by a Pi-native skill, but actual tool authority remains code-owned through `op_mode` policy and active-tool gating. `capability` is retired as a synonym — use `method` and `method` in ``. | | **Agent context** | The content the agent reasons over — `cwd`, `graph`, or `node` (D60-L): pulled (typed, read-only) from `graph/`/`session/`, optionally projected when a reusable DTO helps, rendered to LLM-string or JSON, surfaced pushed (compose) or pulled (`read_graph` / `read_workspace_context` / `read_session_context`). Graph context explicitly chooses graph-truth vs active-context reads and may filter by node kind, readiness band, edge category/direction, or absence of an edge category (gap query). Distinct from the **workspace projection** (`workspace.state`), which is product/UI state, not agent content. | | **Context-render house style** | The RENDER-stage convention (D83-L) for LLM-facing agent context: a markdown frame (md-pen) with uniform record sets as TOON (`@toon-format/toon`) and file hierarchy as a fenced ASCII tree (stringify-tree over Brunch's gitignore-aware walk), each top-level block wrapped in an XML-style `
` tag. Format follows reader legibility, not internal shape (prose where structure misleads). Agent context clusters into three scopes mirroring `workspace → spec → session` (D19-L): `` (project / documents / spec-roster, no sessions), `` (spec header / graph / ranked gaps / sessions), `` (runtime posture / mentions / transcript). It is the agent-context *dialect* within `renderers/`; the md-pen substrate is shared with human-facing renders (print/evidence), which do not use the `
` clustering. Distinct from the `workspace.state` product-state projection (D60-L). | diff --git a/src/.pi/__tests__/context-tools.test.ts b/src/.pi/__tests__/context-tools.test.ts index 8524081a4..ef7bae76d 100644 --- a/src/.pi/__tests__/context-tools.test.ts +++ b/src/.pi/__tests__/context-tools.test.ts @@ -9,7 +9,7 @@ import { createBrunchFauxHarness } from '../../dev/index.js'; import { openWorkspaceCommandExecutor } from '../../graph/index.js'; import { seedFixture, type SeedFixture } from '../../graph/seed-fixtures.js'; import { createSessionBindingData, SESSION_BINDING_TYPE } from '../../session/session-binding.js'; -import { registerBrunchContext } from '../extensions/context/index.js'; +import { registerBrunchContext } from '../extensions/brunch-data/context/index.js'; function collectContextTools() { const tools = new Map Promise }>(); diff --git a/src/.pi/__tests__/extension-registry.test.ts b/src/.pi/__tests__/extension-registry.test.ts index ecca6a2e9..3c4d40ea8 100644 --- a/src/.pi/__tests__/extension-registry.test.ts +++ b/src/.pi/__tests__/extension-registry.test.ts @@ -6,6 +6,10 @@ import { describe, expect, it } from 'vitest'; import { createBrunchPiExtensions } from '../../app/pi-extensions.js'; import { registerBrunchAlternatives as alternatives } from '../components/alternatives.js'; +import { BRUNCH_ORCHESTRATOR_STUB_TOOL } from '../extensions/agent-runtime/orchestrator-stub/index.js'; +import { registerBrunchOperationalModePolicy as operationalMode } from '../extensions/agent-runtime/runtime/index.js'; +import { registerBrunchPrompting as prompting } from '../extensions/agent-runtime/system-prompts/index.js'; +import { registerBrunchContext as context } from '../extensions/brunch-data/context/index.js'; import chrome from '../extensions/chrome/index.js'; import { BRUNCH_LENS_COMMAND, @@ -15,7 +19,6 @@ import { registerBrunchCommands as commands, } from '../extensions/commands/index.js'; import { registerBrunchBranchPolicyHandlers as commandPolicy } from '../extensions/commands/policy.js'; -import { registerBrunchContext as context } from '../extensions/context/index.js'; import { PRESENT_CANDIDATES_TOOL, PRESENT_QUESTION_TOOL, @@ -24,10 +27,7 @@ import { registerStructuredExchange as structuredExchange, } from '../extensions/exchanges/index.js'; import { registerBrunchMentionAutocomplete as mentionAutocomplete } from '../extensions/mentions/index.js'; -import { BRUNCH_ORCHESTRATOR_STUB_TOOL } from '../extensions/orchestrator-stub/index.js'; -import { registerBrunchOperationalModePolicy as operationalMode } from '../extensions/runtime/index.js'; -import { registerBrunchSessionBoundary as sessionLifecycle } from '../extensions/session/lifecycle.js'; -import { registerBrunchPrompting as prompting } from '../extensions/system-prompts/index.js'; +import { registerBrunchSessionBoundary as sessionLifecycle } from '../extensions/session-hooks/session/lifecycle.js'; const extensionDefaults = { 'components/alternatives.ts': alternatives, @@ -58,16 +58,20 @@ describe('Brunch explicit Pi extension registry', () => { expect(settings.extensions).not.toContain('!extensions/**'); expect(settings.extensions).toEqual( expect.arrayContaining([ + '-extensions/agent-runtime/index.ts', + '-extensions/agent-runtime/runtime/index.ts', + '-extensions/agent-runtime/system-prompts/index.ts', + '-extensions/brunch-data/index.ts', + '-extensions/brunch-data/elicitation/index.ts', + '-extensions/brunch-data/graph/index.ts', + '-extensions/brunch-data/reconciliation/index.ts', '-extensions/commands/index.ts', '-extensions/compaction/index.ts', - '-extensions/elicitation/index.ts', - '-extensions/graph/index.ts', - '-extensions/introspect-query/index.ts', - '-extensions/reconciliation/index.ts', - '-extensions/runtime/index.ts', + '-extensions/dev-mode/index.ts', + '-extensions/dev-mode/introspect-query/index.ts', + '-extensions/session-hooks/index.ts', '-extensions/subagents/index.ts', - '-extensions/system-prompts/index.ts', - '-extensions/web/index.ts', + '-extensions/web-tools/index.ts', '-extensions/workspace/index.ts', ]), ); diff --git a/src/.pi/__tests__/graph-tools.test.ts b/src/.pi/__tests__/graph-tools.test.ts index 1d8d8a391..032292666 100644 --- a/src/.pi/__tests__/graph-tools.test.ts +++ b/src/.pi/__tests__/graph-tools.test.ts @@ -15,9 +15,12 @@ import { } from '../../graph/queries.js'; import { READINESS_BANDS } from '../../graph/schema/kinds.js'; import { formatGraphOverview } from '../../renderers/graph/graph-slice.js'; -import { translateMutateGraph, formatMutateGraphResult } from '../extensions/graph/command-adapter.js'; -import { registerBrunchGraph, type GraphReaders } from '../extensions/graph/index.js'; -import { ReadGraphParams } from '../extensions/graph/tool-schemas.js'; +import { + translateMutateGraph, + formatMutateGraphResult, +} from '../extensions/brunch-data/graph/command-adapter.js'; +import { registerBrunchGraph, type GraphReaders } from '../extensions/brunch-data/graph/index.js'; +import { ReadGraphParams } from '../extensions/brunch-data/graph/tool-schemas.js'; let nextSpecSlug = 0; diff --git a/src/.pi/__tests__/introspection.test.ts b/src/.pi/__tests__/introspection.test.ts index 14793dae3..e8f4bf815 100644 --- a/src/.pi/__tests__/introspection.test.ts +++ b/src/.pi/__tests__/introspection.test.ts @@ -5,7 +5,7 @@ import { join } from 'node:path'; import { describe, expect, it } from 'vitest'; import { createBrunchPiExtensions } from '../../app/pi-extensions.js'; -import { BRUNCH_INTROSPECT_QUERY_TOOL } from '../extensions/introspect-query/index.js'; +import { BRUNCH_INTROSPECT_QUERY_TOOL } from '../extensions/dev-mode/introspect-query/index.js'; import { appendEntryContentToDebugCache, appendOriginationRecordToDebugCache, @@ -13,8 +13,8 @@ import { createInMemoryBrunchIntrospectionStore, mirrorSystemPromptToDebugCache, registerBrunchIntrospection, -} from '../extensions/introspection/index.js'; -import { BRUNCH_SESSION_QUERY_TOOL } from '../extensions/session-query/index.js'; +} from '../extensions/dev-mode/introspection/index.js'; +import { BRUNCH_SESSION_QUERY_TOOL } from '../extensions/dev-mode/session-query/index.js'; interface FakeCommandContext { readonly ui: { notify(message: string, type?: 'info' | 'warning' | 'error'): void }; diff --git a/src/.pi/__tests__/operational-mode.test.ts b/src/.pi/__tests__/operational-mode.test.ts index 5f35d1a80..3c0ec104d 100644 --- a/src/.pi/__tests__/operational-mode.test.ts +++ b/src/.pi/__tests__/operational-mode.test.ts @@ -16,7 +16,7 @@ import { registerBrunchOperationalModePolicy, type BrunchAgentState, type BrunchAgentStateEntryData, -} from '../extensions/runtime/index.js'; +} from '../extensions/agent-runtime/runtime/index.js'; function runtimeEntry(state: BrunchAgentState, data: Record = {}) { return { diff --git a/src/.pi/__tests__/prompting.test.ts b/src/.pi/__tests__/prompting.test.ts index 252f232c8..fb21dea57 100644 --- a/src/.pi/__tests__/prompting.test.ts +++ b/src/.pi/__tests__/prompting.test.ts @@ -8,8 +8,6 @@ import { createBrunchPiExtensions } from '../../app/pi-extensions.js'; import { groundingFloorGaps } from '../../graph/schema/elicitation-gap-fixtures.js'; import type { ElicitationGap } from '../../graph/schema/elicitation-gaps.js'; import type { WorkspacePostureState } from '../../session/workspace-session-coordinator.js'; -import { BRUNCH_INTROSPECT_QUERY_TOOL } from '../extensions/introspect-query/index.js'; -import { createInMemoryBrunchIntrospectionStore } from '../extensions/introspection/index.js'; import { BRUNCH_AGENT_RUNTIME_STATE_CUSTOM_TYPE, DEFAULT_BRUNCH_AGENT_STATE, @@ -18,10 +16,12 @@ import { type BrunchAgentState, type BrunchAgentStateEntryData, registerBrunchOperationalModePolicy, -} from '../extensions/runtime/index.js'; -import { BRUNCH_SESSION_QUERY_TOOL } from '../extensions/session-query/index.js'; -import { composeAgentPrompt } from '../extensions/system-prompts/compose.js'; -import { registerBrunchPrompting } from '../extensions/system-prompts/index.js'; +} from '../extensions/agent-runtime/runtime/index.js'; +import { composeAgentPrompt } from '../extensions/agent-runtime/system-prompts/compose.js'; +import { registerBrunchPrompting } from '../extensions/agent-runtime/system-prompts/index.js'; +import { BRUNCH_INTROSPECT_QUERY_TOOL } from '../extensions/dev-mode/introspect-query/index.js'; +import { createInMemoryBrunchIntrospectionStore } from '../extensions/dev-mode/introspection/index.js'; +import { BRUNCH_SESSION_QUERY_TOOL } from '../extensions/dev-mode/session-query/index.js'; function runtimeEntry(state: BrunchAgentState) { return { @@ -662,7 +662,7 @@ describe('Brunch prompt-pack topology', () => { it('does not expose prompt manifests through Pi resource discovery or legacy context imports', async () => { const [promptingSource, shellSource] = await Promise.all([ - readFile(join(projectRoot(), 'src/.pi/extensions/system-prompts/index.ts'), 'utf8'), + readFile(join(projectRoot(), 'src/.pi/extensions/agent-runtime/system-prompts/index.ts'), 'utf8'), readFile(join(projectRoot(), 'src/app/pi-extensions.ts'), 'utf8'), ]); diff --git a/src/.pi/agents/README.md b/src/.pi/agents/README.md index 0b7bede7b..7db564a5a 100644 --- a/src/.pi/agents/README.md +++ b/src/.pi/agents/README.md @@ -55,13 +55,13 @@ The prompt-assembly machinery that *uses* these definitions now lives with the extension that consumes it: - **Foreground prompt composition + pushed seed contexts** — - `.pi/extensions/system-prompts/` (`compose.ts` emits the runtime header + gated + `.pi/extensions/agent-runtime/system-prompts/` (`compose.ts` emits the runtime header + gated manifest; `seed/workspace.ts` and `seed/graph.ts` render the pushed context blocks). - **Background prompt assembly and injected-world child-session wiring** — `.pi/extensions/subagents/`. - **Prompt-resource manifest selection + tool/method legality** — - `.pi/extensions/runtime/` (`state.ts`), fed by the foreground roster in + `.pi/extensions/agent-runtime/runtime/` (`state.ts`), fed by the foreground roster in `src/projections/session/runtime-policy.ts`. - **Strategy/lens/method prompt-resource skills** — `.pi/skills/`. - **Reusable lossy text/markdown rendering** — `renderers/`. diff --git a/src/.pi/extensions/README.md b/src/.pi/extensions/README.md index b39b315a9..d6881786f 100644 --- a/src/.pi/extensions/README.md +++ b/src/.pi/extensions/README.md @@ -8,9 +8,9 @@ Pi-facing registration and adaptation only: lifecycle hooks, agent tool definiti ## Does NOT own -- Agent role prompt definitions and skill resource bodies (markdown) — `.pi/agents/` and `.pi/skills/`. (Prompt composition and the prompt-resource manifest/legality policy are owned here, by `system-prompts/` and `runtime/`.) -- Graph truth, graph mutation policy, or graph readers — `graph/`. -- Pi JSONL/session semantics, runtime-state projection, workspace coordination, or transcript exchange projection — `session/` until the runtime-state follow-up split lands. +- Agent role prompt definitions and skill resource bodies (markdown) — `.pi/agents/` and `.pi/skills/`. Prompt composition and prompt-resource legality live in `agent-runtime/`. +- Graph truth, graph mutation policy, or graph readers — top-level `graph/`. +- Pi JSONL/session semantics, runtime-state projection, workspace coordination, or transcript exchange projection — top-level `session/`, `projections/`, and related domain seams. - Reusable DTO projection or reusable markdown/text rendering — top-level `projections/` and `renderers/`. - Product transport handlers — `rpc/`, `app/`, and `web/`. @@ -19,27 +19,32 @@ Pi-facing registration and adaptation only: lifecycle hooks, agent tool definiti ```text extensions/ ├── README.md -├── AUDIT.md temporary audit note; do not treat as topology source -├── chrome/ TUI header/title/footer/sidecar-widget chrome projection -├── commands/ /brunch:* commands, shortcut, branch/tree policy -├── compaction/ auto-compaction anchor contract and future hook -├── context/ snapshot/context Pi tools -├── elicitation/ read_elicitation_gaps/update_elicitation_gaps Pi tools over the gap register -├── exchanges/ structured-exchange present_* / request_* Pi tools -├── graph/ mutate_graph/read_graph Pi tools + selected-spec graph read seam -├── reconciliation/ read_reconciliation_needs/update_reconciliation_needs Pi tools over the recon-need register -├── introspection/ dev-gated read-only provider-payload tap + /introspect command -├── introspect-query/ dev-gated read-only brunch_introspect_query tool over captured payloads -├── orchestrator-stub/ code-owned execute-mode standup tool registered on the product path -├── session-query/ dev-gated read-only brunch_session_query tool over current branch -├── shared/ projection/truncation helpers + Zod→Pi schema adapter for dev query tools -├── mentions/ #graph mention prompt hint + autocomplete provider -├── runtime/ active-tool policy + tool/user_bash guards; prompt-resource selection + method/tool legality (state.ts) -├── session/ session lifecycle hooks -├── system-prompts/ before_agent_start prompt append; prompt composition (compose.ts), prompt-skill manifest render/loader (prompt-skills.ts), pushed seed contexts (seed/) -├── web/ web_fetch/web_search read tools for referenced-document acquisition -├── workspace/ spec/session picker command adapter -└── subagents/ D44-L/D91-L `subagent` tool — sealed SDK child sessions, assembled background prompts, injected parent-world reads (default-off, dev-gated opt-in) +├── agent-runtime/ foreground prompt composition, active-tool policy, prompt-resource legality, execute-mode stub +│ ├── runtime/ +│ ├── system-prompts/ +│ └── orchestrator-stub/ +├── brunch-data/ Pi tools over selected Brunch graph/spec/workspace/session data +│ ├── graph/ mutate_graph/read_graph tools + selected-spec graph read seam +│ ├── context/ workspace/spec/session context tools +│ ├── elicitation/ read/update elicitation-gap register tools +│ └── reconciliation/ read/update reconciliation-need register tools +├── session-hooks/ session lifecycle and boundary refresh hooks +│ └── session/ +├── dev-mode/ dev-gated observability/query tools +│ ├── introspection/ passive provider-payload tap + /introspect command +│ ├── introspect-query/ brunch_introspect_query over captured payloads +│ └── session-query/ brunch_session_query over the current branch +├── web-tools/ web_fetch/web_search read tools for referenced-document acquisition +│ └── web/ +├── subagents/ D44-L/D91-L sealed SDK child sessions and `subagent` tool +├── chrome/ TUI header/title/footer/sidecar-widget chrome projection +├── commands/ /brunch:* commands, shortcut, branch/tree policy +├── compaction/ auto-compaction anchor contract and future hook +├── exchanges/ structured-exchange present_* / request_* Pi tools +├── mentions/ #graph mention prompt hint + autocomplete provider +├── shared/ projection/truncation helpers + Zod→Pi schema adapter for dev query tools +├── workspace/ spec/session picker command adapter +└── tui-lab/ local TUI experiment registrar ``` ## Boundary rules @@ -58,35 +63,6 @@ rules: `chrome/` is the only product extension that should install Brunch's persistent TUI shell chrome. It receives launch facts from `src/app/brunch-tui.ts` through `BrunchChromeState`; it does not read web host, workspace, or activation state itself. -```pseudo tree -launch facts -> BrunchChromeState -├── cwd/spec/session -> footer + terminal title -├── webSidecarUrl? -> header + footer `web-ui:` line -└── startupHeader? [continue|openSession|newSpec|newSession] - -> ctx.ui.setHeader(...) - -> .pi/components/chrome-header.ts -``` - -```pseudo chain -runBrunchTui - -> chooseSpecSessionActivationDecision - -> activateWorkspace - -> start web sidecar - -> decide browser auto-open [BRUNCH_DEV defaults off, explicit option wins] - -> launchPiInteractive(context) - -> createBrunchPiExtensions(chromeStateForWorkspace(...)) - -> registerBrunchChrome - -> session_start - -> renderBrunchChrome(ctx.ui, chrome) -``` - -Chrome-specific rules: - -- Keep raw `setHeader`, `setFooter`, and `setTitle` calls inside the chrome wrapper unless a later SPEC decision names another owner. -- The web sidecar URL is chrome state rendered as a `web-ui:` line in the startup header and footer, not a `setStatus` contribution, upper widget, or transport concern for `.pi/extensions/`. -- The startup header is TUI-only, non-transcript chrome shown on Brunch-activated TUI launches (`continue`, `openSession`, `newSpec`, or `newSession`) so the product shell does not fall back to Pi's quiet empty header. -- `chrome/` may delegate reusable component rendering to `.pi/components/`, but `.pi/components/` must not register Pi hooks. - ## Migration notes `exchanges/schemas/` is the intentional current exception to "adapter-only": it owns the Zod-authored structured-exchange details schema per D37-L/D41-L until a separate schema-ownership slice moves or names that seam. Zod-to-Pi `TSchema` conversion is confined to two per-plane adapters: `exchanges/pi-schema.ts` (structured-exchange) and `shared/pi-tool-schema.ts` (dev-gated query tools). Both export JSON Schema draft 2020-12 (`z.toJSONSchema`), which strict provider validators require. diff --git a/src/.pi/extensions/agent-runtime/index.ts b/src/.pi/extensions/agent-runtime/index.ts new file mode 100644 index 000000000..6ce10420c --- /dev/null +++ b/src/.pi/extensions/agent-runtime/index.ts @@ -0,0 +1,3 @@ +export * from './orchestrator-stub/index.js'; +export * from './runtime/index.js'; +export * from './system-prompts/index.js'; diff --git a/src/.pi/extensions/orchestrator-stub/index.ts b/src/.pi/extensions/agent-runtime/orchestrator-stub/index.ts similarity index 87% rename from src/.pi/extensions/orchestrator-stub/index.ts rename to src/.pi/extensions/agent-runtime/orchestrator-stub/index.ts index 3bd524cfe..6818125ad 100644 --- a/src/.pi/extensions/orchestrator-stub/index.ts +++ b/src/.pi/extensions/agent-runtime/orchestrator-stub/index.ts @@ -1,9 +1,9 @@ import type { ExtensionAPI, ToolDefinition } from '@earendil-works/pi-coding-agent'; import { Type, type Static } from 'typebox'; -import { BRUNCH_ORCHESTRATOR_STUB_TOOL } from '../../../session/schema/tool-names.js'; +import { BRUNCH_ORCHESTRATOR_STUB_TOOL } from '../../../../session/schema/tool-names.js'; -export { BRUNCH_ORCHESTRATOR_STUB_TOOL } from '../../../session/schema/tool-names.js'; +export { BRUNCH_ORCHESTRATOR_STUB_TOOL } from '../../../../session/schema/tool-names.js'; const OrchestratorStubParams = Type.Object({ message: Type.String({ diff --git a/src/.pi/extensions/runtime/authority-matrix.test.ts b/src/.pi/extensions/agent-runtime/runtime/authority-matrix.test.ts similarity index 91% rename from src/.pi/extensions/runtime/authority-matrix.test.ts rename to src/.pi/extensions/agent-runtime/runtime/authority-matrix.test.ts index 73545a28f..e191bff54 100644 --- a/src/.pi/extensions/runtime/authority-matrix.test.ts +++ b/src/.pi/extensions/agent-runtime/runtime/authority-matrix.test.ts @@ -1,10 +1,10 @@ import type { ExtensionAPI } from '@earendil-works/pi-coding-agent'; import { describe, expect, it } from 'vitest'; -import type { CommandResult } from '../../../graph/command-executor.js'; -import { groundingFloorGaps } from '../../../graph/schema/elicitation-gap-fixtures.js'; -import { isToolBlockedForRuntimeState } from '../../../projections/session/runtime-policy.js'; -import { DEFAULT_BRUNCH_AGENT_STATE } from '../../../session/runtime-state.js'; +import type { CommandResult } from '../../../../graph/command-executor.js'; +import { groundingFloorGaps } from '../../../../graph/schema/elicitation-gap-fixtures.js'; +import { isToolBlockedForRuntimeState } from '../../../../projections/session/runtime-policy.js'; +import { DEFAULT_BRUNCH_AGENT_STATE } from '../../../../session/runtime-state.js'; import { activeToolNamesForBrunchAgentState, projectBrunchAgentState } from './index.js'; const SIDE_EFFECTING_POC_TOOLS = ['bash', 'edit', 'write'] as const; diff --git a/src/.pi/extensions/runtime/index.ts b/src/.pi/extensions/agent-runtime/runtime/index.ts similarity index 95% rename from src/.pi/extensions/runtime/index.ts rename to src/.pi/extensions/agent-runtime/runtime/index.ts index 00f6c8a9f..c0ba1a617 100644 --- a/src/.pi/extensions/runtime/index.ts +++ b/src/.pi/extensions/agent-runtime/runtime/index.ts @@ -17,12 +17,12 @@ import { } from '@earendil-works/pi-coding-agent'; import { Text } from '@earendil-works/pi-tui'; -import { groundingFloorGaps } from '../../../graph/schema/elicitation-gap-fixtures.js'; -import type { ElicitationGap } from '../../../graph/schema/elicitation-gaps.js'; +import { groundingFloorGaps } from '../../../../graph/schema/elicitation-gap-fixtures.js'; +import type { ElicitationGap } from '../../../../graph/schema/elicitation-gaps.js'; import { isToolBlockedForRuntimeState, toolPolicyForRuntimeState, -} from '../../../projections/session/runtime-policy.js'; +} from '../../../../projections/session/runtime-policy.js'; import { activeToolNamesForPosture } from './state.js'; export { agentBodyResourceLocation } from './state.js'; @@ -31,7 +31,7 @@ export { DEFAULT_BRUNCH_AGENT_STATE, projectBrunchAgentState, type ResolvedBrunchAgentState, -} from '../../../projections/session/runtime-state.js'; +} from '../../../../projections/session/runtime-state.js'; export { BRUNCH_AGENT_RUNTIME_STATE_CUSTOM_TYPE, @@ -41,15 +41,15 @@ export { type BrunchAgentState, type BrunchAgentStateEntryData, type BrunchAgentStateEntrySessionManager, -} from '../../../session/runtime-state.js'; +} from '../../../../session/runtime-state.js'; import { projectBrunchAgentState, type ResolvedBrunchAgentState, -} from '../../../projections/session/runtime-state.js'; +} from '../../../../projections/session/runtime-state.js'; import { appendBrunchAgentRuntimeInit, type BrunchAgentStateEntrySessionManager, -} from '../../../session/runtime-state.js'; +} from '../../../../session/runtime-state.js'; interface CustomEntryLike { type?: unknown; diff --git a/src/.pi/extensions/runtime/state.test.ts b/src/.pi/extensions/agent-runtime/runtime/state.test.ts similarity index 97% rename from src/.pi/extensions/runtime/state.test.ts rename to src/.pi/extensions/agent-runtime/runtime/state.test.ts index 62734d9e9..61dc345f1 100644 --- a/src/.pi/extensions/runtime/state.test.ts +++ b/src/.pi/extensions/agent-runtime/runtime/state.test.ts @@ -3,12 +3,12 @@ import { fileURLToPath } from 'node:url'; import { describe, expect, it } from 'vitest'; -import { groundingFloorGaps } from '../../../graph/schema/elicitation-gap-fixtures.js'; +import { groundingFloorGaps } from '../../../../graph/schema/elicitation-gap-fixtures.js'; import { FOREGROUND_AGENT_ROSTER, delegatableAgentsForRuntimeState, -} from '../../../projections/session/runtime-policy.js'; -import { projectBrunchAgentState } from '../../../projections/session/runtime-state.js'; +} from '../../../../projections/session/runtime-policy.js'; +import { projectBrunchAgentState } from '../../../../projections/session/runtime-state.js'; import { BRUNCH_ORCHESTRATOR_STUB_TOOL } from '../orchestrator-stub/index.js'; import { activeToolNamesForPosture, agentBodyResourceLocation, manifestsForState } from './state.js'; diff --git a/src/.pi/extensions/runtime/state.ts b/src/.pi/extensions/agent-runtime/runtime/state.ts similarity index 93% rename from src/.pi/extensions/runtime/state.ts rename to src/.pi/extensions/agent-runtime/runtime/state.ts index 176fbc0d2..dcdb71330 100644 --- a/src/.pi/extensions/runtime/state.ts +++ b/src/.pi/extensions/agent-runtime/runtime/state.ts @@ -1,21 +1,21 @@ import { fileURLToPath } from 'node:url'; -import type { ElicitationGap } from '../../../graph/schema/elicitation-gaps.js'; -import type { CapabilityId } from '../../../projections/session/capability-readiness.js'; +import type { ElicitationGap } from '../../../../graph/schema/elicitation-gaps.js'; +import type { CapabilityId } from '../../../../projections/session/capability-readiness.js'; import { AUTO_EXCLUDED_STRATEGIES, axisOptionsForRuntimeState, isCapabilityLegalForGaps, toolPolicyForRuntimeState, type ResolvedBrunchAgentState, -} from '../../../projections/session/runtime-policy.js'; +} from '../../../../projections/session/runtime-policy.js'; import { AGENT_LENS_IDS, AGENT_METHOD_IDS, AGENT_STRATEGY_IDS, type AgentMethodId, type AgentRoleId, -} from '../../../session/schema/kinds.js'; +} from '../../../../session/schema/kinds.js'; import { loadPromptResourceManifestEntries, type PromptManifests, @@ -169,5 +169,5 @@ function selectAxisResources({ } export function agentBodyResourceLocation(agentId: AgentRoleId): string { - return fileURLToPath(new URL(`../../agents/${agentId}/SYSTEM.md`, import.meta.url)); + return fileURLToPath(new URL(`../../../agents/${agentId}/SYSTEM.md`, import.meta.url)); } diff --git a/src/.pi/extensions/system-prompts/__previews__/elicitor--auto-floor-gaps-open.md b/src/.pi/extensions/agent-runtime/system-prompts/__previews__/elicitor--auto-floor-gaps-open.md similarity index 100% rename from src/.pi/extensions/system-prompts/__previews__/elicitor--auto-floor-gaps-open.md rename to src/.pi/extensions/agent-runtime/system-prompts/__previews__/elicitor--auto-floor-gaps-open.md diff --git a/src/.pi/extensions/system-prompts/__previews__/elicitor--auto-high-coverage.md b/src/.pi/extensions/agent-runtime/system-prompts/__previews__/elicitor--auto-high-coverage.md similarity index 100% rename from src/.pi/extensions/system-prompts/__previews__/elicitor--auto-high-coverage.md rename to src/.pi/extensions/agent-runtime/system-prompts/__previews__/elicitor--auto-high-coverage.md diff --git a/src/.pi/extensions/system-prompts/__previews__/elicitor--pinned-strategy-lens.md b/src/.pi/extensions/agent-runtime/system-prompts/__previews__/elicitor--pinned-strategy-lens.md similarity index 100% rename from src/.pi/extensions/system-prompts/__previews__/elicitor--pinned-strategy-lens.md rename to src/.pi/extensions/agent-runtime/system-prompts/__previews__/elicitor--pinned-strategy-lens.md diff --git a/src/.pi/extensions/system-prompts/__previews__/elicitor--pushed-context.md b/src/.pi/extensions/agent-runtime/system-prompts/__previews__/elicitor--pushed-context.md similarity index 100% rename from src/.pi/extensions/system-prompts/__previews__/elicitor--pushed-context.md rename to src/.pi/extensions/agent-runtime/system-prompts/__previews__/elicitor--pushed-context.md diff --git a/src/.pi/extensions/system-prompts/__tests__/compose.test.ts b/src/.pi/extensions/agent-runtime/system-prompts/__tests__/compose.test.ts similarity index 97% rename from src/.pi/extensions/system-prompts/__tests__/compose.test.ts rename to src/.pi/extensions/agent-runtime/system-prompts/__tests__/compose.test.ts index 6bcdbff2e..1086ae371 100644 --- a/src/.pi/extensions/system-prompts/__tests__/compose.test.ts +++ b/src/.pi/extensions/agent-runtime/system-prompts/__tests__/compose.test.ts @@ -5,19 +5,21 @@ import { fileURLToPath } from 'node:url'; import { parseFrontmatter } from '@earendil-works/pi-coding-agent'; import { describe, expect, it } from 'vitest'; -import { groundingFloorGaps } from '../../../../graph/schema/elicitation-gap-fixtures.js'; -import type { ElicitationGap } from '../../../../graph/schema/elicitation-gaps.js'; -import type { NodeKind } from '../../../../graph/schema/nodes.js'; +import { groundingFloorGaps } from '../../../../../graph/schema/elicitation-gap-fixtures.js'; +import type { ElicitationGap } from '../../../../../graph/schema/elicitation-gaps.js'; +import type { NodeKind } from '../../../../../graph/schema/nodes.js'; import { DEFAULT_BRUNCH_AGENT_STATE, projectBrunchAgentState, -} from '../../../../projections/session/runtime-state.js'; -import type { WorkspacePostureState } from '../../../../session/workspace-session-coordinator.js'; +} from '../../../../../projections/session/runtime-state.js'; +import type { WorkspacePostureState } from '../../../../../session/workspace-session-coordinator.js'; import { LENS_RESOURCES, METHOD_RESOURCES, STRATEGY_RESOURCES } from '../../runtime/state.js'; import { composeAgentPrompt, type ComposeAgentPromptInput } from '../compose.js'; import { renderBrunchSkills } from '../prompt-skills.js'; -const projectRoot = dirname(dirname(dirname(dirname(dirname(dirname(fileURLToPath(import.meta.url))))))); +const projectRoot = dirname( + dirname(dirname(dirname(dirname(dirname(dirname(fileURLToPath(import.meta.url))))))), +); const groundingSpec = { id: 1, diff --git a/src/.pi/extensions/system-prompts/__tests__/world-reads.test.ts b/src/.pi/extensions/agent-runtime/system-prompts/__tests__/world-reads.test.ts similarity index 95% rename from src/.pi/extensions/system-prompts/__tests__/world-reads.test.ts rename to src/.pi/extensions/agent-runtime/system-prompts/__tests__/world-reads.test.ts index 81418fe6b..03d2ace72 100644 --- a/src/.pi/extensions/system-prompts/__tests__/world-reads.test.ts +++ b/src/.pi/extensions/agent-runtime/system-prompts/__tests__/world-reads.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest'; -import type { GraphSlice } from '../../../../graph/queries.js'; -import type { GraphReaders } from '../../graph/index.js'; +import type { GraphSlice } from '../../../../../graph/queries.js'; +import type { GraphReaders } from '../../../brunch-data/graph/index.js'; import { createWorldReadCache } from '../world-reads.js'; interface ReadCounts { diff --git a/src/.pi/extensions/system-prompts/compose.ts b/src/.pi/extensions/agent-runtime/system-prompts/compose.ts similarity index 92% rename from src/.pi/extensions/system-prompts/compose.ts rename to src/.pi/extensions/agent-runtime/system-prompts/compose.ts index cb8c298a4..40b87ac0a 100644 --- a/src/.pi/extensions/system-prompts/compose.ts +++ b/src/.pi/extensions/agent-runtime/system-prompts/compose.ts @@ -1,11 +1,11 @@ -import { selectElicitationGap } from '../../../graph/elicitation-driver.js'; -import type { ElicitationGap } from '../../../graph/schema/elicitation-gaps.js'; -import type { ResolvedBrunchAgentState } from '../../../projections/session/runtime-state.js'; -import { renderSoftReadinessEstimate } from '../../../renderers/session/readiness-estimate.js'; +import { selectElicitationGap } from '../../../../graph/elicitation-driver.js'; +import type { ElicitationGap } from '../../../../graph/schema/elicitation-gaps.js'; +import type { ResolvedBrunchAgentState } from '../../../../projections/session/runtime-state.js'; +import { renderSoftReadinessEstimate } from '../../../../renderers/session/readiness-estimate.js'; import type { AgentPromptSpecContext, AgentPromptWorkspaceContext, -} from '../../../session/agent-context-seed.js'; +} from '../../../../session/agent-context-seed.js'; import { manifestsForState } from '../runtime/state.js'; import { renderBrunchSkills, type PromptManifests } from './prompt-skills.js'; diff --git a/src/.pi/extensions/system-prompts/index.ts b/src/.pi/extensions/agent-runtime/system-prompts/index.ts similarity index 97% rename from src/.pi/extensions/system-prompts/index.ts rename to src/.pi/extensions/agent-runtime/system-prompts/index.ts index d92ef13e4..39ee68447 100644 --- a/src/.pi/extensions/system-prompts/index.ts +++ b/src/.pi/extensions/agent-runtime/system-prompts/index.ts @@ -7,8 +7,8 @@ import { type AgentPromptSessionContext, type AgentPromptSpecContext, type AgentPromptWorkspaceContext, -} from '../../../session/agent-context-seed.js'; -import type { GraphReaders } from '../graph/index.js'; +} from '../../../../session/agent-context-seed.js'; +import type { GraphReaders } from '../../brunch-data/graph/index.js'; import { activeToolNamesForBrunchAgentState, agentBodyResourceLocation, diff --git a/src/.pi/extensions/system-prompts/prompt-skills.ts b/src/.pi/extensions/agent-runtime/system-prompts/prompt-skills.ts similarity index 97% rename from src/.pi/extensions/system-prompts/prompt-skills.ts rename to src/.pi/extensions/agent-runtime/system-prompts/prompt-skills.ts index da4b965aa..f24075f3e 100644 --- a/src/.pi/extensions/system-prompts/prompt-skills.ts +++ b/src/.pi/extensions/agent-runtime/system-prompts/prompt-skills.ts @@ -87,7 +87,7 @@ export function skillToPromptResourceManifestEntry( } function promptResourceLocation(family: PromptResourceFamily, id: string): string { - return fileURLToPath(new URL(`../../skills/${family}/${id}/SKILL.md`, import.meta.url)); + return fileURLToPath(new URL(`../../../skills/${family}/${id}/SKILL.md`, import.meta.url)); } function escapeXml(value: string): string { diff --git a/src/.pi/extensions/system-prompts/world-reads.ts b/src/.pi/extensions/agent-runtime/system-prompts/world-reads.ts similarity index 86% rename from src/.pi/extensions/system-prompts/world-reads.ts rename to src/.pi/extensions/agent-runtime/system-prompts/world-reads.ts index 127a01e97..faedad70b 100644 --- a/src/.pi/extensions/system-prompts/world-reads.ts +++ b/src/.pi/extensions/agent-runtime/system-prompts/world-reads.ts @@ -11,9 +11,9 @@ * the reads are cached, never the rendered blocks. */ -import type { GraphSlice } from '../../../graph/queries.js'; -import type { ElicitationGap } from '../../../graph/schema/elicitation-gaps.js'; -import type { GraphReaders } from '../graph/index.js'; +import type { GraphSlice } from '../../../../graph/queries.js'; +import type { ElicitationGap } from '../../../../graph/schema/elicitation-gaps.js'; +import type { GraphReaders } from '../../brunch-data/graph/index.js'; export interface WorldReads { readonly graph: GraphSlice; diff --git a/src/.pi/extensions/context/get-cwd.ts b/src/.pi/extensions/brunch-data/context/get-cwd.ts similarity index 86% rename from src/.pi/extensions/context/get-cwd.ts rename to src/.pi/extensions/brunch-data/context/get-cwd.ts index 1af1b14c6..97277781f 100644 --- a/src/.pi/extensions/context/get-cwd.ts +++ b/src/.pi/extensions/brunch-data/context/get-cwd.ts @@ -1,14 +1,14 @@ import { resolve } from 'node:path'; -import { renderWorkspaceContext } from '../../../renderers/workspace/workspace-context.js'; +import { renderWorkspaceContext } from '../../../../renderers/workspace/workspace-context.js'; import { inspectWorkspaceOverview, type WorkspaceOverview, -} from '../../../session/workspace-overview-context.js'; +} from '../../../../session/workspace-overview-context.js'; import { inspectWorkspaceCwdInventory, type WorkspaceCwdInventory, -} from '../../../workspace/cwd-inventory.js'; +} from '../../../../workspace/cwd-inventory.js'; import type { SessionManagerLike } from './session-binding.js'; // The session cwd lives on the Pi header, which is reachable only via diff --git a/src/.pi/extensions/context/get-specification.ts b/src/.pi/extensions/brunch-data/context/get-specification.ts similarity index 86% rename from src/.pi/extensions/context/get-specification.ts rename to src/.pi/extensions/brunch-data/context/get-specification.ts index 0e752d2cb..d35e1e527 100644 --- a/src/.pi/extensions/context/get-specification.ts +++ b/src/.pi/extensions/brunch-data/context/get-specification.ts @@ -1,5 +1,5 @@ -import { renderSpecificationContext } from '../../../renderers/specification/specification-context.js'; -import { inspectSpecificationOverview } from '../../../session/specification-overview-context.js'; +import { renderSpecificationContext } from '../../../../renderers/specification/specification-context.js'; +import { inspectSpecificationOverview } from '../../../../session/specification-overview-context.js'; import { resolveWorkspaceCwd } from './get-cwd.js'; import { resolveSelectedSpecBinding, type SessionManagerLike } from './session-binding.js'; diff --git a/src/.pi/extensions/context/index.ts b/src/.pi/extensions/brunch-data/context/index.ts similarity index 96% rename from src/.pi/extensions/context/index.ts rename to src/.pi/extensions/brunch-data/context/index.ts index 431e45e58..ed6681ce8 100644 --- a/src/.pi/extensions/context/index.ts +++ b/src/.pi/extensions/brunch-data/context/index.ts @@ -3,12 +3,12 @@ import type { ExtensionAPI } from '@earendil-works/pi-coding-agent'; import { projectSessionRuntimeState, type RuntimeStateProjection, -} from '../../../projections/session/runtime-state.js'; +} from '../../../../projections/session/runtime-state.js'; import { renderRuntimeFrame, type SessionRuntimeFrameRenderInput, -} from '../../../renderers/session/runtime-frame.js'; -import { NonLinearTranscriptError } from '../../../session/brunch-session-envelope.js'; +} from '../../../../renderers/session/runtime-frame.js'; +import { NonLinearTranscriptError } from '../../../../session/brunch-session-envelope.js'; import { readWorkspaceContext } from './get-cwd.js'; import { readSpecificationContext } from './get-specification.js'; import { resolveSelectedSpecBinding, type SessionManagerLike } from './session-binding.js'; diff --git a/src/.pi/extensions/context/session-binding.ts b/src/.pi/extensions/brunch-data/context/session-binding.ts similarity index 97% rename from src/.pi/extensions/context/session-binding.ts rename to src/.pi/extensions/brunch-data/context/session-binding.ts index 9d4c3609a..b07e2b533 100644 --- a/src/.pi/extensions/context/session-binding.ts +++ b/src/.pi/extensions/brunch-data/context/session-binding.ts @@ -1,6 +1,6 @@ import type { SessionEntry, SessionHeader } from '@earendil-works/pi-coding-agent'; -import { isSessionBindingEntry, type SessionBindingData } from '../../../session/session-binding.js'; +import { isSessionBindingEntry, type SessionBindingData } from '../../../../session/session-binding.js'; export interface SessionManagerLike { getHeader(): SessionHeader | null; diff --git a/src/.pi/extensions/elicitation/index.test.ts b/src/.pi/extensions/brunch-data/elicitation/index.test.ts similarity index 97% rename from src/.pi/extensions/elicitation/index.test.ts rename to src/.pi/extensions/brunch-data/elicitation/index.test.ts index dbedb8178..5b90bcd09 100644 --- a/src/.pi/extensions/elicitation/index.test.ts +++ b/src/.pi/extensions/brunch-data/elicitation/index.test.ts @@ -1,11 +1,11 @@ import { eq } from 'drizzle-orm'; import { describe, expect, it } from 'vitest'; -import { createDb } from '../../../db/connection.js'; -import * as schema from '../../../db/schema.js'; -import { sortElicitationGapsForAsking } from '../../../graph/elicitation-driver.js'; -import type { ElicitationGap } from '../../../graph/index.js'; -import { CommandExecutor, getElicitationGaps } from '../../../graph/index.js'; +import { createDb } from '../../../../db/connection.js'; +import * as schema from '../../../../db/schema.js'; +import { sortElicitationGapsForAsking } from '../../../../graph/elicitation-driver.js'; +import type { ElicitationGap } from '../../../../graph/index.js'; +import { CommandExecutor, getElicitationGaps } from '../../../../graph/index.js'; import { READ_ELICITATION_GAPS_TOOL, registerBrunchElicitation, diff --git a/src/.pi/extensions/elicitation/index.ts b/src/.pi/extensions/brunch-data/elicitation/index.ts similarity index 97% rename from src/.pi/extensions/elicitation/index.ts rename to src/.pi/extensions/brunch-data/elicitation/index.ts index a8e82a745..bba8100dc 100644 --- a/src/.pi/extensions/elicitation/index.ts +++ b/src/.pi/extensions/brunch-data/elicitation/index.ts @@ -19,9 +19,14 @@ import type { Static } from '@earendil-works/pi-ai'; import type { ExtensionAPI } from '@earendil-works/pi-coding-agent'; -import { sortElicitationGapsForAsking } from '../../../graph/elicitation-driver.js'; -import type { CommandExecutor, Diagnostic, ElicitationGap, StructuralIllegal } from '../../../graph/index.js'; -import { GAP_DISPOSITIONS, NODE_KINDS, READINESS_BANDS } from '../../../graph/index.js'; +import { sortElicitationGapsForAsking } from '../../../../graph/elicitation-driver.js'; +import type { + CommandExecutor, + Diagnostic, + ElicitationGap, + StructuralIllegal, +} from '../../../../graph/index.js'; +import { GAP_DISPOSITIONS, NODE_KINDS, READINESS_BANDS } from '../../../../graph/index.js'; export const READ_ELICITATION_GAPS_TOOL = 'read_elicitation_gaps'; export const UPDATE_ELICITATION_GAPS_TOOL = 'update_elicitation_gaps'; diff --git a/src/.pi/extensions/graph/command-adapter.ts b/src/.pi/extensions/brunch-data/graph/command-adapter.ts similarity index 97% rename from src/.pi/extensions/graph/command-adapter.ts rename to src/.pi/extensions/brunch-data/graph/command-adapter.ts index 5816d9219..df62c651a 100644 --- a/src/.pi/extensions/graph/command-adapter.ts +++ b/src/.pi/extensions/brunch-data/graph/command-adapter.ts @@ -18,10 +18,10 @@ import type { MutateGraphSuccess, RoleNamedEdgeDraft, StructuralIllegal, -} from '../../../graph/command-executor.js'; -import { authoredEdgeEndpointFields } from '../../../graph/index.js'; -import type { NodeNeighborhood } from '../../../graph/queries.js'; -import { formatGraphNodeCode, parseGraphNodeCode } from '../../../graph/schema/nodes.js'; +} from '../../../../graph/command-executor.js'; +import { authoredEdgeEndpointFields } from '../../../../graph/index.js'; +import type { NodeNeighborhood } from '../../../../graph/queries.js'; +import { formatGraphNodeCode, parseGraphNodeCode } from '../../../../graph/schema/nodes.js'; import type { ToolMutateGraphParams } from './tool-schemas.js'; export type ResolveGraphNodeCode = (code: string) => number | undefined; diff --git a/src/.pi/extensions/graph/index.ts b/src/.pi/extensions/brunch-data/graph/index.ts similarity index 95% rename from src/.pi/extensions/graph/index.ts rename to src/.pi/extensions/brunch-data/graph/index.ts index b81bfc0b6..5b8749fe9 100644 --- a/src/.pi/extensions/graph/index.ts +++ b/src/.pi/extensions/brunch-data/graph/index.ts @@ -2,7 +2,7 @@ import type { ExtensionAPI, ToolDefinition } from '@earendil-works/pi-coding-agent'; -import type { CommandExecutor } from '../../../graph/command-executor.js'; +import type { CommandExecutor } from '../../../../graph/command-executor.js'; import type { EdgeCategory, EdgeDirection, @@ -15,11 +15,11 @@ import type { NodeNeighborhood, NodeSelector, ReadinessBand, -} from '../../../graph/index.js'; -import { formatGraphOverview } from '../../../renderers/graph/graph-slice.js'; -import { formatNeighborhood } from '../../../renderers/graph/node-neighborhood.js'; -import { graphMutationProductUpdates, type ProductUpdatePublisher } from '../../../rpc/product-updates.js'; -import { stampOwnMutationWatermark } from '../../../session/prepare-next-turn.js'; +} from '../../../../graph/index.js'; +import { formatGraphOverview } from '../../../../renderers/graph/graph-slice.js'; +import { formatNeighborhood } from '../../../../renderers/graph/node-neighborhood.js'; +import { graphMutationProductUpdates, type ProductUpdatePublisher } from '../../../../rpc/product-updates.js'; +import { stampOwnMutationWatermark } from '../../../../session/prepare-next-turn.js'; import { translateMutateGraph, formatMutateGraphResult, diff --git a/src/.pi/extensions/graph/tool-schemas.ts b/src/.pi/extensions/brunch-data/graph/tool-schemas.ts similarity index 99% rename from src/.pi/extensions/graph/tool-schemas.ts rename to src/.pi/extensions/brunch-data/graph/tool-schemas.ts index d645dd126..7061b30e7 100644 --- a/src/.pi/extensions/graph/tool-schemas.ts +++ b/src/.pi/extensions/brunch-data/graph/tool-schemas.ts @@ -28,7 +28,7 @@ import { type EdgeDirection, type GraphVisibility, type NodeKindWithFormDetail, -} from '../../../graph/index.js'; +} from '../../../../graph/index.js'; const ALL_KINDS = [...INTENT_KINDS, ...ORACLE_KINDS, ...DESIGN_KINDS, ...PLAN_KINDS] as const; const DETAIL_KINDS = new Set(NODE_KINDS_REQUIRING_DETAIL); diff --git a/src/.pi/extensions/brunch-data/index.ts b/src/.pi/extensions/brunch-data/index.ts new file mode 100644 index 000000000..8fa47b151 --- /dev/null +++ b/src/.pi/extensions/brunch-data/index.ts @@ -0,0 +1,4 @@ +export * from './context/index.js'; +export * from './elicitation/index.js'; +export * from './graph/index.js'; +export * from './reconciliation/index.js'; diff --git a/src/.pi/extensions/reconciliation/index.test.ts b/src/.pi/extensions/brunch-data/reconciliation/index.test.ts similarity index 94% rename from src/.pi/extensions/reconciliation/index.test.ts rename to src/.pi/extensions/brunch-data/reconciliation/index.test.ts index 32835bc2d..15e147798 100644 --- a/src/.pi/extensions/reconciliation/index.test.ts +++ b/src/.pi/extensions/brunch-data/reconciliation/index.test.ts @@ -1,16 +1,16 @@ import { eq } from 'drizzle-orm'; import { describe, expect, it } from 'vitest'; -import { createDb } from '../../../db/connection.js'; -import * as schema from '../../../db/schema.js'; +import { createDb } from '../../../../db/connection.js'; +import * as schema from '../../../../db/schema.js'; import { CommandExecutor, getOpenReconciliationNeeds, type ReconciliationNeed, -} from '../../../graph/index.js'; -import { groundingFloorGaps } from '../../../graph/schema/elicitation-gap-fixtures.js'; -import { projectBrunchAgentState } from '../runtime/index.js'; -import { activeToolNamesForPosture } from '../runtime/state.js'; +} from '../../../../graph/index.js'; +import { groundingFloorGaps } from '../../../../graph/schema/elicitation-gap-fixtures.js'; +import { projectBrunchAgentState } from '../../agent-runtime/runtime/index.js'; +import { activeToolNamesForPosture } from '../../agent-runtime/runtime/state.js'; import { READ_RECONCILIATION_NEEDS_TOOL, registerBrunchReconciliation, diff --git a/src/.pi/extensions/reconciliation/index.ts b/src/.pi/extensions/brunch-data/reconciliation/index.ts similarity index 99% rename from src/.pi/extensions/reconciliation/index.ts rename to src/.pi/extensions/brunch-data/reconciliation/index.ts index aa8a2e505..6d1c6db4b 100644 --- a/src/.pi/extensions/reconciliation/index.ts +++ b/src/.pi/extensions/brunch-data/reconciliation/index.ts @@ -18,7 +18,7 @@ import type { ReconciliationNeed, ResolveReconNeedResult, StructuralIllegal, -} from '../../../graph/index.js'; +} from '../../../../graph/index.js'; export const READ_RECONCILIATION_NEEDS_TOOL = 'read_reconciliation_needs'; export const UPDATE_RECONCILIATION_NEEDS_TOOL = 'update_reconciliation_needs'; diff --git a/src/.pi/extensions/commands/index.ts b/src/.pi/extensions/commands/index.ts index f4f69cfca..58c21f669 100644 --- a/src/.pi/extensions/commands/index.ts +++ b/src/.pi/extensions/commands/index.ts @@ -52,7 +52,10 @@ import { createRuntimeModePickerComponent, createRuntimeStrategyPickerComponent, } from '../../components/runtime-posture/axis-picker.js'; -import { activeToolNamesForBrunchAgentState, projectBrunchAgentState } from '../runtime/index.js'; +import { + activeToolNamesForBrunchAgentState, + projectBrunchAgentState, +} from '../agent-runtime/runtime/index.js'; import { runBrunchWorkspaceAction, type BrunchSpecSessionPickerOptions, diff --git a/src/.pi/extensions/dev-mode/index.ts b/src/.pi/extensions/dev-mode/index.ts new file mode 100644 index 000000000..98dfaec1a --- /dev/null +++ b/src/.pi/extensions/dev-mode/index.ts @@ -0,0 +1,3 @@ +export * from './introspection/index.js'; +export * from './introspect-query/index.js'; +export * from './session-query/index.js'; diff --git a/src/.pi/extensions/introspect-query/README.md b/src/.pi/extensions/dev-mode/introspect-query/README.md similarity index 100% rename from src/.pi/extensions/introspect-query/README.md rename to src/.pi/extensions/dev-mode/introspect-query/README.md diff --git a/src/.pi/extensions/introspect-query/index.test.ts b/src/.pi/extensions/dev-mode/introspect-query/index.test.ts similarity index 100% rename from src/.pi/extensions/introspect-query/index.test.ts rename to src/.pi/extensions/dev-mode/introspect-query/index.test.ts diff --git a/src/.pi/extensions/introspect-query/index.ts b/src/.pi/extensions/dev-mode/introspect-query/index.ts similarity index 98% rename from src/.pi/extensions/introspect-query/index.ts rename to src/.pi/extensions/dev-mode/introspect-query/index.ts index f22ef278a..7f34d5e9c 100644 --- a/src/.pi/extensions/introspect-query/index.ts +++ b/src/.pi/extensions/dev-mode/introspect-query/index.ts @@ -1,11 +1,7 @@ import { defineTool, type ExtensionAPI } from '@earendil-works/pi-coding-agent'; import * as z from 'zod'; -import { - type BrunchIntrospectionStore, - type BrunchIntrospectionTurnCapture, -} from '../introspection/index.js'; -import { devToolParameters } from '../shared/pi-tool-schema.js'; +import { devToolParameters } from '../../shared/pi-tool-schema.js'; import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, @@ -14,7 +10,11 @@ import { rowsToText, truncateQueryOutput, type TruncationResult, -} from '../shared/query-projection.js'; +} from '../../shared/query-projection.js'; +import { + type BrunchIntrospectionStore, + type BrunchIntrospectionTurnCapture, +} from '../introspection/index.js'; export const BRUNCH_INTROSPECT_QUERY_TOOL = 'brunch_introspect_query'; diff --git a/src/.pi/extensions/introspection/README.md b/src/.pi/extensions/dev-mode/introspection/README.md similarity index 100% rename from src/.pi/extensions/introspection/README.md rename to src/.pi/extensions/dev-mode/introspection/README.md diff --git a/src/.pi/extensions/introspection/debug-cache.ts b/src/.pi/extensions/dev-mode/introspection/debug-cache.ts similarity index 100% rename from src/.pi/extensions/introspection/debug-cache.ts rename to src/.pi/extensions/dev-mode/introspection/debug-cache.ts diff --git a/src/.pi/extensions/introspection/index.ts b/src/.pi/extensions/dev-mode/introspection/index.ts similarity index 100% rename from src/.pi/extensions/introspection/index.ts rename to src/.pi/extensions/dev-mode/introspection/index.ts diff --git a/src/.pi/extensions/session-query/README.md b/src/.pi/extensions/dev-mode/session-query/README.md similarity index 80% rename from src/.pi/extensions/session-query/README.md rename to src/.pi/extensions/dev-mode/session-query/README.md index 69bceee4d..1595342fc 100644 --- a/src/.pi/extensions/session-query/README.md +++ b/src/.pi/extensions/dev-mode/session-query/README.md @@ -1,4 +1,4 @@ -# .pi/extensions/session-query/ — dev session-log query tool +# .pi/extensions/dev-mode/session-query/ — dev session-log query tool SPEC decisions: D39-L, D58-L, D69-L, D71-L @@ -9,7 +9,7 @@ Dev-gated, read-only Pi tool registration for `brunch_session_query`: predicate ## Does NOT own - Provider-payload capture or `/introspect` reporting — sibling `../introspection/` owns the payload plane. -- Prompt-resource manifests or product prompt behavior — `.pi/extensions/runtime/` (manifest/legality), `.pi/extensions/system-prompts/` (composition), and the `.pi/agents/` + `.pi/skills/` markdown bodies. +- Prompt-resource manifests or product prompt behavior — `.pi/extensions/agent-runtime/runtime/` (manifest/legality), `.pi/extensions/agent-runtime/system-prompts/` (composition), and the `.pi/agents/` + `.pi/skills/` markdown bodies. - Product transcript/domain projection — top-level `session/` and `projections/` seams. ## Boundary rules diff --git a/src/.pi/extensions/session-query/index.test.ts b/src/.pi/extensions/dev-mode/session-query/index.test.ts similarity index 99% rename from src/.pi/extensions/session-query/index.test.ts rename to src/.pi/extensions/dev-mode/session-query/index.test.ts index f434d0ec2..0793d116d 100644 --- a/src/.pi/extensions/session-query/index.test.ts +++ b/src/.pi/extensions/dev-mode/session-query/index.test.ts @@ -4,7 +4,7 @@ import { fauxAssistantMessage, fauxToolCall } from '@earendil-works/pi-ai'; import type { SessionEntry } from '@earendil-works/pi-coding-agent'; import { describe, expect, it } from 'vitest'; -import { createBrunchFauxHarness } from '../../../dev/index.js'; +import { createBrunchFauxHarness } from '../../../../dev/index.js'; import { BRUNCH_SESSION_QUERY_TOOL, createBrunchSessionQueryTool, diff --git a/src/.pi/extensions/session-query/index.ts b/src/.pi/extensions/dev-mode/session-query/index.ts similarity index 98% rename from src/.pi/extensions/session-query/index.ts rename to src/.pi/extensions/dev-mode/session-query/index.ts index 47a7029e1..b775548aa 100644 --- a/src/.pi/extensions/session-query/index.ts +++ b/src/.pi/extensions/dev-mode/session-query/index.ts @@ -1,7 +1,7 @@ import { defineTool, type ExtensionAPI, type SessionEntry } from '@earendil-works/pi-coding-agent'; import * as z from 'zod'; -import { devToolParameters } from '../shared/pi-tool-schema.js'; +import { devToolParameters } from '../../shared/pi-tool-schema.js'; import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, @@ -10,7 +10,7 @@ import { rowsToText, truncateQueryOutput, type TruncationResult, -} from '../shared/query-projection.js'; +} from '../../shared/query-projection.js'; export const BRUNCH_SESSION_QUERY_TOOL = 'brunch_session_query'; const DEFAULT_LAST_MATCHING = 1; diff --git a/src/.pi/extensions/session-hooks/index.ts b/src/.pi/extensions/session-hooks/index.ts new file mode 100644 index 000000000..bf4f6c7ca --- /dev/null +++ b/src/.pi/extensions/session-hooks/index.ts @@ -0,0 +1 @@ +export * from './session/lifecycle.js'; diff --git a/src/.pi/extensions/session/lifecycle.test.ts b/src/.pi/extensions/session-hooks/session/lifecycle.test.ts similarity index 100% rename from src/.pi/extensions/session/lifecycle.test.ts rename to src/.pi/extensions/session-hooks/session/lifecycle.test.ts diff --git a/src/.pi/extensions/session/lifecycle.ts b/src/.pi/extensions/session-hooks/session/lifecycle.ts similarity index 100% rename from src/.pi/extensions/session/lifecycle.ts rename to src/.pi/extensions/session-hooks/session/lifecycle.ts diff --git a/src/.pi/extensions/subagents/prompt-assembly.ts b/src/.pi/extensions/subagents/prompt-assembly.ts index 27fd2e3f6..d8dfb9f1b 100644 --- a/src/.pi/extensions/subagents/prompt-assembly.ts +++ b/src/.pi/extensions/subagents/prompt-assembly.ts @@ -5,9 +5,9 @@ import type { AgentPromptSessionContext, } from '../../../session/agent-context-seed.js'; import { renderWorkspaceSeed } from '../../../session/agent-context-seed.js'; -import { LENS_RESOURCES, METHOD_RESOURCES, STRATEGY_RESOURCES } from '../runtime/state.js'; -import type { PromptManifests } from '../system-prompts/prompt-skills.js'; -import { renderBrunchSkills } from '../system-prompts/prompt-skills.js'; +import { LENS_RESOURCES, METHOD_RESOURCES, STRATEGY_RESOURCES } from '../agent-runtime/runtime/state.js'; +import type { PromptManifests } from '../agent-runtime/system-prompts/prompt-skills.js'; +import { renderBrunchSkills } from '../agent-runtime/system-prompts/prompt-skills.js'; import type { SubagentDefinition } from './agents.js'; export interface BackgroundWorldSnapshot { diff --git a/src/.pi/extensions/subagents/session.ts b/src/.pi/extensions/subagents/session.ts index 688c0df4e..1905416b1 100644 --- a/src/.pi/extensions/subagents/session.ts +++ b/src/.pi/extensions/subagents/session.ts @@ -36,9 +36,9 @@ import { type ToolDefinition, } from '@earendil-works/pi-coding-agent'; -import { createReadGraphTool, type GraphReaders } from '../graph/index.js'; -import { createWebFetchTool } from '../web/web-fetch.js'; -import { createWebSearchTool } from '../web/web-search.js'; +import { createReadGraphTool, type GraphReaders } from '../brunch-data/graph/index.js'; +import { createWebFetchTool } from '../web-tools/web/web-fetch.js'; +import { createWebSearchTool } from '../web-tools/web/web-search.js'; import type { SubagentDefinition } from './agents.js'; import { composeBackgroundSubagentPrompt, type BackgroundWorldSnapshot } from './prompt-assembly.js'; diff --git a/src/.pi/extensions/subagents/subagents.test.ts b/src/.pi/extensions/subagents/subagents.test.ts index ee611abb5..96cb461fb 100644 --- a/src/.pi/extensions/subagents/subagents.test.ts +++ b/src/.pi/extensions/subagents/subagents.test.ts @@ -24,7 +24,7 @@ import { brunchFauxProviderConfig, defaultBrunchFauxModel, } from '../../../probes/faux-provider.js'; -import type { GraphReaders } from '../graph/index.js'; +import type { GraphReaders } from '../brunch-data/graph/index.js'; import { loadSubagentDefinitions, parseSubagentMarkdown, diff --git a/src/.pi/extensions/web-tools/index.ts b/src/.pi/extensions/web-tools/index.ts new file mode 100644 index 000000000..59c96fb1e --- /dev/null +++ b/src/.pi/extensions/web-tools/index.ts @@ -0,0 +1 @@ +export * from './web/index.js'; diff --git a/src/.pi/extensions/web/index.ts b/src/.pi/extensions/web-tools/web/index.ts similarity index 100% rename from src/.pi/extensions/web/index.ts rename to src/.pi/extensions/web-tools/web/index.ts diff --git a/src/.pi/extensions/web/web-fetch.ts b/src/.pi/extensions/web-tools/web/web-fetch.ts similarity index 100% rename from src/.pi/extensions/web/web-fetch.ts rename to src/.pi/extensions/web-tools/web/web-fetch.ts diff --git a/src/.pi/extensions/web/web-search.ts b/src/.pi/extensions/web-tools/web/web-search.ts similarity index 100% rename from src/.pi/extensions/web/web-search.ts rename to src/.pi/extensions/web-tools/web/web-search.ts diff --git a/src/.pi/extensions/web/web-tools.test.ts b/src/.pi/extensions/web-tools/web/web-tools.test.ts similarity index 100% rename from src/.pi/extensions/web/web-tools.test.ts rename to src/.pi/extensions/web-tools/web/web-tools.test.ts diff --git a/src/.pi/settings.json b/src/.pi/settings.json index e9b830e12..e7d73d40f 100644 --- a/src/.pi/settings.json +++ b/src/.pi/settings.json @@ -1,17 +1,26 @@ { "extensions": [ "extensions/chrome/index.ts", + "-extensions/agent-runtime/index.ts", + "-extensions/agent-runtime/orchestrator-stub/index.ts", + "-extensions/agent-runtime/runtime/index.ts", + "-extensions/agent-runtime/system-prompts/index.ts", + "-extensions/brunch-data/index.ts", + "-extensions/brunch-data/context/index.ts", + "-extensions/brunch-data/elicitation/index.ts", + "-extensions/brunch-data/graph/index.ts", + "-extensions/brunch-data/reconciliation/index.ts", "-extensions/commands/index.ts", + "-extensions/commands/policy.ts", "-extensions/compaction/index.ts", - "-extensions/elicitation/index.ts", - "-extensions/graph/index.ts", - "-extensions/introspect-query/index.ts", - "-extensions/reconciliation/index.ts", - "-extensions/runtime/index.ts", + "-extensions/dev-mode/index.ts", + "-extensions/dev-mode/introspect-query/index.ts", + "-extensions/dev-mode/introspection/index.ts", + "-extensions/dev-mode/session-query/index.ts", + "-extensions/session-hooks/index.ts", "-extensions/subagents/index.ts", - "-extensions/system-prompts/index.ts", - "-extensions/web/index.ts", - "-extensions/workspace/index.ts", - "-extensions/commands/policy.ts" + "-extensions/web-tools/index.ts", + "-extensions/web-tools/web/index.ts", + "-extensions/workspace/index.ts" ] } diff --git a/src/.pi/skills/README.md b/src/.pi/skills/README.md index bf5797578..d2424bda7 100644 --- a/src/.pi/skills/README.md +++ b/src/.pi/skills/README.md @@ -4,7 +4,7 @@ SPEC decisions: D25-L, D39-L, D52-L, D58-L, D59-L, D85-L ## Owns -Agent Skills-standard prompt resources the Brunch Pi session agent reads on demand after `.pi/extensions/runtime/state.ts` advertises them in a runtime-filtered `` manifest. +Agent Skills-standard prompt resources the Brunch Pi session agent reads on demand after `.pi/extensions/agent-runtime/runtime/state.ts` advertises them in a runtime-filtered `` manifest. These are Pi-harness prompt resources, not product data models and not ambient filesystem discovery inputs. @@ -20,19 +20,19 @@ skills/ └── references/*.md optional disclosed reference payloads ``` -Each live resource is a directory whose `SKILL.md` has YAML frontmatter (`name`, `description`) plus the instruction body. `name` must equal the parent directory and the code-owned id in `.pi/extensions/runtime/state.ts`. +Each live resource is a directory whose `SKILL.md` has YAML frontmatter (`name`, `description`) plus the instruction body. `name` must equal the parent directory and the code-owned id in `.pi/extensions/agent-runtime/runtime/state.ts`. ## Boundary rules ```pseudo rules: - .pi/extensions/runtime/state.ts -> .pi/skills/*/*/SKILL.md [explicit code-owned path list] - .pi/extensions/runtime/state.ts -> pi loadSkills(includeDefaults:false, skillPaths=[...]) + .pi/extensions/agent-runtime/runtime/state.ts -> .pi/skills/*/*/SKILL.md [explicit code-owned path list] + .pi/extensions/agent-runtime/runtime/state.ts -> pi loadSkills(includeDefaults:false, skillPaths=[...]) .pi/skills/**/SKILL.md x> TypeScript imports [read-only prompt resources] .pi/skills/ x> graph mutation [guidance only] ``` -The legal set is sealed by the code-owned path list in `.pi/extensions/runtime/state.ts`; adding a `SKILL.md` does not make it available until that table enumerates it. Frontmatter owns `name` and `description`; code owns axis family, legality, and location enumeration. The former `goals/` family is retired by D85-L; the elicitor objective postures are inline in `src/.pi/agents/elicitor/SYSTEM.md`. +The legal set is sealed by the code-owned path list in `.pi/extensions/agent-runtime/runtime/state.ts`; adding a `SKILL.md` does not make it available until that table enumerates it. Frontmatter owns `name` and `description`; code owns axis family, legality, and location enumeration. The former `goals/` family is retired by D85-L; the elicitor objective postures are inline in `src/.pi/agents/elicitor/SYSTEM.md`. ## Prompt-resource sub-shapes diff --git a/src/README.md b/src/README.md index 30833d8f6..00529bf27 100644 --- a/src/README.md +++ b/src/README.md @@ -58,7 +58,7 @@ Rules: - `graph/` imports from `db/`. No other layer imports `db/` directly. - `.pi/` owns Pi-harness agents/resources/extensions/components. It is not just an adapter folder; it is the product's sealed Pi runtime surface. - `.pi/extensions/` registers Pi tools/hooks/UI affordances and delegates product semantics outward. -- `.pi/agents/` owns agent role prompt definitions (markdown); runtime prompt assembly lives in `.pi/extensions/system-prompts/` and the legal resource manifest in `.pi/extensions/runtime/`; `.pi/skills/` owns read-on-demand markdown resources. +- `.pi/agents/` owns agent role prompt definitions (markdown); runtime prompt assembly lives in `.pi/extensions/agent-runtime/system-prompts/` and the legal resource manifest in `.pi/extensions/agent-runtime/runtime/`; `.pi/skills/` owns read-on-demand markdown resources. - `projections/` owns reusable structured output; `renderers/` owns reusable lossy text output. - `web/` is a separate Vite build target. diff --git a/src/app/brunch-tui.ts b/src/app/brunch-tui.ts index b8c3bc1f0..6961624eb 100644 --- a/src/app/brunch-tui.ts +++ b/src/app/brunch-tui.ts @@ -13,11 +13,11 @@ import { } from '@earendil-works/pi-coding-agent'; import { runWorkspaceDialogPreflight } from '../.pi/components/workspace-dialog.js'; -import type { GraphReaders } from '../.pi/extensions/graph/index.js'; +import type { GraphReaders } from '../.pi/extensions/brunch-data/index.js'; import { appendEntryContentToDebugCache, appendOriginationRecordToDebugCache, -} from '../.pi/extensions/introspection/index.js'; +} from '../.pi/extensions/dev-mode/index.js'; import { isBrunchDevEnabled } from '../dev/brunch-dev.js'; import { openWorkspaceGraphRuntime, diff --git a/src/app/pi-extensions.ts b/src/app/pi-extensions.ts index c9c4aa608..ed96396b3 100644 --- a/src/app/pi-extensions.ts +++ b/src/app/pi-extensions.ts @@ -5,51 +5,48 @@ import { } from '@earendil-works/pi-coding-agent'; import { registerBrunchAlternatives } from '../.pi/components/alternatives.js'; +import { registerBrunchOrchestratorStub } from '../.pi/extensions/agent-runtime/index.js'; +import { + conservativeUncoveredFloorGaps, + registerBrunchOperationalModePolicy, +} from '../.pi/extensions/agent-runtime/index.js'; +import { + registerBrunchPrompting, + type BrunchPromptContextProvider, +} from '../.pi/extensions/agent-runtime/index.js'; +import { registerBrunchContext } from '../.pi/extensions/brunch-data/index.js'; +import { registerBrunchElicitation } from '../.pi/extensions/brunch-data/index.js'; +import { registerBrunchGraph, type BrunchGraphDeps } from '../.pi/extensions/brunch-data/index.js'; +import { registerBrunchReconciliation } from '../.pi/extensions/brunch-data/index.js'; import { registerBrunchChrome } from '../.pi/extensions/chrome/index.js'; import { type BrunchChromeState } from '../.pi/extensions/chrome/index.js'; import { registerBrunchCommands, type BrunchCommandsOptions } from '../.pi/extensions/commands/index.js'; import { registerBrunchBranchPolicyHandlers } from '../.pi/extensions/commands/policy.js'; -import { registerBrunchContext } from '../.pi/extensions/context/index.js'; -import { registerBrunchElicitation } from '../.pi/extensions/elicitation/index.js'; -import { registerStructuredExchange } from '../.pi/extensions/exchanges/index.js'; -import { registerBrunchGraph, type BrunchGraphDeps } from '../.pi/extensions/graph/index.js'; import { BRUNCH_INTROSPECT_QUERY_TOOL, registerBrunchIntrospectQuery, -} from '../.pi/extensions/introspect-query/index.js'; +} from '../.pi/extensions/dev-mode/index.js'; import { appendEntryContentToDebugCache, registerBrunchIntrospection, type BrunchDebugCacheOptions, type BrunchIntrospectionOptions, -} from '../.pi/extensions/introspection/index.js'; +} from '../.pi/extensions/dev-mode/index.js'; +import { BRUNCH_SESSION_QUERY_TOOL, registerBrunchSessionQuery } from '../.pi/extensions/dev-mode/index.js'; +import { registerStructuredExchange } from '../.pi/extensions/exchanges/index.js'; import { type GraphMentionSource } from '../.pi/extensions/mentions/index.js'; import { registerBrunchMentionAutocomplete } from '../.pi/extensions/mentions/index.js'; -import { registerBrunchOrchestratorStub } from '../.pi/extensions/orchestrator-stub/index.js'; -import { registerBrunchReconciliation } from '../.pi/extensions/reconciliation/index.js'; -import { - conservativeUncoveredFloorGaps, - registerBrunchOperationalModePolicy, -} from '../.pi/extensions/runtime/index.js'; -import { - BRUNCH_SESSION_QUERY_TOOL, - registerBrunchSessionQuery, -} from '../.pi/extensions/session-query/index.js'; -import { registerBrunchSessionBoundary } from '../.pi/extensions/session/lifecycle.js'; +import { registerBrunchSessionBoundary } from '../.pi/extensions/session-hooks/index.js'; import { type BrunchSessionBoundaryHandler, type BrunchSessionBoundaryPipelineStep, -} from '../.pi/extensions/session/lifecycle.js'; +} from '../.pi/extensions/session-hooks/index.js'; import { BRUNCH_SUBAGENT_TOOL, registerBrunchSubagents, type BrunchSubagentsDeps, } from '../.pi/extensions/subagents/index.js'; -import { - registerBrunchPrompting, - type BrunchPromptContextProvider, -} from '../.pi/extensions/system-prompts/index.js'; -import { registerBrunchWebTools } from '../.pi/extensions/web/index.js'; +import { registerBrunchWebTools } from '../.pi/extensions/web-tools/index.js'; import { formatGraphNodeCode } from '../graph/schema/nodes.js'; import { CAPTURE_SWEEP_WATERMARK_CUSTOM_TYPE, @@ -77,9 +74,9 @@ export { appendBrunchAgentRuntimeSwitch, projectBrunchAgentState, registerBrunchOperationalModePolicy, -} from '../.pi/extensions/runtime/index.js'; -export { registerBrunchPrompting } from '../.pi/extensions/system-prompts/index.js'; -export { registerBrunchContext } from '../.pi/extensions/context/index.js'; +} from '../.pi/extensions/agent-runtime/index.js'; +export { registerBrunchPrompting } from '../.pi/extensions/agent-runtime/index.js'; +export { registerBrunchContext } from '../.pi/extensions/brunch-data/index.js'; export { chromeStateForWorkspace, projectBrunchChromeFooterLines, @@ -92,7 +89,7 @@ export { registerBrunchSessionBoundary, registerBrunchSessionBoundaryRefreshHandlers, type BrunchSessionBoundaryHandler, -} from '../.pi/extensions/session/lifecycle.js'; +} from '../.pi/extensions/session-hooks/index.js'; export { BRUNCH_COMMAND_PREFIX, BRUNCH_CONTINUE_COMMAND, @@ -104,15 +101,15 @@ export { registerBrunchCommands, } from '../.pi/extensions/commands/index.js'; export { runBrunchWorkspaceAction, runBrunchWorkspaceCommand } from '../.pi/extensions/workspace/index.js'; -export { registerBrunchWebTools } from '../.pi/extensions/web/index.js'; +export { registerBrunchWebTools } from '../.pi/extensions/web-tools/index.js'; -export { registerBrunchGraph } from '../.pi/extensions/graph/index.js'; +export { registerBrunchGraph } from '../.pi/extensions/brunch-data/index.js'; export { BRUNCH_ORCHESTRATOR_STUB_TOOL, createOrchestratorStubTool, registerBrunchOrchestratorStub, -} from '../.pi/extensions/orchestrator-stub/index.js'; -export { registerBrunchReconciliation } from '../.pi/extensions/reconciliation/index.js'; +} from '../.pi/extensions/agent-runtime/index.js'; +export { registerBrunchReconciliation } from '../.pi/extensions/brunch-data/index.js'; export { BRUNCH_SUBAGENT_TOOL, registerBrunchSubagents, @@ -125,17 +122,17 @@ export { type BrunchIntrospectionBaseReport, type BrunchIntrospectionStore, type BrunchIntrospectionTurnCapture, -} from '../.pi/extensions/introspection/index.js'; +} from '../.pi/extensions/dev-mode/index.js'; export { BRUNCH_SESSION_QUERY_TOOL, createBrunchSessionQueryTool, registerBrunchSessionQuery, -} from '../.pi/extensions/session-query/index.js'; +} from '../.pi/extensions/dev-mode/index.js'; export { BRUNCH_INTROSPECT_QUERY_TOOL, createBrunchIntrospectQueryTool, registerBrunchIntrospectQuery, -} from '../.pi/extensions/introspect-query/index.js'; +} from '../.pi/extensions/dev-mode/index.js'; export interface BrunchPiExtensionsOptions extends Omit { /** diff --git a/src/app/pi-subagents.ts b/src/app/pi-subagents.ts index fe2c960d1..0bc392eb9 100644 --- a/src/app/pi-subagents.ts +++ b/src/app/pi-subagents.ts @@ -8,7 +8,7 @@ * injection. */ -import type { GraphReaders } from '../.pi/extensions/graph/index.js'; +import type { GraphReaders } from '../.pi/extensions/brunch-data/index.js'; import { loadSubagentDefinitions, subagentAgentsDir } from '../.pi/extensions/subagents/agents.js'; import { loadSubagentConfig, subagentConfigPath } from '../.pi/extensions/subagents/config.js'; import type { BrunchSubagentsDeps } from '../.pi/extensions/subagents/index.js'; diff --git a/src/dev/README.md b/src/dev/README.md index 316224daf..8fe47d118 100644 --- a/src/dev/README.md +++ b/src/dev/README.md @@ -29,7 +29,7 @@ Product probes may import `src/probes/faux-provider.ts` when they need determini ## Introspection loop (D69-L) -`runBrunchIntrospectionTurn()` is the paired-run artifact writer for the dev-only introspection loop. The Pi side is the explicit, read-only `src/.pi/extensions/introspection/` registrar, included only when `createBrunchPiExtensions(..., { introspection: { enabled: true } })` is passed. Product Brunch sessions omit it by default and keep the D39-L offline default. The launcher does not mutate `process.env`; any future online real-provider lift belongs at session construction with save/restore scoping. +`runBrunchIntrospectionTurn()` is the paired-run artifact writer for the dev-only introspection loop. The Pi side is the explicit, read-only `src/.pi/extensions/dev-mode/introspection/` registrar, included only when `createBrunchPiExtensions(..., { introspection: { enabled: true } })` is passed. Product Brunch sessions omit it by default and keep the D39-L offline default. The launcher does not mutate `process.env`; any future online real-provider lift belongs at session construction with save/restore scoping. ## Tier-2 real boot loop (FE-847) diff --git a/src/graph/README.md b/src/graph/README.md index d43198b16..3d6c16a9f 100644 --- a/src/graph/README.md +++ b/src/graph/README.md @@ -114,11 +114,11 @@ not compare bare LSN values across sibling specs. ## Imported by -- `.pi/extensions/graph/` — Pi tool adapters for `mutate_graph` and `read_graph`. +- `.pi/extensions/brunch-data/graph/` — Pi tool adapters for `mutate_graph` and `read_graph`. - `rpc/` — graph projection handlers and synchronous response-capture wiring. - `projections/graph/` — topology stubs for deferred graph PROJECT seams; node-neighborhood consumers read `NodeNeighborhood` directly from `queries.ts`. - `renderers/graph/` — reusable lossy markdown/text rendering over projected graph DTOs. -- `.pi/extensions/system-prompts/` — prompt composition consumes the read-only elicitation driver and the seed renderers consume graph reads. +- `.pi/extensions/agent-runtime/system-prompts/` — prompt composition consumes the read-only elicitation driver and the seed renderers consume graph reads. - `probes/` — graph proof drivers. ## Current topology @@ -236,14 +236,14 @@ CommandExecutor writes rows transactionally appends change_log │ - ├─►.pi/extensions/graph + ├─► .pi/extensions/brunch-data/graph │ agent tool adapter │ ├─► rpc/ │ public product projections │ session.submitExchangeResponse capture wiring │ - └─► .pi/extensions/system-prompts + └─► .pi/extensions/agent-runtime/system-prompts elicitation recommendation selection and pushed seed context render inputs ``` diff --git a/src/graph/__tests__/capture-commitment-gradient-gate.test.ts b/src/graph/__tests__/capture-commitment-gradient-gate.test.ts index 55bb6b943..93defcf22 100644 --- a/src/graph/__tests__/capture-commitment-gradient-gate.test.ts +++ b/src/graph/__tests__/capture-commitment-gradient-gate.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it } from 'vitest'; -import { registerBrunchElicitation } from '../../.pi/extensions/elicitation/index.js'; -import { registerBrunchGraph } from '../../.pi/extensions/graph/index.js'; -import { registerBrunchReconciliation } from '../../.pi/extensions/reconciliation/index.js'; +import { registerBrunchElicitation } from '../../.pi/extensions/brunch-data/elicitation/index.js'; +import { registerBrunchGraph } from '../../.pi/extensions/brunch-data/graph/index.js'; +import { registerBrunchReconciliation } from '../../.pi/extensions/brunch-data/reconciliation/index.js'; import { createDb, type BrunchDb } from '../../db/connection.js'; import { changeLog } from '../../db/schema.js'; import { diff --git a/src/graph/__tests__/mutate-graph-edge-schema.test.ts b/src/graph/__tests__/mutate-graph-edge-schema.test.ts index 0d2893fe8..2678367de 100644 --- a/src/graph/__tests__/mutate-graph-edge-schema.test.ts +++ b/src/graph/__tests__/mutate-graph-edge-schema.test.ts @@ -2,7 +2,10 @@ import type { TSchema } from 'typebox'; import { Value } from 'typebox/value'; import { describe, expect, it } from 'vitest'; -import { MutateCreateEdgeSchema, MutateGraphParams } from '../../.pi/extensions/graph/tool-schemas.js'; +import { + MutateCreateEdgeSchema, + MutateGraphParams, +} from '../../.pi/extensions/brunch-data/graph/tool-schemas.js'; import { devGraphRpcMethods } from '../../rpc/methods/dev-graph.js'; import { EDGE_CATEGORIES, EDGE_CATEGORY_METADATA, type EdgeCategory } from '../index.js'; diff --git a/src/graph/__tests__/observed-shapes-coverage.test.ts b/src/graph/__tests__/observed-shapes-coverage.test.ts index af52b0e80..54faacbb9 100644 --- a/src/graph/__tests__/observed-shapes-coverage.test.ts +++ b/src/graph/__tests__/observed-shapes-coverage.test.ts @@ -3,8 +3,8 @@ import { describe, expect, it } from 'vitest'; import { READ_ELICITATION_GAPS_SHAPE, READ_ELICITATION_GAPS_TOOL, -} from '../../.pi/extensions/elicitation/index.js'; -import { ReadGraphParams } from '../../.pi/extensions/graph/tool-schemas.js'; +} from '../../.pi/extensions/brunch-data/elicitation/index.js'; +import { ReadGraphParams } from '../../.pi/extensions/brunch-data/graph/tool-schemas.js'; import { graphRpcMethods } from '../../rpc/methods/graph.js'; import { queryKeys } from '../../web/query-keys.js'; diff --git a/src/graph/__tests__/spec-ownership.test.ts b/src/graph/__tests__/spec-ownership.test.ts index 9c0ca3e28..0202ef430 100644 --- a/src/graph/__tests__/spec-ownership.test.ts +++ b/src/graph/__tests__/spec-ownership.test.ts @@ -203,7 +203,7 @@ describe('graph items are owned by spec', () => { describe('tool guard: agent-facing graph tool schemas do not expose specId', () => { it('MutateGraphParams has no top-level specId field', async () => { - const mod = await import('../../.pi/extensions/graph/tool-schemas.js'); + const mod = await import('../../.pi/extensions/brunch-data/graph/tool-schemas.js'); // Sinclair TypeBox object schemas store fields under `properties` const schema = mod.MutateGraphParams as unknown as { properties: Record; @@ -213,7 +213,7 @@ describe('tool guard: agent-facing graph tool schemas do not expose specId', () }); it('ReadGraphParams has no top-level specId field', async () => { - const mod = await import('../../.pi/extensions/graph/tool-schemas.js'); + const mod = await import('../../.pi/extensions/brunch-data/graph/tool-schemas.js'); const { Value } = await import('typebox/value'); expect(Value.Check(mod.ReadGraphParams, { mode: 'overview', specId: 1 })).toBe(false); expect(Value.Check(mod.ReadGraphParams, { mode: 'overview', spec_id: 1 })).toBe(false); diff --git a/src/probes/fixture-curation-loop.ts b/src/probes/fixture-curation-loop.ts index 72656ca9b..b2218e196 100644 --- a/src/probes/fixture-curation-loop.ts +++ b/src/probes/fixture-curation-loop.ts @@ -6,7 +6,10 @@ import { fileURLToPath } from 'node:url'; import { getAgentDir } from '@earendil-works/pi-coding-agent'; -import { appendBrunchAgentRuntimeSwitch, type BrunchAgentState } from '../.pi/extensions/runtime/index.js'; +import { + appendBrunchAgentRuntimeSwitch, + type BrunchAgentState, +} from '../.pi/extensions/agent-runtime/runtime/index.js'; import { createBrunchAgentSessionRuntimeFactory } from '../app/brunch-tui.js'; import { formatGraphNodeCode, diff --git a/src/probes/project-graph-review-cycle-proof.ts b/src/probes/project-graph-review-cycle-proof.ts index 4e9e5f4d5..def2a3110 100644 --- a/src/probes/project-graph-review-cycle-proof.ts +++ b/src/probes/project-graph-review-cycle-proof.ts @@ -6,7 +6,10 @@ import { fileURLToPath } from 'node:url'; import { getAgentDir } from '@earendil-works/pi-coding-agent'; -import { appendBrunchAgentRuntimeSwitch, type BrunchAgentState } from '../.pi/extensions/runtime/index.js'; +import { + appendBrunchAgentRuntimeSwitch, + type BrunchAgentState, +} from '../.pi/extensions/agent-runtime/runtime/index.js'; import { createBrunchAgentSessionRuntimeFactory } from '../app/brunch-tui.js'; import { openWorkspaceGraphRuntime, type GraphNode, type GraphSlice } from '../graph/index.js'; import { formatGraphNodeCode } from '../graph/schema/nodes.js'; diff --git a/src/probes/propose-graph-commit-proof.ts b/src/probes/propose-graph-commit-proof.ts index 02d30cb31..c2bd5c824 100644 --- a/src/probes/propose-graph-commit-proof.ts +++ b/src/probes/propose-graph-commit-proof.ts @@ -6,7 +6,10 @@ import { fileURLToPath } from 'node:url'; import { getAgentDir } from '@earendil-works/pi-coding-agent'; -import { appendBrunchAgentRuntimeSwitch, type BrunchAgentState } from '../.pi/extensions/runtime/index.js'; +import { + appendBrunchAgentRuntimeSwitch, + type BrunchAgentState, +} from '../.pi/extensions/agent-runtime/runtime/index.js'; import { createBrunchAgentSessionRuntimeFactory } from '../app/brunch-tui.js'; import { openWorkspaceGraphRuntime, diff --git a/src/projections/README.md b/src/projections/README.md index 400a4b5f0..65fecc96d 100644 --- a/src/projections/README.md +++ b/src/projections/README.md @@ -28,7 +28,7 @@ Disposition: `✓` resolved (direct lock or accepted transitive proof) · `●` | `session/transcript-context` | 2 | ✓ | `transcript-context.test.ts` — no non-empty markdown-bearing message disappears across the Pi `buildSessionContext()` + `convertToLlm()` seam; non-renderable entries drop at the projection boundary. | | `session/runtime-state` | 13 | ✓ | `runtime-state.test.ts` — direct flattened-shape invariant for defaults, last-writer-wins runtime posture, mentions/world/lifecycle slots, and non-linear transcript rejection. | | `session/affordances` | 1 | ✓ | `affordances.test.ts` — gap-driven legality + default-on-switch derivation tested directly. Legal options are a menu projection over capability-readiness; omitted options are not capability refusals (I31-L). | -| `session/capability-readiness` | 1 | ✓ | D74-L/D75-L tracer gate, not a reusable DTO. `capability-readiness.test.ts` locks the explicit capability→node-kind map, proceed / low-epistemic / negotiate outcomes, no-refusal invariant, loud failure when the gap register lacks a required kind, same-kind discrimination through `question`, and live presence-coverage flip. `session/affordances` now consumes it for axis-option legality. **D86-L: capability-readiness gates AUTO axis menus (`strategy`/`lens`) and the non-graph-write `review-for-gaps` method only — it never withholds a graph-write tool. `mutate_graph` and the review-set tools (`present_review_set`/`request_response`) are floor in elicit mode (their `commit-graph`/`generate-proposal` methods are absent from `METHOD_CAPABILITY` in `.pi/extensions/runtime/state.ts`); `negotiate` is advisory (establishment offer + epistemic scaling), proven by `state.test.ts` + the tier-2 live-boot legality test.** | +| `session/capability-readiness` | 1 | ✓ | D74-L/D75-L tracer gate, not a reusable DTO. `capability-readiness.test.ts` locks the explicit capability→node-kind map, proceed / low-epistemic / negotiate outcomes, no-refusal invariant, loud failure when the gap register lacks a required kind, same-kind discrimination through `question`, and live presence-coverage flip. `session/affordances` now consumes it for axis-option legality. **D86-L: capability-readiness gates AUTO axis menus (`strategy`/`lens`) and the non-graph-write `review-for-gaps` method only — it never withholds a graph-write tool. `mutate_graph` and the review-set tools (`present_review_set`/`request_response`) are floor in elicit mode (their `commit-graph`/`generate-proposal` methods are absent from `METHOD_CAPABILITY` in `.pi/extensions/agent-runtime/runtime/state.ts`); `negotiate` is advisory (establishment offer + epistemic scaling), proven by `state.test.ts` + the tier-2 live-boot legality test.** | | `session/readiness-estimate` | — | ✓ | D45-L soft per-band coverage rollup over `ElicitationGap[]`; UI-only and gates nothing. `readiness-estimate.test.ts` locks every-band shape, empty-band zero, importance-weighted mean, honest regression, no grade imports, and no legality-path imports. | | `session/runtime-policy` | 4 | ○ | Policy/definitions data, not a DTO transform. Gap-driven legality is guarded via `affordances.test.ts`; no runtime grade table remains. | | `session/assistant-visible-watermark` | 2 | ✓ | Carrier projection over the authoritative `continuity-entry-classifier` watermark set. Unit tests guard seed/overview/own-mutation/`worldUpdate` carriers, narrow-read exclusion, and cross-spec failure. | diff --git a/src/projections/graph/commit-result.ts b/src/projections/graph/commit-result.ts index 172fa23dd..72351d695 100644 --- a/src/projections/graph/commit-result.ts +++ b/src/projections/graph/commit-result.ts @@ -10,7 +10,7 @@ * * Used by: * - renderers/graph/commit-result.ts - * - .pi/extensions/graph/index.ts via mutate_graph tool results + * - .pi/extensions/brunch-data/graph/index.ts via mutate_graph tool results */ export {}; diff --git a/src/projections/graph/overview.ts b/src/projections/graph/overview.ts index 15ebb79e9..461930a8d 100644 --- a/src/projections/graph/overview.ts +++ b/src/projections/graph/overview.ts @@ -10,7 +10,7 @@ * * Used by: * - renderers/graph/overview.ts - * - .pi/extensions/graph/index.ts via graph overview tool results + * - .pi/extensions/brunch-data/graph/index.ts via graph overview tool results * - .pi/extensions/prompting.ts via pushed graph context */ diff --git a/src/projections/session/__tests__/readiness-estimate.test.ts b/src/projections/session/__tests__/readiness-estimate.test.ts index 25d499852..52ff0cf5a 100644 --- a/src/projections/session/__tests__/readiness-estimate.test.ts +++ b/src/projections/session/__tests__/readiness-estimate.test.ts @@ -71,7 +71,7 @@ describe('readiness estimate projection', () => { for (const relativePath of [ '../runtime-policy.ts', '../affordances.ts', - '../../../.pi/extensions/runtime/state.ts', + '../../../.pi/extensions/agent-runtime/runtime/state.ts', ]) { const source = readFileSync(fileURLToPath(new URL(relativePath, import.meta.url)), 'utf8'); expect(source).not.toMatch(/readiness-estimate|readinessEstimate/); diff --git a/src/renderers/README.md b/src/renderers/README.md index 7e0c853e0..80f193024 100644 --- a/src/renderers/README.md +++ b/src/renderers/README.md @@ -73,8 +73,8 @@ Ledger statuses: Tool-owned render targets are ledgered by their durable renderer row when they use `src/renderers/` directly: -- Graph tools: `read_graph` overview/list modes are covered by `graph/graph-slice` (G-D) and neighborhood mode by `graph/node-neighborhood` (G-C); `mutate_graph` currently formats command outcomes in the graph extension adapter, not a reusable renderer row. **Gap:** `read_graph` `related` mode is rendered by `formatRelatedNodesResult` in `.pi/extensions/graph/command-adapter.ts` (not a `renderers/` row) and still emits structural leaks (`-[category/direction]->` arrows, raw `#id`, `plane/kind`) — it must migrate onto the prose vocabulary and relocate into `renderers/` (tracked under `renderer-golden-coverage` in `memory/PLAN.md`). -- Elicitation-gap tools: `read_elicitation_gaps` and `update_elicitation_gaps` format in `src/.pi/extensions/elicitation`; no renderer row is admitted until a second consumer or drift-prone reusable surface appears. +- Graph tools: `read_graph` overview/list modes are covered by `graph/graph-slice` (G-D) and neighborhood mode by `graph/node-neighborhood` (G-C); `mutate_graph` currently formats command outcomes in the graph extension adapter, not a reusable renderer row. **Gap:** `read_graph` `related` mode is rendered by `formatRelatedNodesResult` in `.pi/extensions/brunch-data/graph/command-adapter.ts` (not a `renderers/` row) and still emits structural leaks (`-[category/direction]->` arrows, raw `#id`, `plane/kind`) — it must migrate onto the prose vocabulary and relocate into `renderers/` (tracked under `renderer-golden-coverage` in `memory/PLAN.md`). +- Elicitation-gap tools: `read_elicitation_gaps` and `update_elicitation_gaps` format in `src/.pi/extensions/brunch-data/elicitation`; no renderer row is admitted until a second consumer or drift-prone reusable surface appears. - Context tools: `read_workspace_context` is covered by `workspace/workspace-context`; `read_specification_context` is covered by `specification/specification-context`; `read_session_context` is covered by `session/runtime-frame`. - Structured-exchange tools: `present_*` and `request_*` rows are the exchange renderer family above; TUI presentation currently delegates to each tool's `renderResult` adapter over the same markdown text, so lock mechanism follows the renderer row unless a component-specific display diverges. - Base file floor (`read`, `grep`, `find`, `ls`) and dev-only tools (`brunch_session_query`, `brunch_introspect_query`) are Pi/dev tool surfaces, not renderer-frontier rows. @@ -87,6 +87,6 @@ Provider-visible strings composed outside `src/renderers/` carry the same drift | --- | --- | --- | --- | | `kickTurnMessage` | `src/session/originate-assistant-turn.ts` | ◐ partial | Origination follow-up Card 3 locks D78-L wording; not part of renderer coverage cards. | | Mention-staleness hints | `src/session/mention-ledger.ts` / turn-boundary reconciler | ○ review-only | No renderer row until copy changes or drift appears. | -| Session lifecycle notices | `src/.pi/extensions/session/lifecycle.ts` | ○ review-only | Keep owner-local unless promoted by a wording bug. | +| Session lifecycle notices | `src/.pi/extensions/session-hooks/session/lifecycle.ts` | ○ review-only | Keep owner-local unless promoted by a wording bug. | | Compaction copy / anchor rationale | `src/.pi/extensions/compaction/index.ts` | ○ review-only | Contract prose, not a renderer row. | | Seed framing | `src/session/context-seed.ts` | ◐ partial | Covered indirectly through origination/context tests today; promote only if wording churn continues. | diff --git a/src/renderers/graph/commit-result.ts b/src/renderers/graph/commit-result.ts index 04b7fb7d2..539c01f36 100644 --- a/src/renderers/graph/commit-result.ts +++ b/src/renderers/graph/commit-result.ts @@ -8,7 +8,7 @@ * - markdown summary for success or structural diagnostics * * Replaces/adapts: - * - .pi/extensions/graph/command-adapter.ts commit result formatting + * - .pi/extensions/brunch-data/graph/command-adapter.ts commit result formatting */ export {}; diff --git a/src/session/README.md b/src/session/README.md index 892252f5a..160730102 100644 --- a/src/session/README.md +++ b/src/session/README.md @@ -66,7 +66,7 @@ plus the coordination logic for workspace/spec/session lifecycle. - **Turn-boundary choreography** — write-side seam for the assistant-visible watermark, `worldUpdate`, mention staleness, and honest assistant origination. `prepare-next-turn.ts` owns the single pre-turn continuity writer; Pi lifecycle - hooks adapt it through `.pi/extensions/session/lifecycle.ts`, and + hooks adapt it through `.pi/extensions/session-hooks/session/lifecycle.ts`, and `before_provider_request` is a guard-only check. `start-assistant-turn.ts` owns the origination decision and context seed entries; `context-seed.ts` composes the seed's provider-visible payload (spec overview + top-ranked @@ -106,8 +106,8 @@ directly instead of growing a wrapper. | `cwd_inventory` | `workspace/cwd-inventory.ts` (`inspectWorkspaceCwdInventory`) | `read_workspace_context`, `renderers/workspace/workspace-context.ts` | Workspace-owned direct PULL read. The typed inventory already matches the tool/renderer seam, so no `projections/workspace/workspace-context` wrapper survives. | | `workspace_overview` | `workspace-overview-context.ts` (`inspectWorkspaceOverview`) | `read_workspace_context`, origination seed context, `renderers/workspace/workspace-context.ts` | Session-side composition over graph specs and canonical session files. Same no-wrapper rationale as `cwd_inventory`: the source shape is already the consumer shape. | | `workspace_session_state` | `WorkspaceSessionCoordinator` (`WorkspaceSessionState`) | `projections/workspace/workspace-state.ts`, `chromeStateForWorkspace`, app/rpc/web workspace flows | Source union owned by the coordinator. Downstream code may flatten it, but the coordinator remains the authority for the narrow chrome snapshot and status-variant field set. | -| `agent_runtime_vocab` | `schema/kinds.ts`, `schema/tool-names.ts` | `runtime-state.ts`, `projections/session/runtime-policy.ts`, `projections/session/affordances.ts`, `.pi/extensions/runtime/state.ts`, `.pi/extensions/orchestrator-stub/` | Pure vocabulary leaf for runtime axes, agent-role ids, and shared Brunch tool-name constants; imports nothing and mirrors D73-L's graph taxonomy direction on the session side. | -| `agent_runtime_state` | `latestValidBrunchAgentStateEntryData` and transcript-backed runtime-state facts in `session/runtime-state.ts` | `projections/session/runtime-state.ts`, `projections/session/affordances.ts`, `.pi/extensions/runtime/` | Transcript-backed source read. Projection/policy layers derive from these facts rather than storing parallel hidden runtime memory. | +| `agent_runtime_vocab` | `schema/kinds.ts`, `schema/tool-names.ts` | `runtime-state.ts`, `projections/session/runtime-policy.ts`, `projections/session/affordances.ts`, `.pi/extensions/agent-runtime/runtime/state.ts`, `.pi/extensions/agent-runtime/orchestrator-stub/` | Pure vocabulary leaf for runtime axes, agent-role ids, and shared Brunch tool-name constants; imports nothing and mirrors D73-L's graph taxonomy direction on the session side. | +| `agent_runtime_state` | `latestValidBrunchAgentStateEntryData` and transcript-backed runtime-state facts in `session/runtime-state.ts` | `projections/session/runtime-state.ts`, `projections/session/affordances.ts`, `.pi/extensions/agent-runtime/runtime/` | Transcript-backed source read. Projection/policy layers derive from these facts rather than storing parallel hidden runtime memory. | ## Runtime affordance coverage ledger @@ -139,13 +139,13 @@ schema, and the product-state-gated rows must stay explicit deferred tripwires. - Cwd project identity, pure cwd inventory, and `.brunch/workspace.json` persistence — those live in `workspace/`. - Graph state, CommandExecutor, graph queries — those live in `graph/`. -- Prompt composition, pushed seed context building — those live in `.pi/extensions/system-prompts/` (manifest/legality policy in `.pi/extensions/runtime/`). +- Prompt composition, pushed seed context building — those live in `.pi/extensions/agent-runtime/system-prompts/` (manifest/legality policy in `.pi/extensions/agent-runtime/runtime/`). - Pi extension registration — those live in `.pi/extensions/`. ## Imported by -- `.pi/extensions/system-prompts/seed/` — for workspace/graph pushed-context reads. -- `.pi/extensions/context/` — for direct workspace overview reads; pure cwd inventory comes from `workspace/`. +- `.pi/extensions/agent-runtime/system-prompts/seed/` — for workspace/graph pushed-context reads. +- `.pi/extensions/brunch-data/context/` — for direct workspace overview reads; pure cwd inventory comes from `workspace/`. - `projections/session/` — for reusable transcript-context DTO projection. - `projections/workspace/` — for reusable workspace-state DTO projection. - `renderers/session/` — for reusable transcript markdown rendering. diff --git a/src/session/agent-context-seed.ts b/src/session/agent-context-seed.ts index 0ca754309..6a944eeb0 100644 --- a/src/session/agent-context-seed.ts +++ b/src/session/agent-context-seed.ts @@ -5,7 +5,7 @@ * Owns the per-turn pushed context blocks the agent receives each turn: the * selected-workspace seed and the selected-spec graph seed. This is session/ * world state rendered for the agent, distinct from system-prompt assembly - * (`.pi/extensions/system-prompts/compose.ts`), which only splices these blocks + * (`.pi/extensions/agent-runtime/system-prompts/compose.ts`), which only splices these blocks * into the prompt frame. Keeping composition here means cycling operational * modes — which swaps the agent role and therefore the system prompt — does not * re-own context derivation: the prompt layer consumes a bundle it does not @@ -14,7 +14,7 @@ * * Input: selected spec/workspace/session + gaps + already-read graph slice + lens * Output: rendered context block strings (lossy, bounded) - * Used by: `.pi/extensions/system-prompts` (before_agent_start) via composeAgentContextSeed + * Used by: `.pi/extensions/agent-runtime/system-prompts` (before_agent_start) via composeAgentContextSeed */ import type { GraphSlice } from '../graph/queries.js'; diff --git a/src/session/schema/README.md b/src/session/schema/README.md index 1b95ed8a4..0c808010f 100644 --- a/src/session/schema/README.md +++ b/src/session/schema/README.md @@ -12,7 +12,7 @@ Drizzle-free, Pi-free closed vocabulary for session-domain state. ## Does NOT own - Runtime-state transcript entry parsing or append helpers — those stay in `src/session/runtime-state.ts`. -- Runtime policy, legal option derivation, or prompt-resource manifests — those stay in `src/projections/session/` and `src/.pi/extensions/runtime/`. +- Runtime policy, legal option derivation, or prompt-resource manifests — those stay in `src/projections/session/` and `src/.pi/extensions/agent-runtime/runtime/`. - Graph vocabulary — that remains in `src/graph/schema/kinds.ts`. ## Dependency rule From 03c16a95e0943c9acfc0f0be90eaec81b16cc24b Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Thu, 25 Jun 2026 17:34:29 +0200 Subject: [PATCH 09/54] tool schema type hardening --- .../brunch-data/graph/tool-schemas.ts | 70 ++----------------- src/graph/command-executor.ts | 2 +- src/graph/index.ts | 1 + 3 files changed, 6 insertions(+), 67 deletions(-) diff --git a/src/.pi/extensions/brunch-data/graph/tool-schemas.ts b/src/.pi/extensions/brunch-data/graph/tool-schemas.ts index 7061b30e7..cb4c08be3 100644 --- a/src/.pi/extensions/brunch-data/graph/tool-schemas.ts +++ b/src/.pi/extensions/brunch-data/graph/tool-schemas.ts @@ -28,6 +28,7 @@ import { type EdgeDirection, type GraphVisibility, type NodeKindWithFormDetail, + type RoleNamedEdgeDraftOf, } from '../../../../graph/index.js'; const ALL_KINDS = [...INTENT_KINDS, ...ORACLE_KINDS, ...DESIGN_KINDS, ...PLAN_KINDS] as const; @@ -52,72 +53,9 @@ export type ToolMutateCreateNodeOp = { readonly source?: string | undefined; readonly detail?: unknown; }; -export type ToolMutateCreateEdgeOp = - | { - readonly op: 'create_edge'; - readonly category: 'dependency'; - readonly dependency: ToolEdgeRef; - readonly dependent: ToolEdgeRef; - readonly rationale?: string | undefined; - } - | { - readonly op: 'create_edge'; - readonly category: 'witness'; - readonly oracle: ToolEdgeRef; - readonly claim: ToolEdgeRef; - readonly stance: 'for' | 'against'; - readonly rationale?: string | undefined; - } - | { - readonly op: 'create_edge'; - readonly category: 'rationale'; - readonly support: ToolEdgeRef; - readonly claim: ToolEdgeRef; - readonly stance: 'for' | 'against'; - readonly rationale?: string | undefined; - } - | { - readonly op: 'create_edge'; - readonly category: 'realization'; - readonly abstract: ToolEdgeRef; - readonly concrete: ToolEdgeRef; - readonly rationale?: string | undefined; - } - | { - readonly op: 'create_edge'; - readonly category: 'refinement'; - readonly abstract: ToolEdgeRef; - readonly concrete: ToolEdgeRef; - readonly rationale?: string | undefined; - } - | { - readonly op: 'create_edge'; - readonly category: 'exclusion'; - readonly boundary: ToolEdgeRef; - readonly subject: ToolEdgeRef; - readonly rationale?: string | undefined; - } - | { - readonly op: 'create_edge'; - readonly category: 'composition'; - readonly whole: ToolEdgeRef; - readonly part: ToolEdgeRef; - readonly rationale?: string | undefined; - } - | { - readonly op: 'create_edge'; - readonly category: 'cross_reference'; - readonly a: ToolEdgeRef; - readonly b: ToolEdgeRef; - readonly rationale?: string | undefined; - } - | { - readonly op: 'create_edge'; - readonly category: 'supersession'; - readonly successor: ToolEdgeRef; - readonly predecessor: ToolEdgeRef; - readonly rationale?: string | undefined; - }; +export type ToolMutateCreateEdgeOp = { + readonly op: 'create_edge'; +} & RoleNamedEdgeDraftOf; export type ToolMutateGraphOp = ToolMutateCreateNodeOp | ToolMutateCreateEdgeOp; export interface ToolMutateGraphParams { readonly createBasis?: 'explicit' | 'implicit' | undefined; diff --git a/src/graph/command-executor.ts b/src/graph/command-executor.ts index c1a1c809e..ca9d7362c 100644 --- a/src/graph/command-executor.ts +++ b/src/graph/command-executor.ts @@ -77,7 +77,7 @@ export type { StructuralIllegal, } from './command-executor/graph-mutation-types.js'; export { normalizeRoleNamedEdgeDraft } from './command-executor/role-named-edge-draft.js'; -export type { RoleNamedEdgeDraft } from './command-executor/role-named-edge-draft.js'; +export type { RoleNamedEdgeDraft, RoleNamedEdgeDraftOf } from './command-executor/role-named-edge-draft.js'; export type { AcceptReviewSetDryRunResult, AcceptReviewSetInput, diff --git a/src/graph/index.ts b/src/graph/index.ts index 6a708ad39..f85fbd4d0 100644 --- a/src/graph/index.ts +++ b/src/graph/index.ts @@ -95,6 +95,7 @@ export type { NodePatch, ResolveReconNeedResult, RoleNamedEdgeDraft, + RoleNamedEdgeDraftOf, SpecRecord, StructuralIllegal, } from './command-executor.js'; From 5a814eafa48d58fd7eab459a29b488b4385f474e Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Thu, 25 Jun 2026 18:08:56 +0200 Subject: [PATCH 10/54] src/agents/ centralization refactor step 1 --- memory/REFACTOR.md | 103 ++++++++++++++++++ memory/SPEC.md | 2 +- src/.pi/README.md | 8 +- src/.pi/agents/README.md | 16 +-- .../agent-runtime/runtime/state.test.ts | 4 +- .../extensions/agent-runtime/runtime/state.ts | 5 +- .../system-prompts/prompt-skills.ts | 13 +-- src/.pi/extensions/subagents/agents.ts | 4 +- src/.pi/skills/README.md | 6 +- src/README.md | 21 ++-- src/agents/README.md | 29 +++++ src/agents/__tests__/registry.test.ts | 31 ++++++ src/agents/registry.ts | 37 +++++++ src/projections/session/runtime-policy.ts | 5 +- 14 files changed, 240 insertions(+), 44 deletions(-) create mode 100644 memory/REFACTOR.md create mode 100644 src/agents/README.md create mode 100644 src/agents/__tests__/registry.test.ts create mode 100644 src/agents/registry.ts diff --git a/memory/REFACTOR.md b/memory/REFACTOR.md new file mode 100644 index 000000000..3a9c2721e --- /dev/null +++ b/memory/REFACTOR.md @@ -0,0 +1,103 @@ +## Problem Statement + +Brunch currently has no single owner for **LLM context ingress**. Agent-facing text is spread by adapter or historical layer: prompt bodies live under the Pi surface, prompt composition and prompt-resource legality live under Pi extension internals, pushed context seed composition lives under session modules, reusable context renderers live under the generic renderer seam, and several tool-result texts are still formatted inside tool adapters. + +That makes it hard to review or test the actual bytes and vocabulary entering the model. A developer asking “what does the agent see?” has to traverse Pi extensions, session helpers, renderers, graph adapters, and skill metadata. The current topology answers “which runtime adapter consumes this?” better than “who controls agent context?” + +```pseudo tree +current LLM-context ownership +├── Pi markdown bodies +│ └── .pi agent body resources +├── Pi prompt extension internals +│ ├── foreground prompt composition +│ ├── prompt-resource manifest rendering +│ └── prompt-resource/tool legality helper +├── session helpers +│ ├── per-turn pushed context seed +│ └── origination/session-entry context seed +├── generic renderers +│ ├── graph/spec/workspace/session context text +│ └── exchange markdown text +└── adapter-local formatters + ├── graph mutation/read-related diagnostics + ├── elicitation agenda text + └── reconciliation agenda text +``` + +## Solution + +Create `src/agents/` as the central, Pi-independent owner of agent prompt bodies, Brunch prompt-resource skills, prompt composition policy, and all Brunch-authored LLM-context renderings. Pi extensions, session origination code, probes, and tools become callers: they gather data, then ask `src/agents/` to produce the model-facing text. + +Keep tool schemas beside the tools. Schemas are the adapter contract. But tool **content**, session-entry **content**, and prompt **content** should be developed and golden-tested in the agent context home. + +```pseudo tree +desired LLM-context ownership +src/agents/ +├── prompts/ +│ ├── foreground agent bodies +│ └── background agent bodies with spawn metadata where needed +├── skills/ +│ ├── strategies/ +│ ├── lenses/ +│ └── methods/ +├── runtime/ +│ ├── foreground prompt composition +│ ├── prompt-resource manifest loading/rendering +│ ├── prompt-resource/tool legality projection +│ └── agent body loading/path registry +└── contexts/ + ├── primitives: markdown, section, tree, toon helpers for agent context + ├── graph: overview, neighborhood, related, mutation-result text + ├── workspace/specification/session: context blocks and runtime frames + ├── exchanges: present/request markdown + ├── elicitation: gap agenda/update text + ├── reconciliation: need agenda/update text + └── seeds: per-turn and origination/session-entry context composition + +callers after refactor +├── Pi extensions: register tools/hooks, gather deps, call src/agents/* for text +├── session origination: gathers graph/workspace/gaps, calls src/agents/contexts/seeds +├── dev/probes: use agent-context renderers for probe-visible artifacts +└── product CLI/human renderers: remain outside unless the text enters LLM context +``` + +## Commits + +1. ✓ Add the new `src/agents` topology and README as an empty central owner, and introduce central path/registry helpers that still point at the existing prompt and skill homes. +2. Move agent prompt bodies into the new prompt home and update foreground/body loading, background subagent loading, build asset copying, and prompt-body tests without changing prompt bytes. +3. Move Brunch prompt-resource skills into the new skills home and update manifest loading, build asset copying, resource-location snapshots, and skill topology docs without changing skill bytes. +4. Move foreground prompt composition and prompt-resource legality code into the new runtime home; leave Pi extension code as a thin hook adapter that imports the central composer. +5. Move per-turn pushed context composition and origination/session-entry seed composition into the new context seed home; update session and app callers to import from the central agent context layer. +6. Move reusable LLM-facing context renderers into the new context home, keeping product-only/human-only renderers outside unless they are deliberately agent-visible. +7. Promote adapter-local LLM text formatting into the new context home: graph mutation result text, related-node text, elicitation agenda/update text, and reconciliation agenda/update text. +8. Consolidate golden/preview tests for prompt composition and agent context renderers under the new agent tree, preserving existing semantic invariants while making “what enters the model” reviewable in one place. +9. Add a boundary guard that prevents Pi extension adapters from owning Brunch-authored tool/session/prompt content text, while explicitly allowing tool schemas, labels, descriptions, and prompt snippets to remain adapter-owned. +10. Reconcile topology READMEs, SPEC/PLAN references, build scripts, and direct-import docs so the new owner is canonical and the old `.pi`/`renderers` ownership claims are retired. + +## Decisions + +- Build or modify a new `agents` module as the central LLM-context ingress owner. +- Keep the name `skills` for Brunch prompt-resource skill bodies, not `resources`. +- Keep Pi extension modules as runtime adapters only: registration, hook binding, dependency gathering, and tool schemas stay there; Brunch-authored model-facing content moves out. +- Keep tool schemas near their tools because they are provider/adapter contracts, even though they are visible to the model. +- Split generic rendering by audience: agent-visible context text moves to the agent context home; product-only human renderers stay outside unless later made model-visible. +- Background agent metadata may remain frontmatter in prompt markdown, but discovery remains code-owned through explicit registries. +- Topology READMEs to update or retire include the Pi surface README, Pi agents README, Pi skills README, Pi extensions README, renderer README, session README, and root source README. + +## Testing Decisions + +- Existing prompt composition previews and renderer goldens are the core safety net; move or re-anchor them rather than rewriting expected content casually. +- First priority is byte stability for existing prompt bodies, skill bodies, and model-facing render outputs. +- Keep semantic invariants for graph code rendering, no raw structural leaks where already locked, readiness estimate parity, prompt-resource legality, and active-tool/prompt-manifest alignment. +- Add one boundary/architecture test after the moves: extension adapters may register schemas and call central renderers, but should not define Brunch-authored result/session/prompt body text locally. +- Run the full gate after each commit-sized move because this is import/topology-heavy and build asset copying is part of product behavior. + +## Out of Scope + +- Changing graph ontology vocabulary, node kinds, edge categories, readiness bands, or detail schemas. +- Rewriting prompt/skill prose for quality beyond path-sensitive updates required by the move. +- Changing tool schemas, tool availability, runtime policy behavior, or subagent spawnability. +- Changing Pi sealed-profile behavior or ambient discovery rules. +- Building a new renderer framework or generalized preview harness beyond relocating existing goldens and adding the boundary guard. +- Moving product-only CLI text unless it becomes agent-visible. +- Changing transcript debug rendering unless a later slice decides transcript reports are agent context rather than human/probe artifacts. diff --git a/memory/SPEC.md b/memory/SPEC.md index a1d6aa11b..8eebd7324 100644 --- a/memory/SPEC.md +++ b/memory/SPEC.md @@ -133,7 +133,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c - **D40-L — Runtime state is transcript-backed Brunch session-agent state, not hidden extension memory.** The architectural commitment is that Brunch session-agent posture remains transcript-backed Pi JSONL state rather than hidden extension memory: posture switches are user/system authority, the foreground session agent is derived from `op_mode`, child axes default back to `AUTO` when invalidated, and tool authority remains `op_mode`-gated rather than prompt-composition-owned. Runtime-state entries are Pi JSONL state-change facts, not assistant/user chat content: init and switch entries should render, when visible, as dim non-chat state rows analogous to Pi thinking/model-change rows, and must not enter LLM context as ordinary conversation. Current materialized state lives in [`src/session/README.md`](src/session/README.md), [`src/projections/README.md`](src/projections/README.md), [`src/.pi/README.md`](src/.pi/README.md), [`src/.pi/agents/README.md`](src/.pi/agents/README.md), and [`src/projections/session/runtime-policy.ts`](src/projections/session/runtime-policy.ts). Depends on: D17-L, D23-L, D25-L, D39-L, D58-L, D59-L. Refined by: D85-L (the AUTO-able objective axes reduce to `strategy` + `lens`; `goal` is dropped from the runtime-state axis set and inlined into the agent role prompt). Supersedes: mode-only vocabulary, extension-local mutable state as authority, storing the foreground role as independent session state, the "runtime bundle / role preset" as one knob deriving model/thinking/resources, and binding prompt-resource location to `src/.pi/context/`. - **D34-L — Command containment separates visibility suppression from effect blocking.** Current Pi extension seams can hide unsupported slash suggestions with autocomplete wrapping and can cancel branch/session effects through lifecycle hooks, but they cannot strictly suppress exact interactive built-in commands before `InteractiveMode` dispatches them. Brunch-owned commands must use product-specific names and route writes through Brunch handlers/`CommandExecutor`; extension command collisions are not an override mechanism. Strict built-in command/keybinding policy is a Pi upstream/API ask, while POC safety relies on hiding generic affordances, blocking dangerous effects (`/fork`, `/clone`, raw session replacement), allowing native `/tree` as inspection/navigation, and failing fast on branched transcripts. Brunch's command-policy code should live in `src/.pi/extensions/commands/policy.ts`, merging branch/session-effect blocking with any product command allow/deny behavior instead of preserving a branch-only module. Depends on: D2-L, D24-L, A18-L. Supersedes: treating extension `input` handlers or command-name collisions as built-in command allowlisting. - **D35-L — Dynamic TUI chrome is a Brunch projection wrapper over Pi UI primitives.** The architectural commitment is that downstream TUI affordances call one Brunch-owned renderer (`renderBrunchChrome` or its successor) with a single activated product-state value rather than scattering raw `ctx.ui.setHeader` / `setFooter` / `setWidget` / title / working-indicator calls; the wrapper is stateless projection over canonical workspace/session/graph facts, never its own mutable state. Chrome is a project-first shell surface with selected-spec context — project name labels the cwd container, spec title labels the selected graph, session label distinguishes transcript instances — and a session label must never replace spec identity or graph truth. Chrome must not consume the status-key namespace for its own summary (`ctx.ui.setStatus` stays a lateral channel for other extensions), must not advertise unwired affordances, and RPC clients must rely only on surfaces Pi actually emits (header/footer/working-indicator are TUI-only in current Pi RPC mode). Current chrome state shape, render surfaces, telemetry/refresh, startup-header behavior, and status-key filtering live in [`src/.pi/extensions/chrome/README.md`](src/.pi/extensions/chrome/README.md); launch/activation wiring lives in [`src/.pi/extensions/README.md`](src/.pi/extensions/README.md). Depends on: D2-L, D21-L, D34-L, A18-L. Supersedes: treating Pi UI methods as direct downstream affordance APIs, rendering placeholder session state such as `unbound` after a session is activated, consuming the status-key namespace for chrome's own static summary, using spec title as the default session label, or allowing two unchanged Brunch-created default names to collide in one cwd, and the earlier resume/open-launches-stay-quiet clause (superseded 2026-06-11: the shipped, test-locked behavior headers every non-cancel activation). -- **D52-L — Source topology targets `src/{app, workspace, scripts, .pi, db, graph, session, projections, renderers, rpc, web}` with directed layer dependencies.** Reusable projection and rendering modules live in top-level `src/projections/` and `src/renderers/` rather than whichever domain or adapter first needed them; domain layers (`graph/`, `session/`) and the reusable `projections/` / `renderers/` layers must not import adapters, transports, app entrypoints, or web code; `graph/` is the only layer that imports `db/`, plus the single sanctioned `db/`→`graph/schema/kinds.ts` taxonomy edge (D73-L). The concrete per-directory ownership, layout sketch, and full import matrix are owned by [`src/README.md`](src/README.md). Depends on: D2-L, D4-L, D39-L, D40-L. Refined by: D73-L. Supersedes: scattering session domain files at `src/` root; treating Pi-only agents as a host-independent top-level `src/.pi/` layer; nesting prompt composition under `src/.pi/context/`; treating reusable `project` / `format` helpers as owned by whichever adapter first needed them. +- **D52-L — Source topology targets `src/{app, workspace, scripts, agents, .pi, db, graph, session, projections, renderers, rpc, web}` with directed layer dependencies.** Reusable projection and rendering modules live in top-level `src/projections/` and `src/renderers/` rather than whichever domain or adapter first needed them; `src/agents/` is the Pi-independent owner for Brunch-authored LLM context ingress (currently the central registry for bundled agent-body and prompt-resource skill file paths, with later content moves tracked by the LLM-context refactor; the `projections/session/runtime-policy.ts` registry import is a temporary migration edge until runtime policy moves under `agents/runtime/`); domain layers (`graph/`, `session/`) and the reusable `projections` / `renderers` / `agents` layers must not import adapters, transports, app entrypoints, or web code; `graph/` is the only layer that imports `db/`, plus the single sanctioned `db/`→`graph/schema/kinds.ts` taxonomy edge (D73-L). The concrete per-directory ownership, layout sketch, and full import matrix are owned by [`src/README.md`](src/README.md). Depends on: D2-L, D4-L, D39-L, D40-L. Refined by: D73-L. Supersedes: scattering session domain files at `src/` root; treating Pi-only agents as a host-independent top-level `src/.pi/` layer; nesting prompt composition under `src/.pi/context/`; treating reusable `project` / `format` helpers as owned by whichever adapter first needed them; treating `.pi/agents` and `.pi/skills` as the long-term conceptual owner for Brunch-authored model-facing content. - **D73-L — Domain enum taxonomy is owned by drizzle-free schema leaves; persistence and adapters are consumers, not the source.** The closed enum `const` arrays that define graph vocabulary — node kinds (`INTENT_KINDS`, `ORACLE_KINDS`, `DESIGN_KINDS`, `PLAN_KINDS`), `NODE_PLANES` (`intent`/`oracle`/`design`/`plan`), `NODE_BASES`, `EDGE_CATEGORIES`, `EDGE_STANCES`, `READINESS_BANDS`, `LENS_AFFINITIES`, `GAP_DISPOSITIONS`, and `GAP_PREDICATE_KINDS` — live in `graph/schema/kinds.ts`, a pure constants leaf that imports nothing (no drizzle, no `graph/atoms`). Both `db/schema.ts` (for `text({ enum })` column constraints, including the previously-inlined `plane` columns) and `graph/` domain modules import the arrays from this leaf; `graph/index.ts` re-exports them from the leaf so non-graph layers still avoid importing `db/` directly (I26-L). Session runtime axis vocabulary mirrors the same ownership direction in `session/schema/kinds.ts`: that leaf imports nothing and owns the `op_mode`, agent-role, `strategy`, `lens`, `auto`, and display-only planned mode choices consumed by `session/runtime-state.ts`, `projections/session/*`, and `.pi/extensions/agent-runtime/runtime/state.ts`; it deliberately contains no `goal` axis and no retired `READINESS_GRADES`. Derivations stay where they are read: `NODE_KIND_METADATA`, `formatGraphNodeCode`, `parseGraphNodeCode`, and `intentKindCategory` remain in `graph/schema/nodes.ts` (D62-L). The motivating defect: because `db/schema.ts` eagerly evaluates `sqliteTable(...)` and `verbatimModuleSyntax` emits even type-only imports at runtime, any value-import path from `web/` into the old taxonomy location pulled Drizzle into the browser bundle. Locating taxonomy in a drizzle-free leaf makes the `web/` build target structurally Drizzle-free (I44-L) and corrects the ownership direction so the domain, not the persistence layer, owns its vocabulary. Vocabulary migration status: `READINESS_GRADES` is retired (readiness is no longer a stored grade, D45-L), `ELICITATION_BACKLOG_STATUSES` is replaced by the `elicitation_gaps` disposition + predicate-shape enums (D65-L), and `READINESS_BANDS` stays. Depends on: D16-L, D52-L, D54-L, D62-L, D63-L, D64-L; I26-L. Supersedes: `db/schema.ts` owning the shared enum `const` arrays and the "enum literals flow outward from `db/schema.ts`" posture; the triplicated inline `['intent','oracle','design','plan']` plane literals. #### Data model & vocabulary diff --git a/src/.pi/README.md b/src/.pi/README.md index 3c75aad5b..d1f97a360 100644 --- a/src/.pi/README.md +++ b/src/.pi/README.md @@ -2,13 +2,13 @@ SPEC decisions: D25-L, D34-L, D35-L, D37-L, D39-L, D40-L, D52-L, D58-L, D59-L, D60-L, D69-L, D90-L, D91-L -This directory is Brunch's sealed Pi-harness surface. It contains agent role definitions, Brunch prompt-resource skills, product extension registrars, and reusable TUI components that run inside the embedded Pi coding-agent harness. +This directory is Brunch's sealed Pi-harness surface. It contains the current markdown files for agent role definitions and Brunch prompt-resource skills, plus product extension registrars and reusable TUI components that run inside the embedded Pi coding-agent harness. Conceptual ownership of Brunch-authored LLM context ingress is moving to `src/agents/`; this tree remains the Pi runtime adapter/file home during the migration. ## Owns -- Pi-facing agent role definitions and runtime prompt-resource skills. +- The current Pi-facing file home for agent role definitions and runtime prompt-resource skills during the `src/agents/` migration. - Pi extension registration: tools, lifecycle hooks, command handlers, autocomplete, TUI chrome, workspace dialogs, and dev-gated read-only introspection. `extensions/session/lifecycle.ts` adapts Pi session/turn hooks into one ordered Brunch session-boundary pipeline: workspace rebinding first, then continuity preparation steps. `extensions/graph/index.ts` stamps the live watermark carriers for own mutations and full graph-overview reads. -- Brunch-owned strategy/lens/method skills that the agent reads on demand after the runtime manifest advertises them. +- Brunch-owned strategy/lens/method skill files that the agent reads on demand after the runtime manifest advertises them; `src/agents/registry.ts` owns the central path registry. - Reusable Pi TUI components used by those extensions. ## Does NOT own @@ -49,7 +49,7 @@ This directory is Brunch's sealed Pi-harness surface. It contains agent role def rules: .pi/agents/ x> TypeScript imports [markdown role definitions only] .pi/skills/ x> TypeScript imports [SKILL.md resources only] - .pi/extensions/ -> .pi/agents/, .pi/components/, graph/, session/, rpc/ [adapter imports] + .pi/extensions/ -> agents/, .pi/agents/, .pi/components/, graph/, session/, rpc/ [adapter imports] .pi/extensions/ x> db/ [no direct storage] graph/, session/ x> .pi/ [domain layers never import Pi] ``` diff --git a/src/.pi/agents/README.md b/src/.pi/agents/README.md index 7db564a5a..53661ca77 100644 --- a/src/.pi/agents/README.md +++ b/src/.pi/agents/README.md @@ -4,11 +4,7 @@ SPEC decisions: D25-L, D40-L, D58-L, D85-L, D90-L, D91-L, D93-L ## Owns -The keyed agent body resources only — the markdown bodies a foreground or -background agent contributes as its system-prompt persona. Live agent definitions -use the `src/.pi/agents/{agent-name}/SYSTEM.md` convention so references can -later sit beside the body without making filesystem discovery part of product -behavior. +The current markdown file home for keyed agent body resources — the bodies a foreground or background agent contributes as its system-prompt persona. Conceptual ownership is migrating to `src/agents/`; `src/agents/registry.ts` is now the central path registry while the files still use the `src/.pi/agents/{agent-name}/SYSTEM.md` convention. ```text agents/ @@ -29,12 +25,7 @@ agents/ └── SYSTEM.md keyed background proposal/commitment review body + frontmatter ``` -This directory is **markdown-only**, like `.pi/skills/`. It carries no -TypeScript and registers no Pi hooks. Foreground metadata and agent-body -locations are code-owned in the op-mode-keyed foreground roster -(`src/projections/session/runtime-policy.ts`); background metadata is authored as -frontmatter but discovered only through the explicit -`BACKGROUND_SUBAGENT_IDS` registry in `src/.pi/extensions/subagents/agents.ts`. +This directory is **markdown-only**, like `.pi/skills/`. It carries no TypeScript and registers no Pi hooks. Foreground metadata is code-owned in the op-mode-keyed foreground roster (`src/projections/session/runtime-policy.ts`), while agent-body file locations are centralized in `src/agents/registry.ts`. Background metadata is authored as frontmatter but discovered only through the explicit `BACKGROUND_SUBAGENT_IDS` registry in `src/.pi/extensions/subagents/agents.ts`. Both project into the shared manifest type (`src/session/schema/agent-manifest.ts`), not filesystem discovery (D39-L/D90-L/D93-L). @@ -51,8 +42,7 @@ Both project into the shared manifest type ## Does NOT own -The prompt-assembly machinery that *uses* these definitions now lives with the -extension that consumes it: +The prompt-assembly machinery that *uses* these definitions now lives with the extension that consumes it; the target owner for Brunch-authored model-facing context is `src/agents/`: - **Foreground prompt composition + pushed seed contexts** — `.pi/extensions/agent-runtime/system-prompts/` (`compose.ts` emits the runtime header + gated diff --git a/src/.pi/extensions/agent-runtime/runtime/state.test.ts b/src/.pi/extensions/agent-runtime/runtime/state.test.ts index 61dc345f1..8418c2bab 100644 --- a/src/.pi/extensions/agent-runtime/runtime/state.test.ts +++ b/src/.pi/extensions/agent-runtime/runtime/state.test.ts @@ -3,6 +3,7 @@ import { fileURLToPath } from 'node:url'; import { describe, expect, it } from 'vitest'; +import { bundledAgentBodyLocation } from '../../../../agents/registry.js'; import { groundingFloorGaps } from '../../../../graph/schema/elicitation-gap-fixtures.js'; import { FOREGROUND_AGENT_ROSTER, @@ -217,8 +218,9 @@ describe('agent posture policy', () => { expect(() => manifestsForState(state, [])).toThrow(/no presence gap/); }); - it('resolves agent SYSTEM.md bodies through the code-owned runtime registry location', () => { + it('resolves agent SYSTEM.md bodies through the central agent context registry location', () => { const location = agentBodyResourceLocation('elicitor'); + expect(location).toBe(bundledAgentBodyLocation('elicitor')); expect(location).toMatch(/src\/\.pi\/agents\/elicitor\/SYSTEM\.md$/); const body = readFileSync(location, 'utf8'); expect(body).toContain('# Agent: elicitor'); diff --git a/src/.pi/extensions/agent-runtime/runtime/state.ts b/src/.pi/extensions/agent-runtime/runtime/state.ts index dcdb71330..32112fdb3 100644 --- a/src/.pi/extensions/agent-runtime/runtime/state.ts +++ b/src/.pi/extensions/agent-runtime/runtime/state.ts @@ -1,5 +1,4 @@ -import { fileURLToPath } from 'node:url'; - +import { bundledAgentBodyLocation } from '../../../../agents/registry.js'; import type { ElicitationGap } from '../../../../graph/schema/elicitation-gaps.js'; import type { CapabilityId } from '../../../../projections/session/capability-readiness.js'; import { @@ -169,5 +168,5 @@ function selectAxisResources({ } export function agentBodyResourceLocation(agentId: AgentRoleId): string { - return fileURLToPath(new URL(`../../../agents/${agentId}/SYSTEM.md`, import.meta.url)); + return bundledAgentBodyLocation(agentId); } diff --git a/src/.pi/extensions/agent-runtime/system-prompts/prompt-skills.ts b/src/.pi/extensions/agent-runtime/system-prompts/prompt-skills.ts index f24075f3e..51c495ea9 100644 --- a/src/.pi/extensions/agent-runtime/system-prompts/prompt-skills.ts +++ b/src/.pi/extensions/agent-runtime/system-prompts/prompt-skills.ts @@ -1,9 +1,12 @@ import { basename, dirname } from 'node:path'; -import { fileURLToPath } from 'node:url'; import { loadSkills, type Skill } from '@earendil-works/pi-coding-agent'; -type PromptResourceFamily = 'strategies' | 'lenses' | 'methods'; +import { + promptResourceAgentDir, + promptResourceLocation, + type PromptResourceFamily, +} from '../../../../agents/registry.js'; export interface PromptResourceManifestEntry { name: string; @@ -49,7 +52,7 @@ export function loadPromptResourceManifestEntries( const skillPaths = ids.map((id) => promptResourceLocation(family, id)); const result = loadSkills({ cwd: process.cwd(), - agentDir: fileURLToPath(new URL('../../', import.meta.url)), + agentDir: promptResourceAgentDir(), skillPaths, includeDefaults: false, }); @@ -86,10 +89,6 @@ export function skillToPromptResourceManifestEntry( }; } -function promptResourceLocation(family: PromptResourceFamily, id: string): string { - return fileURLToPath(new URL(`../../../skills/${family}/${id}/SKILL.md`, import.meta.url)); -} - function escapeXml(value: string): string { return value .replaceAll('&', '&') diff --git a/src/.pi/extensions/subagents/agents.ts b/src/.pi/extensions/subagents/agents.ts index 98342b6aa..05e607097 100644 --- a/src/.pi/extensions/subagents/agents.ts +++ b/src/.pi/extensions/subagents/agents.ts @@ -16,11 +16,11 @@ import { readFile } from 'node:fs/promises'; import { join } from 'node:path'; -import { fileURLToPath } from 'node:url'; import { Type } from 'typebox'; import { Value } from 'typebox/value'; +import { bundledAgentBodyHome } from '../../../agents/registry.js'; import type { BackgroundAgentManifest } from '../../../session/schema/agent-manifest.js'; export const BACKGROUND_SUBAGENT_IDS = ['explorer', 'researcher', 'projector', 'reviewer'] as const; @@ -141,7 +141,7 @@ export function parseSubagentMarkdown( /** Filesystem location of the unified bundled agent body home. */ export function subagentAgentsDir(): string { - return fileURLToPath(new URL('../../agents', import.meta.url)); + return bundledAgentBodyHome(); } /** diff --git a/src/.pi/skills/README.md b/src/.pi/skills/README.md index d2424bda7..09f144ec6 100644 --- a/src/.pi/skills/README.md +++ b/src/.pi/skills/README.md @@ -4,9 +4,9 @@ SPEC decisions: D25-L, D39-L, D52-L, D58-L, D59-L, D85-L ## Owns -Agent Skills-standard prompt resources the Brunch Pi session agent reads on demand after `.pi/extensions/agent-runtime/runtime/state.ts` advertises them in a runtime-filtered `` manifest. +The current file home for Agent Skills-standard prompt resources the Brunch Pi session agent reads on demand after `.pi/extensions/agent-runtime/runtime/state.ts` advertises them in a runtime-filtered `` manifest. -These are Pi-harness prompt resources, not product data models and not ambient filesystem discovery inputs. +These are Brunch-authored model-facing prompt resources, not product data models and not ambient filesystem discovery inputs. Conceptual ownership is migrating to `src/agents/`; `src/agents/registry.ts` now owns the central path registry while files still live here. ## Layout @@ -32,7 +32,7 @@ rules: .pi/skills/ x> graph mutation [guidance only] ``` -The legal set is sealed by the code-owned path list in `.pi/extensions/agent-runtime/runtime/state.ts`; adding a `SKILL.md` does not make it available until that table enumerates it. Frontmatter owns `name` and `description`; code owns axis family, legality, and location enumeration. The former `goals/` family is retired by D85-L; the elicitor objective postures are inline in `src/.pi/agents/elicitor/SYSTEM.md`. +The legal set is sealed by the code-owned path list in `.pi/extensions/agent-runtime/runtime/state.ts`; adding a `SKILL.md` does not make it available until that table enumerates it. `src/agents/registry.ts` owns current file locations. Frontmatter owns `name` and `description`; code owns axis family, legality, and location enumeration. The former `goals/` family is retired by D85-L; the elicitor objective postures are inline in `src/.pi/agents/elicitor/SYSTEM.md`. ## Prompt-resource sub-shapes diff --git a/src/README.md b/src/README.md index 00529bf27..49529c5fe 100644 --- a/src/README.md +++ b/src/README.md @@ -1,6 +1,6 @@ # src/ — Brunch source topology -Decision D52-L in `memory/SPEC.md` locks the target layout. Runtime-state projection remains a planned follow-up split under Cards 4–5 of the active topology chain. +Decision D52-L in `memory/SPEC.md` locks the target layout. The current LLM-context ingress refactor introduces `agents/` as the Pi-independent owner for Brunch-authored agent context; its registry still points at existing `.pi` prompt/skill files until the move slices land. ```text src/ @@ -8,9 +8,12 @@ src/ ├── workspace/ Cwd/package identity helpers and small workspace stores ├── scripts/ Local executable utilities │ +├── agents/ Pi-independent owner for Brunch-authored LLM context ingress +│ (currently central path registry; content moves later) +│ ├── .pi/ Sealed Pi-harness runtime surface -│ ├── agents/ Pi session-agent role prompt definitions (markdown) -│ ├── skills/ goal/strategy/lens/method resources read on demand +│ ├── agents/ current markdown body file home during migration +│ ├── skills/ current prompt-resource file home during migration │ ├── components/ reusable Pi TUI/message components │ └── extensions/ Pi registrars: tools, hooks, commands, TUI affordances │ @@ -41,9 +44,10 @@ src/ rules: graph/ -> db/ [allowed] workspace/ -> constants/ or workspace-local files only - projections/* -> graph/, session/, workspace/ [read/domain imports allowed] + projections/* -> agents/, graph/, session/, workspace/ [read/domain imports allowed; agents/ is temporary registry edge] renderers/* -> projections/, graph/, session/, workspace/ as needed for input types - .pi/ -> graph/, session/, projections/, renderers/ [Pi runtime adapters/resources] + agents/ -> .pi/agents/, .pi/skills/ [current migration registry only] + .pi/ -> agents/, graph/, session/, projections/, renderers/ [Pi runtime adapters/resources] rpc/ -> graph/, session/, projections/, renderers/ app/ -> graph/, session/, projections/, renderers/ graph/, session/ x> .pi/, rpc/, app/, web/ @@ -56,9 +60,10 @@ Rules: - `workspace/` owns cwd-scoped identity, inventory, and workspace default-state persistence. It must not import Pi, session, graph, DB, projection, renderer, adapter, transport, app, or web modules. - `graph/` imports from `db/`. No other layer imports `db/` directly. -- `.pi/` owns Pi-harness agents/resources/extensions/components. It is not just an adapter folder; it is the product's sealed Pi runtime surface. +- `agents/` owns the Brunch-authored LLM-context ingress seam. Today it centralizes the file registry for prompt bodies and prompt-resource skills; later slices move the content/rendering owners under this seam. The current `projections/session/runtime-policy.ts` import of this registry is a migration edge only: once runtime policy moves under `agents/runtime/`, projections should stop depending on `agents/`. +- `.pi/` owns Pi-harness extensions/components and temporarily hosts the existing markdown prompt/skill files while the LLM-context ingress refactor proceeds. - `.pi/extensions/` registers Pi tools/hooks/UI affordances and delegates product semantics outward. -- `.pi/agents/` owns agent role prompt definitions (markdown); runtime prompt assembly lives in `.pi/extensions/agent-runtime/system-prompts/` and the legal resource manifest in `.pi/extensions/agent-runtime/runtime/`; `.pi/skills/` owns read-on-demand markdown resources. +- `.pi/agents/` and `.pi/skills/` are current file homes, not the long-term conceptual owner, for Brunch-authored prompt bodies and read-on-demand markdown resources. - `projections/` owns reusable structured output; `renderers/` owns reusable lossy text output. - `web/` is a separate Vite build target. @@ -72,4 +77,4 @@ The old domain-local `src/{graph,session,structured-exchange}/format/` folders a Runtime-state transcript entry facts live in `session/runtime-state.ts`; reusable flattened runtime-state projection/policy now lives in `projections/session/runtime-state.ts` and `projections/session/runtime-policy.ts`. -The old `src/agents/` top-level prompt subtree has moved under `src/.pi/{agents,skills}/` because these agents/resources live only inside the Pi harness. The old `src/.pi/context/` prompt-pack subtree remains retired. +The earlier `src/agents/` top-level prompt subtree had moved under `src/.pi/{agents,skills}/`; the new `src/agents/` seam reclaims the name for Pi-independent LLM context ingress. It starts with a registry that points at the existing `.pi` files so the move can proceed byte-stably. The old `src/.pi/context/` prompt-pack subtree remains retired. diff --git a/src/agents/README.md b/src/agents/README.md new file mode 100644 index 000000000..fa901b8bd --- /dev/null +++ b/src/agents/README.md @@ -0,0 +1,29 @@ +# agents/ — Brunch agent context ingress + +SPEC decisions: D39-L, D40-L, D52-L, D60-L, D85-L, D90-L, D91-L, D93-L + +## Owns + +`src/agents/` is the Pi-independent home for Brunch-authored model-facing context. In the current migration slice it owns only the central registry for bundled agent bodies and Brunch prompt-resource skill paths; the markdown files still live under `src/.pi/` until the move slices land. + +```text +agents/ +├── README.md +├── registry.ts current path registry for bundled agent bodies and prompt-resource skills +└── __tests__/ registry/topology tests +``` + +## Boundary rules + +```pseudo +rules: + agents/registry.ts -> .pi/agents/*/SYSTEM.md [current body file locations] + agents/registry.ts -> .pi/skills/*/*/SKILL.md [current prompt-resource locations] + .pi/extensions/* -> agents/registry.ts [adapters ask for Brunch-authored context locations] + projections/session/runtime-policy.ts -> agents/registry.ts [temporary roster-location edge] + agents/ x> Pi extension hooks [no registration side effects] +``` + +## Migration note + +This directory is intentionally thin right now. It establishes the owner for LLM context ingress without moving bytes in the same slice. Later slices move prompt bodies, skills, prompt composition, seed context, runtime policy, and agent-visible renderers here; Pi extensions remain runtime adapters that register hooks/tools and call this layer. diff --git a/src/agents/__tests__/registry.test.ts b/src/agents/__tests__/registry.test.ts new file mode 100644 index 000000000..dc560146e --- /dev/null +++ b/src/agents/__tests__/registry.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, it } from 'vitest'; + +import { + BUNDLED_AGENT_BODY_IDS, + bundledAgentBodyRepoPath, + bundledAgentBodyLocation, + bundledAgentBodyHome, + promptResourceAgentDir, + promptResourceLocation, +} from '../registry.js'; + +describe('agent context registry', () => { + it('centralizes current bundled prompt and skill paths while the files still live under .pi', () => { + expect(BUNDLED_AGENT_BODY_IDS).toEqual([ + 'elicitor', + 'orchestrator', + 'explorer', + 'researcher', + 'projector', + 'reviewer', + 'pi-coder', + ]); + expect(bundledAgentBodyRepoPath('elicitor')).toBe('src/.pi/agents/elicitor/SYSTEM.md'); + expect(bundledAgentBodyLocation('reviewer')).toMatch(/src\/\.pi\/agents\/reviewer\/SYSTEM\.md$/); + expect(bundledAgentBodyHome()).toMatch(/src\/\.pi\/agents$/); + expect(promptResourceLocation('methods', 'generate-proposal')).toMatch( + /src\/\.pi\/skills\/methods\/generate-proposal\/SKILL\.md$/, + ); + expect(promptResourceAgentDir()).toMatch(/src\/\.pi$/); + }); +}); diff --git a/src/agents/registry.ts b/src/agents/registry.ts new file mode 100644 index 000000000..9d4529166 --- /dev/null +++ b/src/agents/registry.ts @@ -0,0 +1,37 @@ +import { fileURLToPath } from 'node:url'; + +export const BUNDLED_AGENT_BODY_IDS = [ + 'elicitor', + 'orchestrator', + 'explorer', + 'researcher', + 'projector', + 'reviewer', + 'pi-coder', +] as const; + +export type BundledAgentBodyId = (typeof BUNDLED_AGENT_BODY_IDS)[number]; +export type PromptResourceFamily = 'strategies' | 'lenses' | 'methods'; + +/** Current filesystem home for bundled Brunch agent markdown bodies. */ +export function bundledAgentBodyHome(): string { + return fileURLToPath(new URL('../.pi/agents', import.meta.url)); +} + +/** Repo-relative path used by manifest bodies that are read later by the Pi runtime. */ +export function bundledAgentBodyRepoPath(id: BundledAgentBodyId): string { + return `src/.pi/agents/${id}/SYSTEM.md`; +} + +export function bundledAgentBodyLocation(id: BundledAgentBodyId): string { + return fileURLToPath(new URL(`../.pi/agents/${id}/SYSTEM.md`, import.meta.url)); +} + +/** Agent directory passed to Pi's Agent Skills loader for Brunch prompt resources. */ +export function promptResourceAgentDir(): string { + return fileURLToPath(new URL('../.pi', import.meta.url)); +} + +export function promptResourceLocation(family: PromptResourceFamily, id: string): string { + return fileURLToPath(new URL(`../.pi/skills/${family}/${id}/SKILL.md`, import.meta.url)); +} diff --git a/src/projections/session/runtime-policy.ts b/src/projections/session/runtime-policy.ts index cbedc2b40..907c9ae3e 100644 --- a/src/projections/session/runtime-policy.ts +++ b/src/projections/session/runtime-policy.ts @@ -1,3 +1,4 @@ +import { bundledAgentBodyRepoPath } from '../../agents/registry.js'; import type { ElicitationGap } from '../../graph/schema/elicitation-gaps.js'; import type { BrunchAgentState, ToolPolicyId } from '../../session/runtime-state.js'; import type { ForegroundAgentManifest } from '../../session/schema/agent-manifest.js'; @@ -47,7 +48,7 @@ export const FOREGROUND_AGENT_ROSTER: Record Date: Thu, 25 Jun 2026 18:17:17 +0200 Subject: [PATCH 11/54] Move agent prompt bodies under agents --- memory/REFACTOR.md | 2 +- memory/SPEC.md | 29 +++---- ...orchestrator-tool-port--plan-check-tool.md | 4 +- package.json | 2 +- src/.pi/README.md | 15 +--- src/.pi/__tests__/architecture.test.ts | 50 +----------- .../__tests__/prompt-shape-readmes.test.ts | 9 +-- src/.pi/agents/README.md | 77 ------------------- src/.pi/extensions/README.md | 6 +- .../agent-runtime/runtime/state.test.ts | 2 +- .../elicitor--auto-floor-gaps-open.md | 2 +- .../elicitor--auto-high-coverage.md | 2 +- .../elicitor--pinned-strategy-lens.md | 2 +- .../elicitor--pushed-context.md | 2 +- .../system-prompts/__tests__/compose.test.ts | 12 +-- src/.pi/extensions/subagents/README.md | 8 +- src/.pi/extensions/subagents/agents.ts | 2 +- src/README.md | 15 ++-- src/agents/README.md | 11 +-- src/agents/__tests__/registry.test.ts | 8 +- src/agents/prompts/README.md | 39 ++++++++++ .../prompts/__tests__/prompt-bodies.test.ts | 65 ++++++++++++++++ .../prompts}/elicitor/SYSTEM.md | 0 .../prompts}/explorer/SYSTEM.md | 0 .../prompts}/orchestrator/SYSTEM.md | 0 .../prompts}/pi-coder/SYSTEM.md | 0 .../prompts}/projector/SYSTEM.md | 0 .../prompts}/researcher/SYSTEM.md | 0 .../prompts}/reviewer/SYSTEM.md | 0 src/agents/registry.ts | 8 +- 30 files changed, 169 insertions(+), 203 deletions(-) delete mode 100644 src/.pi/agents/README.md rename src/.pi/extensions/agent-runtime/system-prompts/{__previews__ => __snapshots__}/elicitor--auto-floor-gaps-open.md (98%) rename src/.pi/extensions/agent-runtime/system-prompts/{__previews__ => __snapshots__}/elicitor--auto-high-coverage.md (98%) rename src/.pi/extensions/agent-runtime/system-prompts/{__previews__ => __snapshots__}/elicitor--pinned-strategy-lens.md (98%) rename src/.pi/extensions/agent-runtime/system-prompts/{__previews__ => __snapshots__}/elicitor--pushed-context.md (98%) create mode 100644 src/agents/prompts/README.md create mode 100644 src/agents/prompts/__tests__/prompt-bodies.test.ts rename src/{.pi/agents => agents/prompts}/elicitor/SYSTEM.md (100%) rename src/{.pi/agents => agents/prompts}/explorer/SYSTEM.md (100%) rename src/{.pi/agents => agents/prompts}/orchestrator/SYSTEM.md (100%) rename src/{.pi/agents => agents/prompts}/pi-coder/SYSTEM.md (100%) rename src/{.pi/agents => agents/prompts}/projector/SYSTEM.md (100%) rename src/{.pi/agents => agents/prompts}/researcher/SYSTEM.md (100%) rename src/{.pi/agents => agents/prompts}/reviewer/SYSTEM.md (100%) diff --git a/memory/REFACTOR.md b/memory/REFACTOR.md index 3a9c2721e..2dd501057 100644 --- a/memory/REFACTOR.md +++ b/memory/REFACTOR.md @@ -64,7 +64,7 @@ callers after refactor ## Commits 1. ✓ Add the new `src/agents` topology and README as an empty central owner, and introduce central path/registry helpers that still point at the existing prompt and skill homes. -2. Move agent prompt bodies into the new prompt home and update foreground/body loading, background subagent loading, build asset copying, and prompt-body tests without changing prompt bytes. +2. ✓ Move agent prompt bodies into the new prompt home and update foreground/body loading, background subagent loading, build asset copying, and prompt-body tests without changing prompt bytes. 3. Move Brunch prompt-resource skills into the new skills home and update manifest loading, build asset copying, resource-location snapshots, and skill topology docs without changing skill bytes. 4. Move foreground prompt composition and prompt-resource legality code into the new runtime home; leave Pi extension code as a thin hook adapter that imports the central composer. 5. Move per-turn pushed context composition and origination/session-entry seed composition into the new context seed home; update session and app callers to import from the central agent context layer. diff --git a/memory/SPEC.md b/memory/SPEC.md index 8eebd7324..097790521 100644 --- a/memory/SPEC.md +++ b/memory/SPEC.md @@ -130,10 +130,10 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c - **D2-L — Brunch is an opinionated product, not a pi platform shell.** The POC hardcodes its toolset, system prompt, and policy doctrine; scopes state to `.brunch/`; and hides pi's generic extension surface from end users. Depends on: A1-L. Supersedes: —. - **D39-L — Brunch owns sealed Pi settings plus an explicit Brunch extension bundle around the embedded harness.** Product behavior must come from Brunch-owned programmatic policy, not ambient Pi discovery. The settings layer must stay sealed (no ambient global/project `.pi` behavior shaping, no ambient resource discovery, offline-by-default for Brunch-launched Pi), and the product extension bundle must remain an explicit static registrar list rather than any filesystem discovery or runtime `import()` path. Current sealed-profile policy and bundle topology live in [`src/.pi/README.md`](src/.pi/README.md), [`src/.pi/extensions/README.md`](src/.pi/extensions/README.md), [`src/app/pi-settings.ts`](src/app/pi-settings.ts), [`src/app/pi-extensions.ts`](src/app/pi-extensions.ts), and [`src/dev/README.md`](src/dev/README.md). Depends on: D1-L, D2-L, A19-L. Supersedes: treating `noSkills: true` as full profile isolation, relying on user/project `.pi/` defaults to be harmless, nesting Brunch's product extension modules under `src/.pi/extensions/brunch/`, or replacing the explicit static extension list with a Brunch-internal filesystem-discovery / `brunchExtensionMeta` / `loadOrder` mechanism as the product runtime load path. - Tooling exception: the worktree helper extension now lives outside this repository under the user Pi agent tree (`~/.pi/agent/extensions/worktree/index.ts`) for direct Pi sessions only. It is not a Brunch product extension, is not imported by `src/.pi/brunch-pi-extensions.ts`, and does not weaken the sealed Brunch Pi settings/extensions boundary; Brunch-launched product sessions continue to disable ambient `.pi/` discovery unless deliberately imported. The extension may register direct-Pi `/worktree:switch` / `switch_worktree` and `/worktree:create` / `create_worktree` affordances, but Brunch does not test, package, or document it as a product extension. -- **D40-L — Runtime state is transcript-backed Brunch session-agent state, not hidden extension memory.** The architectural commitment is that Brunch session-agent posture remains transcript-backed Pi JSONL state rather than hidden extension memory: posture switches are user/system authority, the foreground session agent is derived from `op_mode`, child axes default back to `AUTO` when invalidated, and tool authority remains `op_mode`-gated rather than prompt-composition-owned. Runtime-state entries are Pi JSONL state-change facts, not assistant/user chat content: init and switch entries should render, when visible, as dim non-chat state rows analogous to Pi thinking/model-change rows, and must not enter LLM context as ordinary conversation. Current materialized state lives in [`src/session/README.md`](src/session/README.md), [`src/projections/README.md`](src/projections/README.md), [`src/.pi/README.md`](src/.pi/README.md), [`src/.pi/agents/README.md`](src/.pi/agents/README.md), and [`src/projections/session/runtime-policy.ts`](src/projections/session/runtime-policy.ts). Depends on: D17-L, D23-L, D25-L, D39-L, D58-L, D59-L. Refined by: D85-L (the AUTO-able objective axes reduce to `strategy` + `lens`; `goal` is dropped from the runtime-state axis set and inlined into the agent role prompt). Supersedes: mode-only vocabulary, extension-local mutable state as authority, storing the foreground role as independent session state, the "runtime bundle / role preset" as one knob deriving model/thinking/resources, and binding prompt-resource location to `src/.pi/context/`. +- **D40-L — Runtime state is transcript-backed Brunch session-agent state, not hidden extension memory.** The architectural commitment is that Brunch session-agent posture remains transcript-backed Pi JSONL state rather than hidden extension memory: posture switches are user/system authority, the foreground session agent is derived from `op_mode`, child axes default back to `AUTO` when invalidated, and tool authority remains `op_mode`-gated rather than prompt-composition-owned. Runtime-state entries are Pi JSONL state-change facts, not assistant/user chat content: init and switch entries should render, when visible, as dim non-chat state rows analogous to Pi thinking/model-change rows, and must not enter LLM context as ordinary conversation. Current materialized state lives in [`src/session/README.md`](src/session/README.md), [`src/projections/README.md`](src/projections/README.md), [`src/.pi/README.md`](src/.pi/README.md), [`src/agents/README.md`](src/agents/README.md), [`src/agents/prompts/README.md`](src/agents/prompts/README.md), and [`src/projections/session/runtime-policy.ts`](src/projections/session/runtime-policy.ts). Depends on: D17-L, D23-L, D25-L, D39-L, D58-L, D59-L. Refined by: D85-L (the AUTO-able objective axes reduce to `strategy` + `lens`; `goal` is dropped from the runtime-state axis set and inlined into the agent role prompt). Supersedes: mode-only vocabulary, extension-local mutable state as authority, storing the foreground role as independent session state, the "runtime bundle / role preset" as one knob deriving model/thinking/resources, and binding prompt-resource location to `src/.pi/context/`. - **D34-L — Command containment separates visibility suppression from effect blocking.** Current Pi extension seams can hide unsupported slash suggestions with autocomplete wrapping and can cancel branch/session effects through lifecycle hooks, but they cannot strictly suppress exact interactive built-in commands before `InteractiveMode` dispatches them. Brunch-owned commands must use product-specific names and route writes through Brunch handlers/`CommandExecutor`; extension command collisions are not an override mechanism. Strict built-in command/keybinding policy is a Pi upstream/API ask, while POC safety relies on hiding generic affordances, blocking dangerous effects (`/fork`, `/clone`, raw session replacement), allowing native `/tree` as inspection/navigation, and failing fast on branched transcripts. Brunch's command-policy code should live in `src/.pi/extensions/commands/policy.ts`, merging branch/session-effect blocking with any product command allow/deny behavior instead of preserving a branch-only module. Depends on: D2-L, D24-L, A18-L. Supersedes: treating extension `input` handlers or command-name collisions as built-in command allowlisting. - **D35-L — Dynamic TUI chrome is a Brunch projection wrapper over Pi UI primitives.** The architectural commitment is that downstream TUI affordances call one Brunch-owned renderer (`renderBrunchChrome` or its successor) with a single activated product-state value rather than scattering raw `ctx.ui.setHeader` / `setFooter` / `setWidget` / title / working-indicator calls; the wrapper is stateless projection over canonical workspace/session/graph facts, never its own mutable state. Chrome is a project-first shell surface with selected-spec context — project name labels the cwd container, spec title labels the selected graph, session label distinguishes transcript instances — and a session label must never replace spec identity or graph truth. Chrome must not consume the status-key namespace for its own summary (`ctx.ui.setStatus` stays a lateral channel for other extensions), must not advertise unwired affordances, and RPC clients must rely only on surfaces Pi actually emits (header/footer/working-indicator are TUI-only in current Pi RPC mode). Current chrome state shape, render surfaces, telemetry/refresh, startup-header behavior, and status-key filtering live in [`src/.pi/extensions/chrome/README.md`](src/.pi/extensions/chrome/README.md); launch/activation wiring lives in [`src/.pi/extensions/README.md`](src/.pi/extensions/README.md). Depends on: D2-L, D21-L, D34-L, A18-L. Supersedes: treating Pi UI methods as direct downstream affordance APIs, rendering placeholder session state such as `unbound` after a session is activated, consuming the status-key namespace for chrome's own static summary, using spec title as the default session label, or allowing two unchanged Brunch-created default names to collide in one cwd, and the earlier resume/open-launches-stay-quiet clause (superseded 2026-06-11: the shipped, test-locked behavior headers every non-cancel activation). -- **D52-L — Source topology targets `src/{app, workspace, scripts, agents, .pi, db, graph, session, projections, renderers, rpc, web}` with directed layer dependencies.** Reusable projection and rendering modules live in top-level `src/projections/` and `src/renderers/` rather than whichever domain or adapter first needed them; `src/agents/` is the Pi-independent owner for Brunch-authored LLM context ingress (currently the central registry for bundled agent-body and prompt-resource skill file paths, with later content moves tracked by the LLM-context refactor; the `projections/session/runtime-policy.ts` registry import is a temporary migration edge until runtime policy moves under `agents/runtime/`); domain layers (`graph/`, `session/`) and the reusable `projections` / `renderers` / `agents` layers must not import adapters, transports, app entrypoints, or web code; `graph/` is the only layer that imports `db/`, plus the single sanctioned `db/`→`graph/schema/kinds.ts` taxonomy edge (D73-L). The concrete per-directory ownership, layout sketch, and full import matrix are owned by [`src/README.md`](src/README.md). Depends on: D2-L, D4-L, D39-L, D40-L. Refined by: D73-L. Supersedes: scattering session domain files at `src/` root; treating Pi-only agents as a host-independent top-level `src/.pi/` layer; nesting prompt composition under `src/.pi/context/`; treating reusable `project` / `format` helpers as owned by whichever adapter first needed them; treating `.pi/agents` and `.pi/skills` as the long-term conceptual owner for Brunch-authored model-facing content. +- **D52-L — Source topology targets `src/{app, workspace, scripts, agents, .pi, db, graph, session, projections, renderers, rpc, web}` with directed layer dependencies.** Reusable projection and rendering modules live in top-level `src/projections/` and `src/renderers/` rather than whichever domain or adapter first needed them; `src/agents/` is the Pi-independent owner for Brunch-authored LLM context ingress (currently bundled agent prompt bodies plus the central registry for body and prompt-resource skill file paths, with later skill/runtime/context moves tracked by the LLM-context refactor; the `projections/session/runtime-policy.ts` registry import is a temporary migration edge until runtime policy moves under `agents/runtime/`); domain layers (`graph/`, `session/`) and the reusable `projections` / `renderers` / `agents` layers must not import adapters, transports, app entrypoints, or web code; `graph/` is the only layer that imports `db/`, plus the single sanctioned `db/`→`graph/schema/kinds.ts` taxonomy edge (D73-L). The concrete per-directory ownership, layout sketch, and full import matrix are owned by [`src/README.md`](src/README.md). Depends on: D2-L, D4-L, D39-L, D40-L. Refined by: D73-L. Supersedes: scattering session domain files at `src/` root; treating Pi-only agents as a host-independent top-level `src/.pi/` layer; nesting prompt composition under `src/.pi/context/`; treating reusable `project` / `format` helpers as owned by whichever adapter first needed them; treating `.pi/skills` or retired `.pi/agents` locations as the long-term conceptual owner for Brunch-authored model-facing content. - **D73-L — Domain enum taxonomy is owned by drizzle-free schema leaves; persistence and adapters are consumers, not the source.** The closed enum `const` arrays that define graph vocabulary — node kinds (`INTENT_KINDS`, `ORACLE_KINDS`, `DESIGN_KINDS`, `PLAN_KINDS`), `NODE_PLANES` (`intent`/`oracle`/`design`/`plan`), `NODE_BASES`, `EDGE_CATEGORIES`, `EDGE_STANCES`, `READINESS_BANDS`, `LENS_AFFINITIES`, `GAP_DISPOSITIONS`, and `GAP_PREDICATE_KINDS` — live in `graph/schema/kinds.ts`, a pure constants leaf that imports nothing (no drizzle, no `graph/atoms`). Both `db/schema.ts` (for `text({ enum })` column constraints, including the previously-inlined `plane` columns) and `graph/` domain modules import the arrays from this leaf; `graph/index.ts` re-exports them from the leaf so non-graph layers still avoid importing `db/` directly (I26-L). Session runtime axis vocabulary mirrors the same ownership direction in `session/schema/kinds.ts`: that leaf imports nothing and owns the `op_mode`, agent-role, `strategy`, `lens`, `auto`, and display-only planned mode choices consumed by `session/runtime-state.ts`, `projections/session/*`, and `.pi/extensions/agent-runtime/runtime/state.ts`; it deliberately contains no `goal` axis and no retired `READINESS_GRADES`. Derivations stay where they are read: `NODE_KIND_METADATA`, `formatGraphNodeCode`, `parseGraphNodeCode`, and `intentKindCategory` remain in `graph/schema/nodes.ts` (D62-L). The motivating defect: because `db/schema.ts` eagerly evaluates `sqliteTable(...)` and `verbatimModuleSyntax` emits even type-only imports at runtime, any value-import path from `web/` into the old taxonomy location pulled Drizzle into the browser bundle. Locating taxonomy in a drizzle-free leaf makes the `web/` build target structurally Drizzle-free (I44-L) and corrects the ownership direction so the domain, not the persistence layer, owns its vocabulary. Vocabulary migration status: `READINESS_GRADES` is retired (readiness is no longer a stored grade, D45-L), `ELICITATION_BACKLOG_STATUSES` is replaced by the `elicitation_gaps` disposition + predicate-shape enums (D65-L), and `READINESS_BANDS` stays. Depends on: D16-L, D52-L, D54-L, D62-L, D63-L, D64-L; I26-L. Supersedes: `db/schema.ts` owning the shared enum `const` arrays and the "enum literals flow outward from `db/schema.ts`" posture; the triplicated inline `['intent','oracle','design','plan']` plane literals. #### Data model & vocabulary @@ -267,17 +267,17 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c - **D46-L — Retired: commitment posture as persisted spec state.** Design and oracle lenses may still create accepted graph material, and cohesive review sets still commit atomically through `acceptReviewSet` per D27-L, but Brunch no longer models `pinning` or `commitment_focus` as spec-row state. Future commitment projection should derive from capability-readiness (D74-L), active strategy/lens/review-set state, and graph evidence rather than a persisted posture enum. Depends on: D27-L, D28-L, D45-L, D74-L. Supersedes: per-item requirement/criterion confirmation, treating design/oracle commitment phases as first permission to discuss design/oracle topics, and storing commitment posture/focus on the spec. - **D47-L — Structured-exchange `preface` is the near-term carrier for non-committed elicitor interpretation.** The structured-exchange payload's plain prose `preface` summarizes working context before the next question: exploratory file-reading/tool-use findings, implied graph candidates, low-confidence edges, and the rationale for what is being asked next. Preface text is transcript truth and user-visible orientation, but it is not graph truth, not candidate-artefact schema, and not a hidden side store. High-confidence facts still commit through `CommandExecutor`; low-confidence implications stay in preface/question material until clarified, accepted, or escalated to reconciliation needs. Future `capture_*` analysis entries provide a separate post-exchange/review evidence surface for candidate semantic changes; they do not replace preface as next-question orientation and do not become graph truth. Structured candidate metadata is deferred until fixtures/projections prove plain prose is insufficient. Depends on: D12-L, D18-L, D37-L, D50-L. Refined by: D82-L (the digest step generalizes the preface pattern for bulk acquisition — capture runs over the assistant-authored characterization, not the raw bulk). Supersedes: inventing a candidate-artefact substrate merely to carry ordinary next-question disambiguation material. - **D50-L — `capture_*` tools persist transcript-native ANALYSIS, not graph mutations.** Brunch may add a third structured-exchange tool family such as `capture_analysis` alongside `present_*` and `request_*`. A `capture_*` tool returns a normal persisted Pi `toolResult` with Brunch details and markdown content describing likely graph/node/edge changes, grouped into high-confidence candidates that could be committed later and low-confidence candidates that should drive clarification. `capture_*` output is transcript-visible evidence for Markdown/ASCII review and later graph-mutation cross-checking, but it is not graph truth and never bypasses the `CommandExecutor`. Product UI should hide capture analysis entirely if Pi exposes a supported hide seam; otherwise `renderResult` should be maximally collapsed/minimal while preserving full persisted `toolResult.content`/`details` for transcript renderers. The current schema layer deliberately defines only minimum capture details (`schema`, `v`, `exchange_id`, `tool_meta`) and rejects graph payloads; richer analysis payloads and shared component subparts (`Preface`, prompt body, option list, answer summary, capture analysis) require a later `ln-design` pass before implementation. Depends on: D12-L, D17-L, D18-L, D37-L, D41-L, D47-L. Supersedes: using ad hoc hidden custom entries, probe-only side files, or graph writes as the first carrier for pre-graph analysis. -- **D44-L — Subagents are main-agent-invoked, blocking Pi tool calls that gather data and propose variants through sealed SDK child sessions.** Brunch may register a single `subagent` Pi tool whose parameters are either `{ agent, task }` or `{ tasks: [] }` (parallel), never both. Each invocation runs an in-process SDK `AgentSession` built from explicit sealed services: in-memory settings/auth/session managers, no ambient resource discovery, a per-agent system prompt, the parent's model registry, and an explicit read-only/no-tool allowlist. The subagent has no inherited conversation context, so the task string must carry everything it needs. Background agent definitions are declarative `SYSTEM.md` bodies under the shared `src/.pi/agents//SYSTEM.md` convention with TypeBox-validated frontmatter (`name`, `description`, `tools`, `model`, `thinking`) plus a system-prompt body; duplicate frontmatter keys fail loud. Concurrency cap lives in [src/.pi/extensions/subagents/config.json](src/.pi/extensions/subagents/config.json) (default 4). The subagent's result text is returned directly to the main agent as tool result content; subagents do not append custom messages to the session log on their own behalf, do not invoke the `CommandExecutor`, and do not gain access to the parent's Brunch RPC handlers. Registration is opt-in: `src/app/pi-subagents.ts` can assemble deps for `createBrunchPiExtensions(...)`, but launch paths that omit those deps do not register or advertise the tool. POC starter agents split into two families: +- **D44-L — Subagents are main-agent-invoked, blocking Pi tool calls that gather data and propose variants through sealed SDK child sessions.** Brunch may register a single `subagent` Pi tool whose parameters are either `{ agent, task }` or `{ tasks: [] }` (parallel), never both. Each invocation runs an in-process SDK `AgentSession` built from explicit sealed services: in-memory settings/auth/session managers, no ambient resource discovery, a per-agent system prompt, the parent's model registry, and an explicit read-only/no-tool allowlist. The subagent has no inherited conversation context, so the task string must carry everything it needs. Background agent definitions are declarative `SYSTEM.md` bodies under the shared `src/agents/prompts//SYSTEM.md` convention with TypeBox-validated frontmatter (`name`, `description`, `tools`, `model`, `thinking`) plus a system-prompt body; duplicate frontmatter keys fail loud. Concurrency cap lives in [src/.pi/extensions/subagents/config.json](src/.pi/extensions/subagents/config.json) (default 4). The subagent's result text is returned directly to the main agent as tool result content; subagents do not append custom messages to the session log on their own behalf, do not invoke the `CommandExecutor`, and do not gain access to the parent's Brunch RPC handlers. Registration is opt-in: `src/app/pi-subagents.ts` can assemble deps for `createBrunchPiExtensions(...)`, but launch paths that omit those deps do not register or advertise the tool. POC starter agents split into two families: - **Data gatherers** — read-only context fetchers whose output grounds proposals: **explorer** (codebase + selected-spec graph recon: `read`, `grep`, `find`, `ls`, `read_graph`) and **researcher** (web research: `web_search`, `web_fetch`). `read_graph` is granted only when the app root injects parent graph readers; no write-capable graph child exists yet. - **Projectors/reviewers** — **projector** (no tools) emits one variant of a candidate proposal from a grounding bundle and lens frame; **reviewer** (no tools) checks supplied candidate material before main-agent presentation or commitment. The main agent achieves diversity by issuing parallel `tasks: []` invocations of `projector` with intentionally distinct framings — the subagent realization of the "design it twice" pattern from `ln-design` and the parallel fan-out anticipated by `ln-oracles`. Each `projector` invocation runs in its own isolated context so variants don't cross-contaminate; the main agent collects outputs and owns any product write. This division mirrors the batch-proposal flow in D26-L. Worker-style write-capable subagents and nesting remain deferred beyond the initial `execute`/`orchestrator` standup. Cross-extension agent registration (Amos's `globalThis.__pi_subagents` bridge), raw `pi` subprocesses, and ambient `~/.pi` discovery are rejected for the POC because they conflict with profile sealing. Subagents remain an optional enhancement to candidate-proposal diversity and future delegated acquisition, not a load-bearing M0–M9 substrate. Depends on: D2-L, D26-L, D27-L, D30-L, D31-L, D39-L, D40-L, D41-L. Distinct from: D15-L Side task (non-blocking, status-via-custom-message), the deferred Side chat (user-invoked overlay; see Future Direction Register). Supersedes: subprocess/argv-shaped subagents and the `globalThis.__pi_subagents` bridge. Refined by: D90-L (shared foreground/background manifest + code-owned background discovery), D91-L (semi-permeable seal + assembled prompt + injected world), D92-L (sovereign tool grants + op_mode delegatable-set gate). -- **D90-L — Foreground and background agents share one manifest model; background discovery is code-owned (frontmatter is authoring DX, not a second agent model).** Agent definitions project into one `AgentManifest` (`id`, `kind`, `description`, `model`, `thinking`, body at the canonical `src/.pi/agents//SYSTEM.md` convention, a skills grant, a tools grant, and a `canDelegate` set naming the background agents it may spawn — D92-L/D93-L) discriminated by `kind: "foreground" | "background"` — the execution **lifecycle/host**, not a noun: a foreground agent is a live op_mode-derived Pi session; a background agent is a spawned-to-completion sealed child. The kinds keep **distinct authority sources**: a foreground agent's identity is derived from `op_mode` (D40-L) and its tool/skill legality is dynamic (op_mode policy + live gaps); a background agent's identity is caller-chosen (`{agent, task}`) and its skills/tools come from its authored manifest. DX-vs-strictness is reconciled by keeping **frontmatter as the authoring surface** for background agents while making **discovery code-owned**: the `readdir` scan over `agents/*.md` is retired for an explicit registry id list (mirroring how `state.ts` loads foreground bodies/skills through `loadSkills({ skillPaths, includeDefaults: false })`), so D39-L "no filesystem discovery" holds and frontmatter authoring survives. "subagent" stays the tool/UX noun (the main-agent tool call), not the kind name. Depends on: D39-L, D40-L, D44-L, D58-L. Refines: D44-L (the parallel frontmatter-discovered format collapses into the shared manifest; background agent bodies migrate from `src/.pi/extensions/subagents/agents/*.md` onto the canonical `src/.pi/agents//SYSTEM.md` convention, so SPEC carries one agent-body layout — D44-L and the `src/.pi/extensions/subagents/README.md` topology notes reconcile to that path during the implementation slices). Establishing frontier: `subagent-reconciliation`. Supersedes: `readdir` filesystem discovery of subagent definitions; the standalone subagent frontmatter format as a second, separate agent model. +- **D90-L — Foreground and background agents share one manifest model; background discovery is code-owned (frontmatter is authoring DX, not a second agent model).** Agent definitions project into one `AgentManifest` (`id`, `kind`, `description`, `model`, `thinking`, body at the canonical `src/agents/prompts//SYSTEM.md` convention, a skills grant, a tools grant, and a `canDelegate` set naming the background agents it may spawn — D92-L/D93-L) discriminated by `kind: "foreground" | "background"` — the execution **lifecycle/host**, not a noun: a foreground agent is a live op_mode-derived Pi session; a background agent is a spawned-to-completion sealed child. The kinds keep **distinct authority sources**: a foreground agent's identity is derived from `op_mode` (D40-L) and its tool/skill legality is dynamic (op_mode policy + live gaps); a background agent's identity is caller-chosen (`{agent, task}`) and its skills/tools come from its authored manifest. DX-vs-strictness is reconciled by keeping **frontmatter as the authoring surface** for background agents while making **discovery code-owned**: the `readdir` scan over `agents/*.md` is retired for an explicit registry id list (mirroring how `state.ts` loads foreground bodies/skills through `loadSkills({ skillPaths, includeDefaults: false })`), so D39-L "no filesystem discovery" holds and frontmatter authoring survives. "subagent" stays the tool/UX noun (the main-agent tool call), not the kind name. Depends on: D39-L, D40-L, D44-L, D58-L. Refines: D44-L (the parallel frontmatter-discovered format collapses into the shared manifest; background agent bodies migrated from `src/.pi/extensions/subagents/agents/*.md` onto the canonical `src/agents/prompts//SYSTEM.md` convention, so SPEC carries one agent-body layout — D44-L and the `src/.pi/extensions/subagents/README.md` topology notes reconcile to that path). Establishing frontier: `subagent-reconciliation`. Supersedes: `readdir` filesystem discovery of subagent definitions; the standalone subagent frontmatter format as a second, separate agent model. - **D91-L — Background subagents run a semi-permeable seal: explicitly-injected parent world handles plus an assembled (not verbatim) prompt; ambient leakage stays closed.** This deliberately reopens the D44-L/I29-L "no graph access, no Brunch RPC, no inherited context" clause. The seal stays closed against **ambient** leakage (in-memory auth/settings/session, no `~/.pi` discovery — D39-L intact) but opens to **explicitly injected** parent world handles the app root (`src/app/pi-subagents.ts`) supplies at spawn: the same `GraphReaders` the foreground uses scoped to the parent's `specId`, the spec/workspace context seed, and a bounded **session digest** (the parent branch flattened via `sessionManager.getBranch()`, the pattern in pi's `summarize.ts` example). The child's system prompt becomes **assembled, not verbatim**: body + a background control header (sealed child, delegated task, snapshot view) + world snapshot + a `` manifest built from the manifest's skills grant + router rules — reusing the foreground composer's extracted prompt-skill core (`renderBrunchSkills`, the skill-manifest loader) plus the selected workspace/spec seed renderer from `src/session/agent-context-seed.ts`, minus the foreground-only elicitation-recommendation block. World binding is **snapshot-at-spawn** (the child runs to completion against a fixed view) where the foreground is live-per-turn. Read access is asymmetric **by design**: the **session digest** is a snapshot block baked into the prompt (expensive, rarely re-pulled), while the **graph** is exposed as Brunch read tools (`read_graph` now; `read_session_context`, `read_elicitation_gaps`, … remain future grants) the child calls on demand (a recon agent iterates on graph). Return to the main agent is the ordinary tool-call result: findings re-enter main-agent context as the tool-result `content`; the structured `details` payload (`{ agent, status, text, … }`) is render-only via custom `renderCall`/`renderResult`, never model context. Write-capable children stay deferred (gated by D92-L); when they land, a `mutate_graph` against the parent's `specId` is a real side effect crossing back *outside* the tool result, and is named here so the write slice does not surprise. Depends on: D39-L, D43-L, D44-L, D58-L, D60-L, D82-L. Establishing frontier: `subagent-reconciliation`. Supersedes: the D44-L/I29-L clause that subagents have no graph access, no Brunch RPC/graph reads, no inherited world context, and a verbatim-body system prompt. - **D92-L — Background tool grants are sovereign per-agent ceilings gated by a code-owned, op_mode-keyed delegatable-set allowlist — not parent-subset containment.** The earlier containment invariant (child tools ⊆ the parent's current legal set) is rejected: delegation is **capability-inverting on purpose** — a low-privilege orchestrator (the `execute`-mode foreground agent holding only data-layer tools) may spawn a narrow high-privilege child (e.g. a file-writing worker) so writes are quarantined in a child that does one job and exits. Each background agent's tool grant is therefore **sovereign** (authored in its manifest; may exceed the parent's). The surviving safety boundary is not a tool subset but **which background agents an op_mode may spawn**: a **code-owned, op_mode-keyed delegatable-set allowlist** living beside `AGENT_PROMPT_DEFINITIONS` / the op_mode policy, *not* authored in frontmatter (otherwise a manifest could self-advertise into a read-only mode). This lifts D40-L's registration ≠ advertisement from tools to agents: every background agent is registered; op_mode decides which are advertised as spawnable. A read-only `elicit` session is write-safe because elicit's delegatable set **excludes** write-capable agents, not because children are subset-bounded. Enabling write tools later = author the write-capable worker manifest + add it to the relevant op_mode's delegatable set (an advertisement change), not a re-derivation of parent authority. Depends on: D39-L, D40-L, D44-L. Establishing frontier: `subagent-reconciliation`. Refined by: D93-L (the delegatable-set allowlist becomes a per-agent `AgentManifest` `canDelegate` field; for a foreground mode it is that mode's code-owned delegatable set, and it generalizes to background→background nesting). Supersedes: the parent-subset tool-containment model for subagents; D44-L's "read-only/no-tool allowlist" as the only background tool posture; the framing that write-capable subagents wait on an execute mode raising both parent and child ceilings together. - **D93-L — Operational mode and foreground agent collapse to one op-mode-keyed source of truth; the foreground roster is `elicit` / `execute` / `code`.** A foreground agent and its operational mode are 1:1 (D40-L: the foreground agent is derived from `op_mode`), so the prior **three-record fragmentation** — id enums in `src/session/schema/kinds.ts`, `OPERATIONAL_MODE_DEFINITIONS` + `AGENT_ROLE_DEFINITIONS` + `TOOL_POLICY_DEFINITIONS` in `src/projections/session/runtime-policy.ts`, and `AGENT_PROMPT_DEFINITIONS` in `src/.pi/extensions/agent-runtime/runtime/state.ts` (which duplicated `model`/`thinking`/`allowedStrategies`/`allowedLenses` across two of them) — collapses to a **single op-mode-keyed record**. An operational mode IS `{ foreground AgentManifest (D90-L), tool policy, canDelegate set }`; background agents live in a sibling `AgentManifest` registry, and the per-agent **`canDelegate`** field (D92-L generalized from op_mode-keyed to a manifest field) links a foreground mode to the background agents it may spawn — **code-owned for foreground modes** so the write-safety boundary (I49-L) holds; it also generalizes to background→background nesting. The foreground roster is three modes: **`elicit` → `elicitor`** (current: read-only + graph-writes-via-tools; delegates to read-only gatherers); **`execute` → `orchestrator`** (live initial standup: direct shell/file writes blocked, no delegated workers yet, and one code-owned `orchestrator_stub` grant proves the non-dev runnable path; future worker delegation quarantines writes in spawned high-privilege workers per D92-L); and **`code` → `pi-coder`** (planned direct-coding assistant with direct `read`/`edit`/`write`/`bash`, **augmented to be Brunch-tooling-aware** — it also sees the graph read/capture tools). `execute` and `code` contrast deliberately: the orchestrator does not write directly, while the pi-coder will write directly (and does not delegate by default). `pi-coder` **instantiates** the augment case D58-L anticipated — its `SYSTEM.md` augments Pi's base coding-agent prompt rather than replacing it; whether other foreground roles should instead suppress/replace the base stays open per D58-L. (`code` is provisional naming for the pi-coder mode; rename is a one-token change. `code` remains in `PLANNED_OPERATIONAL_MODE_IDS` until built; `execute` is live.) Depends on: D23-L, D40-L, D58-L, D90-L, D92-L; I49-L. Establishing frontier: `subagent-reconciliation` (model + collapse land here; `execute` stood up here with the stub grant, while `code` remains declarative build-out — see PLAN). Supersedes: the three-record foreground-agent fragmentation as separate sources of truth; `defaultRole`/`allowedRoles` as a flexible many-roles-per-mode model (it is 1:1); the `executor` name for the execute-mode agent (now `orchestrator`). - **D36-L — Spec/session selection is a reusable hierarchical decision model with transport-specific presentations.** Brunch owns a pure spec/session selection model that renders cwd-scoped inventory under the discovered project name without calling the user-created object a “workspace”. In TUI mode, the model may present a fast “continue last session” affordance when `.brunch/workspace.json` points to a valid spec+session; otherwise, or after “other spec/session”, the durable tree is: `create new spec → provide spec name → session created automatically`; `resume existing spec → choose existing spec → create a new session OR resume existing session → choose existing session`. The UI should not list every spec as a top-level action label; “resume existing spec” is the top-level intent, and the spec list is the next screen/scrollable selector. The model returns a product decision (`new spec`, `new session for spec`, `open session`, `continue selected session`, `cancel/quit`) without opening Pi sessions or mutating `.brunch/workspace.json` itself. The `WorkspaceSessionCoordinator` activates that decision and owns all persistence/session-binding effects. TUI startup and in-session paths share branded `pi-tui` components and colocated logo assets under `src/.pi/components/workspace-dialog`; adapters differ only in terminal lifecycle and Pi session-replacement mechanics (`ProcessTerminal`/`TUI.showOverlay` before Pi starts, `ctx.ui.custom(..., { overlay: true })` inside Pi), not in product semantics. RPC/headless transports must not invoke the TUI picker; they expose the same initial-selection requirement and activation decisions as JSON-RPC/product results so CLI JSON-RPC clients can select or create spec/session correctly. Depends on: D11-L, D21-L, D24-L, D33-L. Supersedes: implicit resume of `.brunch/workspace.json` on TUI launch, Pi `/resume`/`/new` as Brunch's product session chooser, one-off startup-only picker implementations, a flat action list that says “workspace” for specs, top-level `resume spec X` labels, and a separate intermediate action chooser for switching. - **D42-L — Session naming is Pi `session_info` presentation metadata, not spec identity.** Brunch-created sessions should be named at creation with neutral workspace-global defaults (`Untitled Session 1`, `Untitled Session 2`, …) so pickers/chrome never show an unnamed Brunch session and unchanged defaults do not collide across specs in the same cwd. These defaults are immediate lifecycle metadata, not LLM-generated summaries and not derived from the selected spec title. Brunch may later use Pi session lifecycle hooks to opportunistically replace a default with a short human-readable name that characterizes what happened in the transcript. The preferred generation trigger is `session_shutdown` for `quit`, `new`, and `resume` replacements because it sees the just-finished transcript and can name it before later picker lists need to distinguish sessions; `session_before_compact` or post-compaction (`session_compact`) may be used to refresh names after major summarization, and a manual/user rename command can force or override naming. The generation call should mirror the model-selection pattern in the local `summarize.ts` extension example: choose a cheap/fast authorized model, extract user/assistant text plus salient tool calls from the current branch, ask for a concise title, and append a Pi `session_info` entry through `SessionManager.appendSessionInfo`. Naming must be best-effort and non-blocking with a tight budget: failures, missing auth, empty transcripts, or shutdown aborts preserve the existing default/user label rather than blocking session replacement or exit. Session display names label sessions in pickers and chrome, but do not affect spec ids, session bindings, graph truth, or replay semantics. Depends on: D6-L, D17-L, D21-L, D35-L. Supersedes: using spec title or session UUID alone as the only durable display label once transcripts have meaningful content, leaving Brunch-created sessions unnamed, spec-local default numbering, or treating generated session names as canonical spec identity. -- **D58-L — Brunch prompt composition is a thin runtime header plus a gated prompt-resource manifest, not eager selection of every objective pack.** The architectural commitment is: composition stays a projection layer, not a behavioral state machine; detailed strategy/lens/method guidance lives in read-on-demand prompt resources rather than eager prompt-pack concatenation; runtime-state-gated availability is Brunch's sealed manifest, not ambient Pi discovery; pinned axes remain visible when role/mode-legal while readiness constrains AUTO menus and gated methods/tools; and the pushed-context slice stays compact, with deeper access governed by D60-L. Current prompt-resource topology, manifest emission, file-owned skill metadata, and ownership split across `.pi/agents/`, `.pi/skills/`, `.pi/extensions/agent-runtime/system-prompts/`, and `.pi/extensions/agent-runtime/runtime/` live in [`src/.pi/README.md`](src/.pi/README.md), [`src/.pi/agents/README.md`](src/.pi/agents/README.md), [`src/.pi/skills/README.md`](src/.pi/skills/README.md), [`src/.pi/extensions/README.md`](src/.pi/extensions/README.md), [`src/.pi/extensions/agent-runtime/system-prompts/compose.ts`](src/.pi/extensions/agent-runtime/system-prompts/compose.ts), and [`src/.pi/extensions/agent-runtime/runtime/state.ts`](src/.pi/extensions/agent-runtime/runtime/state.ts). **Base-prompt relationship (validated 2026-06-18, slice 1):** the `before_agent_start` handler **appends** Brunch's composed block (now led by the agent `SYSTEM.md` body, then runtime header + manifests) to Pi's base system prompt (`${basePrompt}\n\n${composed}`), so a foreground agent currently *augments* Pi's base coding-agent prompt rather than replacing it. Whether a foreground role's `SYSTEM.md` body should suppress or replace that base is **open** and tied to the future `pi-coder` op-mode (which deliberately augments Pi's coding agent); the `elicitor` augmenting a coding base is a known follow-on question, not a settled choice. Refined by: D93-L (the `code`→`pi-coder` foreground mode instantiates the augment case; the replace option for other roles stays open). Composition is projection, not a behavioral state machine. Depends on: D23-L, D25-L, D39-L, D40-L, D52-L, D59-L, D60-L. Refined by: D85-L (implemented 2026-06-18/19: the manifest drops `` — two axes `strategy` + `lens` — and the `goal` body inlines into the `elicitor` role prompt) and by the 2026-06-22 prompt-skill-topology slice (all prompt resources adopt Agent Skills `SKILL.md` topology; `description` becomes file-owned frontmatter; the emitted wrapper becomes `` with per-skill ``). Supersedes: the flat "base + mode + role + strategy + lens + grade + …" layering; the fixed all-packs concatenation in `compose-brunch-prompt.ts`; "role preset / runtime bundle" as the composition unit; direct Layer-2 eager prompt-pack injection as the default mechanism; top-level `src/agents/` for Pi-only agents; and `capability` as a parallel name for `method`. +- **D58-L — Brunch prompt composition is a thin runtime header plus a gated prompt-resource manifest, not eager selection of every objective pack.** The architectural commitment is: composition stays a projection layer, not a behavioral state machine; detailed strategy/lens/method guidance lives in read-on-demand prompt resources rather than eager prompt-pack concatenation; runtime-state-gated availability is Brunch's sealed manifest, not ambient Pi discovery; pinned axes remain visible when role/mode-legal while readiness constrains AUTO menus and gated methods/tools; and the pushed-context slice stays compact, with deeper access governed by D60-L. Current prompt-resource topology, manifest emission, file-owned skill metadata, and ownership split across `agents/prompts/`, `.pi/skills/`, `.pi/extensions/agent-runtime/system-prompts/`, and `.pi/extensions/agent-runtime/runtime/` live in [`src/agents/README.md`](src/agents/README.md), [`src/agents/prompts/README.md`](src/agents/prompts/README.md), [`src/.pi/README.md`](src/.pi/README.md), [`src/.pi/skills/README.md`](src/.pi/skills/README.md), [`src/.pi/extensions/README.md`](src/.pi/extensions/README.md), [`src/.pi/extensions/agent-runtime/system-prompts/compose.ts`](src/.pi/extensions/agent-runtime/system-prompts/compose.ts), and [`src/.pi/extensions/agent-runtime/runtime/state.ts`](src/.pi/extensions/agent-runtime/runtime/state.ts). **Base-prompt relationship (validated 2026-06-18, slice 1):** the `before_agent_start` handler **appends** Brunch's composed block (now led by the agent `SYSTEM.md` body, then runtime header + manifests) to Pi's base system prompt (`${basePrompt}\n\n${composed}`), so a foreground agent currently *augments* Pi's base coding-agent prompt rather than replacing it. Whether a foreground role's `SYSTEM.md` body should suppress or replace that base is **open** and tied to the future `pi-coder` op-mode (which deliberately augments Pi's coding agent); the `elicitor` augmenting a coding base is a known follow-on question, not a settled choice. Refined by: D93-L (the `code`→`pi-coder` foreground mode instantiates the augment case; the replace option for other roles stays open). Composition is projection, not a behavioral state machine. Depends on: D23-L, D25-L, D39-L, D40-L, D52-L, D59-L, D60-L. Refined by: D85-L (implemented 2026-06-18/19: the manifest drops `` — two axes `strategy` + `lens` — and the `goal` body inlines into the `elicitor` role prompt) and by the 2026-06-22 prompt-skill-topology slice (all prompt resources adopt Agent Skills `SKILL.md` topology; `description` becomes file-owned frontmatter; the emitted wrapper becomes `` with per-skill ``). Supersedes: the flat "base + mode + role + strategy + lens + grade + …" layering; the fixed all-packs concatenation in `compose-brunch-prompt.ts`; "role preset / runtime bundle" as the composition unit; direct Layer-2 eager prompt-pack injection as the default mechanism; treating top-level `src/agents/` as Pi-only rather than Brunch LLM-context ingress; and `capability` as a parallel name for `method`. #### Continuity & origination (turn-boundary choreography) @@ -312,12 +312,12 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c - **Rollout** — incremental: `` and `` context first, then migrate the `graph`, `session`/runtime-frame, and transcript renders onto the house style; the `renderer-golden-coverage` frontier re-scopes around the new dialect. - **Closed audit** — per-session `turnCount` is derived once while inspecting canonical session files and counts only current Pi v3 JSONL message entries (`type: "message"` with `message.role: "user" | "assistant"`); tool/custom entries are excluded, and downstream workspace/specification overview renders reuse that inspected count rather than reparsing the file. - **D85-L — Brunch prompt-resource axis model: two AUTO axes (`strategy`, `lens`); `goal` inlined into the agent role; graph-write mechanism is method-routed, not a strategy.** A 2026-06-18 grill consolidation of the `.pi/skills/` topology and the D58-L manifest axes, implemented across FE-893, FE-861, and FE-898. Four locked moves: - 1. **Two AUTO objective axes, not three.** The runtime manifest advertises only `strategy` and `lens`; **`goal` is dropped as a manifest/runtime axis**. The four goal postures (`grounding-advance`, `elicit-expand`, `commit-converge`, always-on `capture-posture`) **inline into the `elicitor` agent role prompt** (`.pi/agents/elicitor/SYSTEM.md`), selected inline by the agent from the pushed readiness-band/posture context. Rationale: `goal` was already internal/readiness-derived and not user-mutable (D59-L), so advertising it as an AUTO-selectable axis was indirection over what is agent-directed-by-band anyway. Consequences for the build: `compose.ts` drops the `` family, `manifestsForState` drops `goals`, `runtime-state.ts` / `runtime-policy.ts` / `affordances.ts` drop the `goal` axis slot, and the runtime header drops the goal line. Capability-readiness (D74-L) is unaffected — it keys on gaps, not goal. + 1. **Two AUTO objective axes, not three.** The runtime manifest advertises only `strategy` and `lens`; **`goal` is dropped as a manifest/runtime axis**. The four goal postures (`grounding-advance`, `elicit-expand`, `commit-converge`, always-on `capture-posture`) **inline into the `elicitor` agent role prompt** (`src/agents/prompts/elicitor/SYSTEM.md`), selected inline by the agent from the pushed readiness-band/posture context. Rationale: `goal` was already internal/readiness-derived and not user-mutable (D59-L), so advertising it as an AUTO-selectable axis was indirection over what is agent-directed-by-band anyway. Consequences for the build: `compose.ts` drops the `` family, `manifestsForState` drops `goals`, `runtime-state.ts` / `runtime-policy.ts` / `affordances.ts` drop the `goal` axis slot, and the runtime header drops the goal line. Capability-readiness (D74-L) is unaffected — it keys on gaps, not goal. 2. **Graph-write mechanism is method-routed, not a strategy-axis member.** `propose-graph` (direct-commit) and `project-graph` (review-set) describe the **graph-write capability ids** (the D26-L commitment mechanisms), not interaction shape; their strategy names are retired rather than rehomed. The existing methods absorb the mechanics: `commit-graph` carries direct-commit mechanics, and `generate-proposal` carries review-set mechanics. The offer→accept / derive→review choreography lives in the inlined `commit-converge` posture, not in method bodies. The graph-write readiness gate was originally placed on those method ids via capability-readiness (**removed by D86-L**: the graph-write methods are floor — readiness is advisory for them, never a tool gate), while the `strategy` axis keeps only genuine interaction shapes: `step-wise-decision-tree`, `step-wise-disambiguate`, and `freestyle` (AUTO-excluded, D66-L). 3. **Gap-reflection conduct belongs to the capture skill, not `review-for-gaps`.** D81-L spawn-on-noticing + close-on-answered is **always-on capture-sweep conduct** (every elicitor turn), so it lives with the D80-L capture skill, not an optionally-selected method. `review-for-gaps` is demoted to the **deliberate-audit sense only** (missing support, contradictions, verification debt). Read/interpret-gap semantics stay on the `read_elicitation_gaps` tool description (tool-local), not duplicated into a skill; the D81-L commitment gradient lives once, in the capture skill, with gap-spawn as its third outlet. 4. **The prompt-content rewrite is design work entangled with live/stubbed seams — not a keyword fossil sweep.** The strategy/method bodies drift and overlap, but audit (2026-06-18) found their suspect tokens are mostly *not* dead history: `tool_meta` is live across every exchange projection; `capture_*` is a live `tool_meta.next` sequencing marker (`present_* → request_* → capture_*`), distinct from the D80-L-retired labeled-prefix capture core; and `present_candidates` + `user_rubric` / `meta_rubric` / `graph_refs` are the **anticipated payload of the live candidate topology stub** (`projections/exchanges/present-candidates.ts`, PLAN-confirmed stubbed), not removable fossils. Only `renderCall` is genuinely unreferenced (confirm against the Pi display API before removal). Rewriting prompt content must reconcile against the candidate stub, the exchange `tool_meta` model, and the D80-L sweep model rather than strip by keyword. Lexicon sweep in the same pass: `elicitation backlog` → `elicitation gaps` / `coverage obligation` (the D65-L rename; the inlined `elicit-expand` posture in `elicitor/SYSTEM.md` still carries the old term after the goal-axis drop relocated it). - Prompt-shape closure (revised 2026-06-22): (a) **`SKILL.md` directory topology is adopted for every strategy/lens/method** because the Agent Skills standard is now the target prompt-resource format; `references/` remains deferred until a concrete skill needs progressive disclosure, and D39-L's code-owned path list remains the availability surface. (b) **`.pi/agents//SYSTEM.md`** is adopted for live and named future bodies; no flat agent-body shape remains open. (c) **`[sub]` sub-agent notation** is deferred until the first real delegated sub-agent lands; no empty sub-agent stubs are introduced. (d) **`_generated/` typed-vocab references** are **materialized** (first instance: the kind→band table at `src/graph/schema/_generated/ontology.md`, generated by `npm run generate:ontology` from the typed `graph/schema` sources and drift-checked by `npm run check:data-model`, wired into `npm run check`); they are read-only projections locked separately from authored prompt-resource bodies, and prompt resources cite them rather than restating vocabulary (the data-model-legibility frontier owns the expansion to further tables + the authored judgment layer). Current state: [`src/.pi/skills/README.md`](src/.pi/skills/README.md). Resolved 2026-06-18: the capture home is `methods/capture`, absorbing the former `infer-and-capture` method name; the full D80/D81/D82 conduct body remains FE-861. Depends on: D23-L, D25-L, D26-L, D39-L, D40-L, D58-L, D59-L, D65-L, D73-L, D80-L, D81-L. Refines: D25-L, D26-L, D40-L, D58-L, D59-L. Supersedes: `goal` as an AUTO-able manifest/runtime axis (the "objective axes `strategy`, `lens`, and `goal`" triple in D40-L/D58-L/D59-L → two axes, goal inlined); `propose-graph` / `project-graph` as `strategy`-axis members (D25-L/D26-L list them as strategies); treating gap spawn/close as a `review-for-gaps` method responsibility; `infer-and-capture` as a separate method; treating the `capture_*` / candidate / `tool_meta` prompt-resource references as removable fossils; and the 2026-06-19 deferral of Agent Skills `SKILL.md` topology. + Prompt-shape closure (revised 2026-06-22): (a) **`SKILL.md` directory topology is adopted for every strategy/lens/method** because the Agent Skills standard is now the target prompt-resource format; `references/` remains deferred until a concrete skill needs progressive disclosure, and D39-L's code-owned path list remains the availability surface. (b) **`src/agents/prompts//SYSTEM.md`** is adopted for live and named future bodies; no flat agent-body shape remains open. (c) **`[sub]` sub-agent notation** is deferred until the first real delegated sub-agent lands; no empty sub-agent stubs are introduced. (d) **`_generated/` typed-vocab references** are **materialized** (first instance: the kind→band table at `src/graph/schema/_generated/ontology.md`, generated by `npm run generate:ontology` from the typed `graph/schema` sources and drift-checked by `npm run check:data-model`, wired into `npm run check`); they are read-only projections locked separately from authored prompt-resource bodies, and prompt resources cite them rather than restating vocabulary (the data-model-legibility frontier owns the expansion to further tables + the authored judgment layer). Current state: [`src/.pi/skills/README.md`](src/.pi/skills/README.md). Resolved 2026-06-18: the capture home is `methods/capture`, absorbing the former `infer-and-capture` method name; the full D80/D81/D82 conduct body remains FE-861. Depends on: D23-L, D25-L, D26-L, D39-L, D40-L, D58-L, D59-L, D65-L, D73-L, D80-L, D81-L. Refines: D25-L, D26-L, D40-L, D58-L, D59-L. Supersedes: `goal` as an AUTO-able manifest/runtime axis (the "objective axes `strategy`, `lens`, and `goal`" triple in D40-L/D58-L/D59-L → two axes, goal inlined); `propose-graph` / `project-graph` as `strategy`-axis members (D25-L/D26-L list them as strategies); treating gap spawn/close as a `review-for-gaps` method responsibility; `infer-and-capture` as a separate method; treating the `capture_*` / candidate / `tool_meta` prompt-resource references as removable fossils; and the 2026-06-19 deferral of Agent Skills `SKILL.md` topology. Depends on: D19-L, D52-L, D60-L, D62-L, D65-L, D75-L. Refines: D60-L (RENDER stage). Supersedes: the ad-hoc `[bracket]`-header + bullet-list render style as the house convention; hand-rolled markdown and tree string generation in renderers; carrying sessions in the `` cwd render. - **D95-L — Elicitor capability spine: `capture` / `generate` / `project` are the three elicitor capabilities, named over the existing skill axes — not a fourth axis or a re-axis.** The elicitor's work decomposes into three capabilities by what each does to the graph: **capture** commits ground material already present in the transcript tail into graph truth (the D80-L banded sweep + D81-L commitment gradient + D82-L acquisition layer, already specced); **generate** proposes new typed graph expressions on a requested plane from grounding plus a lens frame, fanning candidates out and committing the chosen one through review (D96-L); **project** derives nodes on one plane from a subset/plane of the existing graph with connecting cross-plane edges (e.g. requirements→design, design→oracles, A33-L). This is a capability vocabulary layered over D85-L's `strategy` / `lens` / `method` axes (held frozen, A35-L), not a replacement topology: capture is always-on conduct of every elicitor turn (D85-L move 3), while generate and project are method-routed capabilities requested just-in-time and gated only advisorily by capability-readiness (D74-L/D86-L). Background acquisition subagents (D82-L near-future, A34-L) are the `acquire` arm feeding capture, not a fourth capability. Depends on: D74-L, D80-L, D81-L, D82-L, D85-L, D86-L. Supersedes: the proposed `grounding` / `elicitation` / `projection` lifecycle directories as a replacement skill topology. @@ -403,16 +403,17 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c ### Prompt/runtime profile architecture -- Brunch prompt composition is a **runtime-header + gated prompt-resource manifest** composed per agent by `composeAgentPrompt(...)` in `.pi/extensions/agent-runtime/system-prompts/compose.ts` (D58-L). The direct injection is intentionally small: agent control summary, two-axis runtime state (`strategy` + `lens`), a legal `` resource manifest with per-skill `kind`, `name`, `description`, and `location`, router rules for pinned/AUTO axes, and compact context handles/rendered context blocks. Detailed strategy/lens/method bodies are Brunch-owned markdown resources the agent loads with `read` when needed, while the elicitor objective postures are inline in `src/.pi/agents/elicitor/SYSTEM.md`. The old `src/.pi/context/` prompt-pack layout is retired; the old top-level `src/agents/` host-independent appearance is retired because these agents live only inside the Pi harness. -- Concrete `.pi/{agents,skills}` topology (D52-L). The markdown/code boundary falls on the control-plane/behavior split: enforcement and projection are TypeScript under `.pi/extensions/` (`system-prompts/`, `runtime/`); semantic prompting material is markdown under `.pi/agents/{agent-name}/SYSTEM.md` for live agent bodies and `.pi/skills/`. +- Brunch prompt composition is a **runtime-header + gated prompt-resource manifest** composed per agent by `composeAgentPrompt(...)` in `.pi/extensions/agent-runtime/system-prompts/compose.ts` (D58-L). The direct injection is intentionally small: agent control summary, two-axis runtime state (`strategy` + `lens`), a legal `` resource manifest with per-skill `kind`, `name`, `description`, and `location`, router rules for pinned/AUTO axes, and compact context handles/rendered context blocks. Detailed strategy/lens/method bodies are Brunch-owned markdown resources the agent loads with `read` when needed, while the elicitor objective postures are inline in `src/agents/prompts/elicitor/SYSTEM.md`. The old `src/.pi/context/` prompt-pack layout is retired; top-level `src/agents/` is now the Brunch-owned LLM-context ingress home, not a Pi-only agent tree. +- Concrete `agents/prompts` + `.pi/skills` topology (D52-L). The markdown/code boundary falls on the control-plane/behavior split: enforcement and projection are TypeScript under `.pi/extensions/` (`system-prompts/`, `runtime/`); semantic prompting material is markdown under `agents/prompts/{agent-name}/SYSTEM.md` for live agent bodies and `.pi/skills/`. ```text -src/.pi/ - agents/ - README.md [md] ownership + migration note +src/agents/ + prompts/ + README.md [md] ownership + migration note elicitor/SYSTEM.md [md+] live foreground elicit-mode body - reviewer/SYSTEM.md [md] unwired future review-side body + reviewer/SYSTEM.md [md] background proposal/commitment review body pi-coder/SYSTEM.md [md] unwired Pi coding-agent baseline / future augmentation body +src/.pi/ extensions/ system-prompts/ compose.ts [ts] projection -> runtime header + gated manifest (not a state machine) @@ -429,7 +430,7 @@ src/.pi/ acquisition/readback methods ``` -- Manifest availability is code-owned, not filesystem-discovered: `.pi/extensions/agent-runtime/runtime/state.ts` binds each legal axis value to an explicit `src/.pi/skills///SKILL.md` path and each live agent role to its `SYSTEM.md` location. It loads prompt-resource `name` and `description` from `SKILL.md` frontmatter through pi's loader with `includeDefaults: false` and an explicit `skillPaths` list. `composeAgentPrompt()` emits legal resource bindings; the prompt extension reads the selected agent body explicitly and passes it into the pure composer. This keeps the legal set sealed while making the file body/frontmatter the description source of truth. +- Manifest availability is code-owned, not filesystem-discovered: `.pi/extensions/agent-runtime/runtime/state.ts` binds each legal axis value to an explicit `src/.pi/skills///SKILL.md` path and each live agent role to its `src/agents/prompts//SYSTEM.md` location. It loads prompt-resource `name` and `description` from `SKILL.md` frontmatter through pi's loader with `includeDefaults: false` and an explicit `skillPaths` list. `composeAgentPrompt()` emits legal resource bindings; the prompt extension reads the selected agent body explicitly and passes it into the pure composer. This keeps the legal set sealed while making the file body/frontmatter the description source of truth. - The D60-L agent-context orchestration layer (TypeScript) lives in `.pi/extensions/agent-runtime/system-prompts/seed/` (pushed contexts) and `.pi/extensions/brunch-data/context/` (read tools), surfaced as the header's compact pushed context or via the read tools; reusable text renderers live in `renderers/`, and contexts are not part of the `read`-on-demand resource manifest and carry no `` family. - Workspace **posture** is workspace-scoped product state persisted in `.brunch/workspace.json`, not spec state, session state, or graph truth. D57-L keeps it off the spec row and graph; D58-L composition injects known posture values into the runtime header as an axis of agent influence, and the `capture-posture` goal (D59-L) can confirm or refine those values conversationally. - Readiness is judged just-in-time per requested capability, not as a user-facing workflow stepper, a stored grade, a session-local phase, or a graph-node-kind whitelist. There is no `readiness_grade` on the spec row (D45-L); capability-readiness (D74-L) is evaluated over the relevant `elicitation_gaps`, and D64-L readiness bands describe non-exclusive evidence groupings feeding the readiness-estimate rollup, goal selection, and context filtering. The soft readiness estimate may surface in UI but gates nothing. A future structural milestone gate for export/plan/execute op-modes is deferred until such an op-mode exists; before readiness grows beyond the current tracer, Brunch still needs a real evaluator path for `manual` gaps and a more differentiated per-capability map than the shared grounding floor (A27-L). diff --git a/memory/cards/orchestrator-tool-port--plan-check-tool.md b/memory/cards/orchestrator-tool-port--plan-check-tool.md index 551a19485..604086c5a 100644 --- a/memory/cards/orchestrator-tool-port--plan-check-tool.md +++ b/memory/cards/orchestrator-tool-port--plan-check-tool.md @@ -23,7 +23,7 @@ The execute-mode orchestrator can inspect a cook plan through a product-register - `memory/SPEC.md` — decisions / invariants: D39-L, D40-L, D90-L, D91-L, D92-L, D93-L, I49-L. - `memory/PLAN.md` — frontier: `orchestrator-tool-port`. - `src/.pi/extensions/README.md` — adapter-only ownership and boundary rules. -- `src/.pi/agents/orchestrator/SYSTEM.md` — current execute-mode foreground prompt and stub wording to retire. +- `src/agents/prompts/orchestrator/SYSTEM.md` — current execute-mode foreground prompt and stub wording to retire. - `src/projections/session/runtime-policy.ts` — `execute` foreground roster and blocked direct tool policy. - `src/session/schema/tool-names.ts` — shared tool-name constants. - `/Users/lunelson/Code/hashintel/brunch/ORCHESTRATOR.md` — source CLI behavior and plan format. @@ -61,7 +61,7 @@ No separate spike is cheaper than this slice: the useful proof is whether the pr ✓ Invalid or contract-failing plans return deterministic typed findings/errors without creating `.brunch/cook/runs`, git worktrees, Petrinaut artifacts, or child Pi sessions. ✓ `orchestrator_stub` is no longer advertised to the foreground orchestrator, and the old stub registration path is retired. ✓ `runtime-policy.ts` still blocks direct `bash`, `edit`, and `write` for `execute`, with tests or assertions covering the new tool grant. -✓ `src/.pi/agents/orchestrator/SYSTEM.md` tells the foreground agent to use the real plan-check tool and preserves the no-direct-write instruction. +✓ `src/agents/prompts/orchestrator/SYSTEM.md` tells the foreground agent to use the real plan-check tool and preserves the no-direct-write instruction. ## Verification Approach diff --git a/package.json b/package.json index df22181fd..e365c29fe 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "build": "tsc -p tsconfig.build.json && npm run build:info && npm run build:pi-assets && npm run build:web", "build:info": "node scripts/write-build-info.mjs", "prepack": "RELEASE=true npm run build", - "build:pi-assets": "mkdir -p dist/.pi/components/workspace-dialog dist/.pi/agents dist/.pi/skills dist/.pi/extensions/subagents && cp -R src/.pi/components/workspace-dialog/assets dist/.pi/components/workspace-dialog/ && cp -R src/.pi/agents/elicitor src/.pi/agents/explorer src/.pi/agents/orchestrator src/.pi/agents/pi-coder src/.pi/agents/projector src/.pi/agents/researcher src/.pi/agents/reviewer dist/.pi/agents/ && cp -R src/.pi/skills/strategies src/.pi/skills/lenses src/.pi/skills/methods dist/.pi/skills/ && cp src/.pi/extensions/subagents/config.json dist/.pi/extensions/subagents/", + "build:pi-assets": "mkdir -p dist/.pi/components/workspace-dialog dist/.pi/skills dist/.pi/extensions/subagents dist/agents/prompts && cp -R src/.pi/components/workspace-dialog/assets dist/.pi/components/workspace-dialog/ && cp -R src/agents/prompts/elicitor src/agents/prompts/explorer src/agents/prompts/orchestrator src/agents/prompts/pi-coder src/agents/prompts/projector src/agents/prompts/researcher src/agents/prompts/reviewer dist/agents/prompts/ && cp -R src/.pi/skills/strategies src/.pi/skills/lenses src/.pi/skills/methods dist/.pi/skills/ && cp src/.pi/extensions/subagents/config.json dist/.pi/extensions/subagents/", "build:web": "vite build", "seed": "tsx src/graph/seed-fixtures.ts", "generate:ontology": "tsx src/graph/schema/generate-ontology-ref.ts", diff --git a/src/.pi/README.md b/src/.pi/README.md index d1f97a360..001ac69dc 100644 --- a/src/.pi/README.md +++ b/src/.pi/README.md @@ -2,11 +2,11 @@ SPEC decisions: D25-L, D34-L, D35-L, D37-L, D39-L, D40-L, D52-L, D58-L, D59-L, D60-L, D69-L, D90-L, D91-L -This directory is Brunch's sealed Pi-harness surface. It contains the current markdown files for agent role definitions and Brunch prompt-resource skills, plus product extension registrars and reusable TUI components that run inside the embedded Pi coding-agent harness. Conceptual ownership of Brunch-authored LLM context ingress is moving to `src/agents/`; this tree remains the Pi runtime adapter/file home during the migration. +This directory is Brunch's sealed Pi-harness surface. It contains product extension registrars, reusable TUI components, and the current Brunch prompt-resource skill files that run inside the embedded Pi coding-agent harness. Agent role bodies have moved to `src/agents/prompts/`; this tree remains the Pi runtime adapter/file home during the migration. ## Owns -- The current Pi-facing file home for agent role definitions and runtime prompt-resource skills during the `src/agents/` migration. +- The current Pi-facing file home for runtime prompt-resource skills during the `src/agents/` migration. - Pi extension registration: tools, lifecycle hooks, command handlers, autocomplete, TUI chrome, workspace dialogs, and dev-gated read-only introspection. `extensions/session/lifecycle.ts` adapts Pi session/turn hooks into one ordered Brunch session-boundary pipeline: workspace rebinding first, then continuity preparation steps. `extensions/graph/index.ts` stamps the live watermark carriers for own mutations and full graph-overview reads. - Brunch-owned strategy/lens/method skill files that the agent reads on demand after the runtime manifest advertises them; `src/agents/registry.ts` owns the central path registry. - Reusable Pi TUI components used by those extensions. @@ -27,14 +27,6 @@ This directory is Brunch's sealed Pi-harness surface. It contains the current ma ├── settings.json dev Pi settings for local `.pi` iteration ├── brunch-pi-settings.ts sealed Pi settings/resource-loader policy ├── brunch-pi-extensions.ts explicit Brunch extension factory; no ambient discovery -├── agents/ agent role prompt definitions (markdown only) -│ ├── elicitor/SYSTEM.md -│ ├── explorer/SYSTEM.md -│ ├── orchestrator/SYSTEM.md -│ ├── pi-coder/SYSTEM.md -│ ├── projector/SYSTEM.md -│ ├── researcher/SYSTEM.md -│ └── reviewer/SYSTEM.md ├── skills/ Agent Skills-standard prompt resources read by the agent │ ├── strategies//SKILL.md │ ├── lenses//SKILL.md @@ -47,9 +39,8 @@ This directory is Brunch's sealed Pi-harness surface. It contains the current ma ```pseudo rules: - .pi/agents/ x> TypeScript imports [markdown role definitions only] .pi/skills/ x> TypeScript imports [SKILL.md resources only] - .pi/extensions/ -> agents/, .pi/agents/, .pi/components/, graph/, session/, rpc/ [adapter imports] + .pi/extensions/ -> agents/, .pi/components/, graph/, session/, rpc/ [adapter imports] .pi/extensions/ x> db/ [no direct storage] graph/, session/ x> .pi/ [domain layers never import Pi] ``` diff --git a/src/.pi/__tests__/architecture.test.ts b/src/.pi/__tests__/architecture.test.ts index 6eaf62a5d..c1b7d5903 100644 --- a/src/.pi/__tests__/architecture.test.ts +++ b/src/.pi/__tests__/architecture.test.ts @@ -1,4 +1,4 @@ -import { access, readFile, readdir } from 'node:fs/promises'; +import { readFile, readdir } from 'node:fs/promises'; import { dirname, join, relative } from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -14,42 +14,6 @@ const legacyImportNeedles = [ ['context', 'builders'].join('/'), ]; -const agentDefinitionExpectations = [ - { - system: 'src/.pi/agents/elicitor/SYSTEM.md', - legacyFlat: 'src/.pi/agents/elicitor.md', - needles: ['# Agent: elicitor', 'multi-spec discipline'], - }, - { - system: 'src/.pi/agents/orchestrator/SYSTEM.md', - needles: ['# Agent: orchestrator', 'execute mode'], - }, - { - system: 'src/.pi/agents/reviewer/SYSTEM.md', - legacyFlat: 'src/.pi/agents/reviewer.md', - needles: ['name: reviewer', 'checking candidate'], - }, - { - system: 'src/.pi/agents/explorer/SYSTEM.md', - needles: ['name: explorer', 'read-only reconnaissance agent'], - }, - { - system: 'src/.pi/agents/researcher/SYSTEM.md', - needles: ['name: researcher', 'web-research agent'], - }, - { - system: 'src/.pi/agents/projector/SYSTEM.md', - needles: ['name: projector', 'candidate-proposal'], - }, - { - system: 'src/.pi/agents/pi-coder/SYSTEM.md', - needles: [ - 'expert coding assistant operating inside *brunch*', - 'Show file paths clearly when working with files', - ], - }, -]; - const runtimeRegistryExpectations = [ { file: 'src/session/schema/kinds.ts', @@ -137,18 +101,6 @@ describe('agents topology', () => { expect(probes).toContain('should NOT fire'); }); - it('keeps agent body resources under /SYSTEM.md', async () => { - for (const expectation of agentDefinitionExpectations) { - const content = await readFile(join(projectRoot, expectation.system), 'utf8'); - for (const needle of expectation.needles) { - expect(content).toContain(needle); - } - if (expectation.legacyFlat) { - await expect(access(join(projectRoot, expectation.legacyFlat))).rejects.toThrow(); - } - } - }); - it('keeps named future agent bodies out of the runtime registry', async () => { for (const expectation of runtimeRegistryExpectations) { const content = await readFile(join(projectRoot, expectation.file), 'utf8'); diff --git a/src/.pi/__tests__/prompt-shape-readmes.test.ts b/src/.pi/__tests__/prompt-shape-readmes.test.ts index b3758eb84..eaf58c25e 100644 --- a/src/.pi/__tests__/prompt-shape-readmes.test.ts +++ b/src/.pi/__tests__/prompt-shape-readmes.test.ts @@ -7,10 +7,9 @@ function readRepoFile(relativePath: string): string { return readFileSync(fileURLToPath(new URL(`../../../${relativePath}`, import.meta.url)), 'utf8'); } -describe('prompt-shape decisions', () => { - it('records adopted prompt-skill topology and remaining deferred prompt-shape triggers in canonical READMEs', () => { +describe('prompt-skill shape decisions', () => { + it('records adopted prompt-skill topology and remaining deferred prompt-skill triggers in the local README', () => { const skillsReadme = readRepoFile('src/.pi/skills/README.md'); - const agentsReadme = readRepoFile('src/.pi/agents/README.md'); expect(skillsReadme).toContain('Agent Skills-standard prompt resources'); expect(skillsReadme).toContain('/SKILL.md'); @@ -19,9 +18,5 @@ describe('prompt-shape decisions', () => { expect(skillsReadme).toContain('_generated/ typed-vocab references'); expect(skillsReadme).toContain('concrete citing need appears'); expect(skillsReadme).toContain('drift-checked'); - - expect(agentsReadme).toContain('SYSTEM.md convention is adopted'); - expect(agentsReadme).toContain('Background frontmatter is authoring DX'); - expect(agentsReadme).toContain('Unlisted directories are not spawnable'); }); }); diff --git a/src/.pi/agents/README.md b/src/.pi/agents/README.md deleted file mode 100644 index 53661ca77..000000000 --- a/src/.pi/agents/README.md +++ /dev/null @@ -1,77 +0,0 @@ -# .pi/agents/ — agent role definitions (markdown) - -SPEC decisions: D25-L, D40-L, D58-L, D85-L, D90-L, D91-L, D93-L - -## Owns - -The current markdown file home for keyed agent body resources — the bodies a foreground or background agent contributes as its system-prompt persona. Conceptual ownership is migrating to `src/agents/`; `src/agents/registry.ts` is now the central path registry while the files still use the `src/.pi/agents/{agent-name}/SYSTEM.md` convention. - -```text -agents/ -├── README.md -├── elicitor/ -│ └── SYSTEM.md keyed foreground elicit-mode system-prompt resource -├── explorer/ -│ └── SYSTEM.md keyed background codebase recon body + frontmatter -├── orchestrator/ -│ └── SYSTEM.md keyed foreground execute-mode system-prompt resource -├── pi-coder/ -│ └── SYSTEM.md future unwired coding-agent augmentation baseline -├── projector/ -│ └── SYSTEM.md keyed background candidate-proposal variant body + frontmatter -├── researcher/ -│ └── SYSTEM.md keyed background web-research body + frontmatter -└── reviewer/ - └── SYSTEM.md keyed background proposal/commitment review body + frontmatter -``` - -This directory is **markdown-only**, like `.pi/skills/`. It carries no TypeScript and registers no Pi hooks. Foreground metadata is code-owned in the op-mode-keyed foreground roster (`src/projections/session/runtime-policy.ts`), while agent-body file locations are centralized in `src/agents/registry.ts`. Background metadata is authored as frontmatter but discovered only through the explicit `BACKGROUND_SUBAGENT_IDS` registry in `src/.pi/extensions/subagents/agents.ts`. -Both project into the shared manifest type -(`src/session/schema/agent-manifest.ts`), not filesystem discovery (D39-L/D90-L/D93-L). - -## Prompt-shape decisions - -- **SYSTEM.md convention is adopted:** foreground and background agent bodies use - `src/.pi/agents//SYSTEM.md`; this is no longer an open prompt-shape - residue. -- **Background frontmatter is authoring DX:** background `SYSTEM.md` files carry - `name`/`description`/`tools`/`model`/`thinking`, but the code-owned registry - decides which ids exist. Unlisted directories are not spawnable. Background - bodies are the first section of an assembled child prompt; injected world - snapshots and graph-read tools are owned by `extensions/subagents/`. - -## Does NOT own - -The prompt-assembly machinery that *uses* these definitions now lives with the extension that consumes it; the target owner for Brunch-authored model-facing context is `src/agents/`: - -- **Foreground prompt composition + pushed seed contexts** — - `.pi/extensions/agent-runtime/system-prompts/` (`compose.ts` emits the runtime header + gated - manifest; `seed/workspace.ts` and `seed/graph.ts` render the pushed context - blocks). -- **Background prompt assembly and injected-world child-session wiring** — - `.pi/extensions/subagents/`. -- **Prompt-resource manifest selection + tool/method legality** — - `.pi/extensions/agent-runtime/runtime/` (`state.ts`), fed by the foreground roster in - `src/projections/session/runtime-policy.ts`. -- **Strategy/lens/method prompt-resource skills** — `.pi/skills/`. -- **Reusable lossy text/markdown rendering** — `renderers/`. -- **Pi tool definitions, lifecycle hooks, UI, and background child-session - loading/running** — `.pi/extensions/*`. - -## Migration note - -Until 2026-06, this directory also held `compose.ts`, `state.ts`, and a -`contexts/` render layer. Those moved to the extensions that consume them so the -tree answers "who owns prompt assembly?" by walking to `system-prompts/` and -`runtime/`, and so "context" stops meaning both the pushed prompt seed and the -`read_context` pull tool. The seed renderers were renamed (`renderWorkspaceSeed`, -`renderGraphSeed`) to de-conflate from `renderers/` and the pull tool. - -The D85-L agent-definition convention is enacted for foreground bodies, and D90-L -extends the same home to background bodies: `elicitor/SYSTEM.md`, -`orchestrator/SYSTEM.md`, `explorer/SYSTEM.md`, `researcher/SYSTEM.md`, -`projector/SYSTEM.md`, `reviewer/SYSTEM.md`, and the unwired -`pi-coder/SYSTEM.md` baseline all use `/SYSTEM.md`. The former -`src/.pi/extensions/subagents/agents/*.md` background home and the flat legacy -`reviewer.md` shape are retired. `pi-coder` records Pi's `buildSystemPrompt` -worked-example baseline while D58-L's augment-vs-replace question stays open. diff --git a/src/.pi/extensions/README.md b/src/.pi/extensions/README.md index d6881786f..0f8f7dd05 100644 --- a/src/.pi/extensions/README.md +++ b/src/.pi/extensions/README.md @@ -8,7 +8,7 @@ Pi-facing registration and adaptation only: lifecycle hooks, agent tool definiti ## Does NOT own -- Agent role prompt definitions and skill resource bodies (markdown) — `.pi/agents/` and `.pi/skills/`. Prompt composition and prompt-resource legality live in `agent-runtime/`. +- Agent role prompt definitions and skill resource bodies (markdown) — `agents/prompts/` and `.pi/skills/`. Prompt composition and prompt-resource legality live in `agent-runtime/` until their move slice lands. - Graph truth, graph mutation policy, or graph readers — top-level `graph/`. - Pi JSONL/session semantics, runtime-state projection, workspace coordination, or transcript exchange projection — top-level `session/`, `projections/`, and related domain seams. - Reusable DTO projection or reusable markdown/text rendering — top-level `projections/` and `renderers/`. @@ -51,10 +51,10 @@ extensions/ ```pseudo rules: - .pi/extensions/* -> .pi/agents/, .pi/components/, graph/, session/, projections/, renderers/ [adapter imports allowed] + .pi/extensions/* -> agents/, .pi/components/, graph/, session/, projections/, renderers/ [adapter imports allowed] .pi/extensions/* x> db/ [no direct storage] graph/, session/ x> .pi/ [domain layers never import adapters] - .pi/agents/ x> .pi/extensions/ [prompt assembly does not register Pi hooks] + agents/prompts/ x> .pi/extensions/ [prompt bodies do not register Pi hooks] projections/ x> .pi/, rpc/, app/, web/ [no transport/UI imports] renderers/ x> .pi/, rpc/, app/, web/ [no transport/UI imports] ``` diff --git a/src/.pi/extensions/agent-runtime/runtime/state.test.ts b/src/.pi/extensions/agent-runtime/runtime/state.test.ts index 8418c2bab..3bba30aac 100644 --- a/src/.pi/extensions/agent-runtime/runtime/state.test.ts +++ b/src/.pi/extensions/agent-runtime/runtime/state.test.ts @@ -221,7 +221,7 @@ describe('agent posture policy', () => { it('resolves agent SYSTEM.md bodies through the central agent context registry location', () => { const location = agentBodyResourceLocation('elicitor'); expect(location).toBe(bundledAgentBodyLocation('elicitor')); - expect(location).toMatch(/src\/\.pi\/agents\/elicitor\/SYSTEM\.md$/); + expect(location).toMatch(/src\/agents\/prompts\/elicitor\/SYSTEM\.md$/); const body = readFileSync(location, 'utf8'); expect(body).toContain('# Agent: elicitor'); }); diff --git a/src/.pi/extensions/agent-runtime/system-prompts/__previews__/elicitor--auto-floor-gaps-open.md b/src/.pi/extensions/agent-runtime/system-prompts/__snapshots__/elicitor--auto-floor-gaps-open.md similarity index 98% rename from src/.pi/extensions/agent-runtime/system-prompts/__previews__/elicitor--auto-floor-gaps-open.md rename to src/.pi/extensions/agent-runtime/system-prompts/__snapshots__/elicitor--auto-floor-gaps-open.md index 534f5eb3d..ba766a356 100644 --- a/src/.pi/extensions/agent-runtime/system-prompts/__previews__/elicitor--auto-floor-gaps-open.md +++ b/src/.pi/extensions/agent-runtime/system-prompts/__snapshots__/elicitor--auto-floor-gaps-open.md @@ -1,6 +1,6 @@ # Agent: elicitor -Preview role body from `src/.pi/agents/elicitor/SYSTEM.md`. +Preview role body from `src/agents/prompts/elicitor/SYSTEM.md`. [Brunch agent control] - agent: elicitor diff --git a/src/.pi/extensions/agent-runtime/system-prompts/__previews__/elicitor--auto-high-coverage.md b/src/.pi/extensions/agent-runtime/system-prompts/__snapshots__/elicitor--auto-high-coverage.md similarity index 98% rename from src/.pi/extensions/agent-runtime/system-prompts/__previews__/elicitor--auto-high-coverage.md rename to src/.pi/extensions/agent-runtime/system-prompts/__snapshots__/elicitor--auto-high-coverage.md index f760b5aff..9cb6838e9 100644 --- a/src/.pi/extensions/agent-runtime/system-prompts/__previews__/elicitor--auto-high-coverage.md +++ b/src/.pi/extensions/agent-runtime/system-prompts/__snapshots__/elicitor--auto-high-coverage.md @@ -1,6 +1,6 @@ # Agent: elicitor -Preview role body from `src/.pi/agents/elicitor/SYSTEM.md`. +Preview role body from `src/agents/prompts/elicitor/SYSTEM.md`. [Brunch agent control] - agent: elicitor diff --git a/src/.pi/extensions/agent-runtime/system-prompts/__previews__/elicitor--pinned-strategy-lens.md b/src/.pi/extensions/agent-runtime/system-prompts/__snapshots__/elicitor--pinned-strategy-lens.md similarity index 98% rename from src/.pi/extensions/agent-runtime/system-prompts/__previews__/elicitor--pinned-strategy-lens.md rename to src/.pi/extensions/agent-runtime/system-prompts/__snapshots__/elicitor--pinned-strategy-lens.md index cc2588623..37c18c1b4 100644 --- a/src/.pi/extensions/agent-runtime/system-prompts/__previews__/elicitor--pinned-strategy-lens.md +++ b/src/.pi/extensions/agent-runtime/system-prompts/__snapshots__/elicitor--pinned-strategy-lens.md @@ -1,6 +1,6 @@ # Agent: elicitor -Preview role body from `src/.pi/agents/elicitor/SYSTEM.md`. +Preview role body from `src/agents/prompts/elicitor/SYSTEM.md`. [Brunch agent control] - agent: elicitor diff --git a/src/.pi/extensions/agent-runtime/system-prompts/__previews__/elicitor--pushed-context.md b/src/.pi/extensions/agent-runtime/system-prompts/__snapshots__/elicitor--pushed-context.md similarity index 98% rename from src/.pi/extensions/agent-runtime/system-prompts/__previews__/elicitor--pushed-context.md rename to src/.pi/extensions/agent-runtime/system-prompts/__snapshots__/elicitor--pushed-context.md index e124d86d2..69a2e11bd 100644 --- a/src/.pi/extensions/agent-runtime/system-prompts/__previews__/elicitor--pushed-context.md +++ b/src/.pi/extensions/agent-runtime/system-prompts/__snapshots__/elicitor--pushed-context.md @@ -1,6 +1,6 @@ # Agent: elicitor -Preview role body from `src/.pi/agents/elicitor/SYSTEM.md`. +Preview role body from `src/agents/prompts/elicitor/SYSTEM.md`. [Brunch agent control] - agent: elicitor diff --git a/src/.pi/extensions/agent-runtime/system-prompts/__tests__/compose.test.ts b/src/.pi/extensions/agent-runtime/system-prompts/__tests__/compose.test.ts index 1086ae371..ecf658d0d 100644 --- a/src/.pi/extensions/agent-runtime/system-prompts/__tests__/compose.test.ts +++ b/src/.pi/extensions/agent-runtime/system-prompts/__tests__/compose.test.ts @@ -373,7 +373,7 @@ describe('composeAgentPrompt', () => { // ── COMPOSE-stage prompt golden previews ────────────────────────────────────── // Each case composes a fixture runtime state, selected-spec gaps, workspace // posture, and optional rendered context strings, then locks the full -// provider-facing prompt under the sibling `__previews__/`. The locked file IS +// provider-facing prompt under the sibling `__snapshots__/`. The locked file IS // the wording assertion: review the diff when output changes, accept with // `--update` only after human approval. Inline asserts stay limited to // cross-cutting contract invariants a careless snapshot update could hide: @@ -436,7 +436,7 @@ function composePreviewPrompt(input: Partial = {}): str workspace: previewWorkspace, activeTools: ['read', 'grep', 'find', 'ls', 'present_question', 'request_response'], gaps: previewFloorGaps(0), - agentBody: '# Agent: elicitor\n\nPreview role body from `src/.pi/agents/elicitor/SYSTEM.md`.', + agentBody: '# Agent: elicitor\n\nPreview role body from `src/agents/prompts/elicitor/SYSTEM.md`.', ...input, }).prompt; } @@ -459,13 +459,13 @@ function expectPromptContracts(rendered: string): void { describe('composeAgentPrompt previews', () => { it('elicitor--auto-floor-gaps-open: all axes AUTO, floor gaps open', async () => { const rendered = normalizeRepoPaths(composePreviewPrompt()); - await expect(rendered).toMatchFileSnapshot('../__previews__/elicitor--auto-floor-gaps-open.md'); + await expect(rendered).toMatchFileSnapshot('../__snapshots__/elicitor--auto-floor-gaps-open.md'); expectPromptContracts(rendered); }); it('elicitor--auto-high-coverage: all axes AUTO, gaps largely answered', async () => { const rendered = normalizeRepoPaths(composePreviewPrompt({ gaps: previewFloorGaps(1) })); - await expect(rendered).toMatchFileSnapshot('../__previews__/elicitor--auto-high-coverage.md'); + await expect(rendered).toMatchFileSnapshot('../__snapshots__/elicitor--auto-high-coverage.md'); expectPromptContracts(rendered); }); @@ -492,7 +492,7 @@ describe('composeAgentPrompt previews', () => { }), ); - await expect(rendered).toMatchFileSnapshot('../__previews__/elicitor--pinned-strategy-lens.md'); + await expect(rendered).toMatchFileSnapshot('../__snapshots__/elicitor--pinned-strategy-lens.md'); expectPromptContracts(rendered); expect(rendered).toContain('step-wise-disambiguate'); expect(rendered).toContain('design'); @@ -511,7 +511,7 @@ describe('composeAgentPrompt previews', () => { }), ); - await expect(rendered).toMatchFileSnapshot('../__previews__/elicitor--pushed-context.md'); + await expect(rendered).toMatchFileSnapshot('../__snapshots__/elicitor--pushed-context.md'); expectPromptContracts(rendered); expect(rendered).toContain('[fixture rendered context: selected-spec graph overview]'); expect(rendered).toContain('[fixture rendered context: recent transcript]'); diff --git a/src/.pi/extensions/subagents/README.md b/src/.pi/extensions/subagents/README.md index 021017449..a76a0f203 100644 --- a/src/.pi/extensions/subagents/README.md +++ b/src/.pi/extensions/subagents/README.md @@ -93,12 +93,12 @@ context that crosses back to the parent; structured `details` remain render-only | File | Responsibility | | --- | --- | -| [`agents.ts`](./agents.ts) | Markdown agent loader: tiny frontmatter parser (no YAML dep), TypeBox-validated schema (`name`, `description`, `tools`, `model`, `thinking`), explicit `BACKGROUND_SUBAGENT_IDS` registry, `loadSubagentDefinitions(dir, ids?)` over `src/.pi/agents//SYSTEM.md` → `Map`. Projects frontmatter into the shared `AgentManifest` background shape and fails loud on malformed/duplicate/id-drifted agents. | +| [`agents.ts`](./agents.ts) | Markdown agent loader: tiny frontmatter parser (no YAML dep), TypeBox-validated schema (`name`, `description`, `tools`, `model`, `thinking`), explicit `BACKGROUND_SUBAGENT_IDS` registry, `loadSubagentDefinitions(dir, ids?)` over `src/agents/prompts//SYSTEM.md` → `Map`. Projects frontmatter into the shared `AgentManifest` background shape and fails loud on malformed/duplicate/id-drifted agents. | | [`config.ts`](./config.ts) | TypeBox loader for [`config.json`](./config.json) (`version`, `maxConcurrency`; tolerates `$comment`). | | [`prompt-assembly.ts`](./prompt-assembly.ts) | Background prompt assembler: agent body + child-control header + injected world snapshot + `` + background router rules. Reuses the shared prompt-skill manifest renderer; deliberately omits the foreground elicitation recommendation block. | | [`session.ts`](./session.ts) | The sealed child-session runner. `resolveSubagentModel`, `createSubagentToolCatalog`, `planSubagentTools`, `runSubagent`. The catalog is the shared source that resolves sovereign manifest-authored grants. Never throws — failures return as error results. **Injectable SDK builders** (`createServices`/`createSession`) for testing. | | [`index.ts`](./index.ts) | `registerBrunchSubagents(pi, deps)` — registers the one `subagent` tool (single `{agent,task}` or parallel `{tasks:[…]}`), filters advertisement/execution to `definitions ∩ deps.delegatableAgents`, `createSemaphore` for bounded concurrency, result formatting. Re-exports the public surface. | -| [`../../agents//SYSTEM.md`](../../agents) | Declarative foreground/background agent body home. Background bodies carry frontmatter; `agents.ts` loads only registry-listed ids. | +| [`../../../agents/prompts//SYSTEM.md`](../../../agents/prompts) | Declarative foreground/background agent body home. Background bodies carry frontmatter; `agents.ts` loads only registry-listed ids. | | [`config.json`](./config.json) | Externalized concurrency cap (`maxConcurrency: 4`). | | [`subagents.test.ts`](./subagents.test.ts) | Tests parsing, config, model resolution, tool planning, semaphore fairness, registrar usage errors, abort lifecycle, and **two end-to-end faux-provider child-session runs** asserting the sealing invariants. | | [`../../../app/pi-subagents.ts`](../../../app/pi-subagents.ts) | **App composition root.** `loadBrunchSubagents({cwd, agentDir, delegatableAgents, world})` assembles `BrunchSubagentsDeps` using the sealed `pi-settings` helpers plus explicit parent-world handles and the code-owned op-mode delegatable set. Keeps `.pi/` free of `src/app` imports (deps are injected). | @@ -107,7 +107,7 @@ Boundary rule: `.pi/extensions/subagents/*` may import the SDK and `../web/` (for `web_search`/`web_fetch`), but **never** `src/app/*`. The app layer injects the sealed primitives. -## Agent definitions (`src/.pi/agents//SYSTEM.md`) +## Agent definitions (`src/agents/prompts//SYSTEM.md`) Frontmatter is the background-agent authoring contract; the code-owned `BACKGROUND_SUBAGENT_IDS` list is the registry. The markdown body is the first @@ -193,7 +193,7 @@ and carry a depth/allowlist bound; pairs naturally with the future write-capable | Aspect | Original | Brunch (this) | | --- | --- | --- | -| Agent discovery | Bundled `agents/*.md` beside `index.ts` **+** `globalThis.__pi_subagents` runtime bridge for other extensions | Unified `src/.pi/agents//SYSTEM.md` home via explicit `BACKGROUND_SUBAGENT_IDS` → `loadSubagentDefinitions(dir, ids?)`; **no** bridge, **no** ambient `~/.pi` scan, and no directory scan | +| Agent discovery | Bundled `agents/*.md` beside `index.ts` **+** `globalThis.__pi_subagents` runtime bridge for other extensions | Unified `src/agents/prompts//SYSTEM.md` home via explicit `BACKGROUND_SUBAGENT_IDS` → `loadSubagentDefinitions(dir, ids?)`; **no** bridge, **no** ambient `~/.pi` scan, and no directory scan | | Frontmatter | Loose: string split + silent defaults; extra `subagent_agents` allowlist; `model` default `anthropic/claude-sonnet-4-6` | Strict TypeBox schema, **fails loud**; no `subagent_agents` (no nesting); `model: default` inherits parent | | Execution | `spawn()` a child `pi` process (`--mode json -p --no-session --no-skills --no-extensions`, re-adds `--extension` paths, `--append-system-prompt` temp file) | In-process SDK `AgentSession` with sealed services | | Isolation basis | OS process boundary + flags; depends on a resolvable `pi` binary on PATH | Sealed in-memory services; no binary, no ambient leakage | diff --git a/src/.pi/extensions/subagents/agents.ts b/src/.pi/extensions/subagents/agents.ts index 05e607097..ab39e8c55 100644 --- a/src/.pi/extensions/subagents/agents.ts +++ b/src/.pi/extensions/subagents/agents.ts @@ -2,7 +2,7 @@ * Subagent agent definitions (D44-L / D90-L). * * Background agents are declarative SYSTEM.md files under the shared - * `src/.pi/agents//` body home. Each file carries a small frontmatter block + * `src/agents/prompts//` body home. Each file carries a small frontmatter block * plus a system-prompt body. The frontmatter is the registry contract; the body * is the subagent's standing instructions and the first section of the assembled * child prompt. Frontmatter is validated through a TypeBox schema (D41-L) so a diff --git a/src/README.md b/src/README.md index 49529c5fe..2e6909eb6 100644 --- a/src/README.md +++ b/src/README.md @@ -1,6 +1,6 @@ # src/ — Brunch source topology -Decision D52-L in `memory/SPEC.md` locks the target layout. The current LLM-context ingress refactor introduces `agents/` as the Pi-independent owner for Brunch-authored agent context; its registry still points at existing `.pi` prompt/skill files until the move slices land. +Decision D52-L in `memory/SPEC.md` locks the target layout. The current LLM-context ingress refactor introduces `agents/` as the Pi-independent owner for Brunch-authored agent context; agent prompt bodies now live there, while prompt-resource skills still live under `.pi` until their move slice lands. ```text src/ @@ -9,10 +9,9 @@ src/ ├── scripts/ Local executable utilities │ ├── agents/ Pi-independent owner for Brunch-authored LLM context ingress -│ (currently central path registry; content moves later) +│ └── prompts/ agent role body markdown resources │ ├── .pi/ Sealed Pi-harness runtime surface -│ ├── agents/ current markdown body file home during migration │ ├── skills/ current prompt-resource file home during migration │ ├── components/ reusable Pi TUI/message components │ └── extensions/ Pi registrars: tools, hooks, commands, TUI affordances @@ -46,7 +45,7 @@ rules: workspace/ -> constants/ or workspace-local files only projections/* -> agents/, graph/, session/, workspace/ [read/domain imports allowed; agents/ is temporary registry edge] renderers/* -> projections/, graph/, session/, workspace/ as needed for input types - agents/ -> .pi/agents/, .pi/skills/ [current migration registry only] + agents/ -> .pi/skills/ [current prompt-resource registry only] .pi/ -> agents/, graph/, session/, projections/, renderers/ [Pi runtime adapters/resources] rpc/ -> graph/, session/, projections/, renderers/ app/ -> graph/, session/, projections/, renderers/ @@ -60,10 +59,10 @@ Rules: - `workspace/` owns cwd-scoped identity, inventory, and workspace default-state persistence. It must not import Pi, session, graph, DB, projection, renderer, adapter, transport, app, or web modules. - `graph/` imports from `db/`. No other layer imports `db/` directly. -- `agents/` owns the Brunch-authored LLM-context ingress seam. Today it centralizes the file registry for prompt bodies and prompt-resource skills; later slices move the content/rendering owners under this seam. The current `projections/session/runtime-policy.ts` import of this registry is a migration edge only: once runtime policy moves under `agents/runtime/`, projections should stop depending on `agents/`. -- `.pi/` owns Pi-harness extensions/components and temporarily hosts the existing markdown prompt/skill files while the LLM-context ingress refactor proceeds. +- `agents/` owns the Brunch-authored LLM-context ingress seam. Today it hosts agent prompt bodies and centralizes the file registry for prompt bodies and prompt-resource skills; later slices move prompt-resource skills, runtime composition, seeds, and agent-visible rendering under this seam. The current `projections/session/runtime-policy.ts` import of this registry is a migration edge only: once runtime policy moves under `agents/runtime/`, projections should stop depending on `agents/`. +- `.pi/` owns Pi-harness extensions/components and temporarily hosts the existing prompt-resource skill files while the LLM-context ingress refactor proceeds. - `.pi/extensions/` registers Pi tools/hooks/UI affordances and delegates product semantics outward. -- `.pi/agents/` and `.pi/skills/` are current file homes, not the long-term conceptual owner, for Brunch-authored prompt bodies and read-on-demand markdown resources. +- `.pi/skills/` is the current file home, not the long-term conceptual owner, for Brunch-authored read-on-demand markdown resources. - `projections/` owns reusable structured output; `renderers/` owns reusable lossy text output. - `web/` is a separate Vite build target. @@ -77,4 +76,4 @@ The old domain-local `src/{graph,session,structured-exchange}/format/` folders a Runtime-state transcript entry facts live in `session/runtime-state.ts`; reusable flattened runtime-state projection/policy now lives in `projections/session/runtime-state.ts` and `projections/session/runtime-policy.ts`. -The earlier `src/agents/` top-level prompt subtree had moved under `src/.pi/{agents,skills}/`; the new `src/agents/` seam reclaims the name for Pi-independent LLM context ingress. It starts with a registry that points at the existing `.pi` files so the move can proceed byte-stably. The old `src/.pi/context/` prompt-pack subtree remains retired. +The earlier `src/agents/` top-level prompt subtree had moved under `src/.pi/{agents,skills}/`; the new `src/agents/` seam reclaims the name for Pi-independent LLM context ingress. Agent bodies have moved to `src/agents/prompts/`; prompt-resource skills still move later. The old `src/.pi/context/` prompt-pack subtree remains retired. diff --git a/src/agents/README.md b/src/agents/README.md index fa901b8bd..6c6a5ef03 100644 --- a/src/agents/README.md +++ b/src/agents/README.md @@ -4,12 +4,13 @@ SPEC decisions: D39-L, D40-L, D52-L, D60-L, D85-L, D90-L, D91-L, D93-L ## Owns -`src/agents/` is the Pi-independent home for Brunch-authored model-facing context. In the current migration slice it owns only the central registry for bundled agent bodies and Brunch prompt-resource skill paths; the markdown files still live under `src/.pi/` until the move slices land. +`src/agents/` is the Pi-independent home for Brunch-authored model-facing context. It now owns bundled agent prompt bodies plus the central registry for body and Brunch prompt-resource skill paths; prompt-resource skills still live under `src/.pi/` until their move slice lands. ```text agents/ ├── README.md -├── registry.ts current path registry for bundled agent bodies and prompt-resource skills +├── prompts/ bundled foreground/background agent body markdown +├── registry.ts path registry for bundled agent bodies and current prompt-resource skills └── __tests__/ registry/topology tests ``` @@ -17,8 +18,8 @@ agents/ ```pseudo rules: - agents/registry.ts -> .pi/agents/*/SYSTEM.md [current body file locations] - agents/registry.ts -> .pi/skills/*/*/SKILL.md [current prompt-resource locations] + agents/registry.ts -> agents/prompts/*/SYSTEM.md [body file locations] + agents/registry.ts -> .pi/skills/*/*/SKILL.md [current prompt-resource locations] .pi/extensions/* -> agents/registry.ts [adapters ask for Brunch-authored context locations] projections/session/runtime-policy.ts -> agents/registry.ts [temporary roster-location edge] agents/ x> Pi extension hooks [no registration side effects] @@ -26,4 +27,4 @@ rules: ## Migration note -This directory is intentionally thin right now. It establishes the owner for LLM context ingress without moving bytes in the same slice. Later slices move prompt bodies, skills, prompt composition, seed context, runtime policy, and agent-visible renderers here; Pi extensions remain runtime adapters that register hooks/tools and call this layer. +This directory is intentionally mid-migration. Agent prompt bodies have moved here byte-stably. Later slices move skills, prompt composition, seed context, runtime policy, and agent-visible renderers here; Pi extensions remain runtime adapters that register hooks/tools and call this layer. diff --git a/src/agents/__tests__/registry.test.ts b/src/agents/__tests__/registry.test.ts index dc560146e..fa5c92b99 100644 --- a/src/agents/__tests__/registry.test.ts +++ b/src/agents/__tests__/registry.test.ts @@ -10,7 +10,7 @@ import { } from '../registry.js'; describe('agent context registry', () => { - it('centralizes current bundled prompt and skill paths while the files still live under .pi', () => { + it('centralizes bundled prompt and current skill paths', () => { expect(BUNDLED_AGENT_BODY_IDS).toEqual([ 'elicitor', 'orchestrator', @@ -20,9 +20,9 @@ describe('agent context registry', () => { 'reviewer', 'pi-coder', ]); - expect(bundledAgentBodyRepoPath('elicitor')).toBe('src/.pi/agents/elicitor/SYSTEM.md'); - expect(bundledAgentBodyLocation('reviewer')).toMatch(/src\/\.pi\/agents\/reviewer\/SYSTEM\.md$/); - expect(bundledAgentBodyHome()).toMatch(/src\/\.pi\/agents$/); + expect(bundledAgentBodyRepoPath('elicitor')).toBe('src/agents/prompts/elicitor/SYSTEM.md'); + expect(bundledAgentBodyLocation('reviewer')).toMatch(/src\/agents\/prompts\/reviewer\/SYSTEM\.md$/); + expect(bundledAgentBodyHome()).toMatch(/src\/agents\/prompts$/); expect(promptResourceLocation('methods', 'generate-proposal')).toMatch( /src\/\.pi\/skills\/methods\/generate-proposal\/SKILL\.md$/, ); diff --git a/src/agents/prompts/README.md b/src/agents/prompts/README.md new file mode 100644 index 000000000..d02630585 --- /dev/null +++ b/src/agents/prompts/README.md @@ -0,0 +1,39 @@ +# agents/prompts/ — agent role bodies + +SPEC decisions: D25-L, D40-L, D58-L, D85-L, D90-L, D91-L, D93-L + +## Owns + +Keyed foreground and background agent body resources — the markdown persona text a Brunch agent contributes to its system prompt. + +```text +prompts/ +├── README.md +├── elicitor/SYSTEM.md foreground elicit-mode body +├── orchestrator/SYSTEM.md foreground execute-mode body +├── explorer/SYSTEM.md background codebase recon body + frontmatter +├── researcher/SYSTEM.md background web-research body + frontmatter +├── projector/SYSTEM.md background candidate-proposal body + frontmatter +├── reviewer/SYSTEM.md background proposal/commitment review body + frontmatter +└── pi-coder/SYSTEM.md future unwired coding-agent augmentation baseline +``` + +This directory is markdown-only. It carries no TypeScript and registers no Pi hooks. Foreground metadata is code-owned in the op-mode-keyed foreground roster (`src/projections/session/runtime-policy.ts`), while body file locations are centralized in `src/agents/registry.ts`. Background metadata is authored as frontmatter but discovered only through the explicit `BACKGROUND_SUBAGENT_IDS` registry in `src/.pi/extensions/subagents/agents.ts`. + +## Prompt-shape decisions + +- **SYSTEM.md convention is adopted:** foreground and background agent bodies use `src/agents/prompts//SYSTEM.md`. +- **Background frontmatter is authoring DX:** background `SYSTEM.md` files carry `name`/`description`/`tools`/`model`/`thinking`, but the code-owned registry decides which ids exist. Unlisted directories are not spawnable. + +## Does NOT own + +- Foreground prompt composition + pushed seed contexts — `.pi/extensions/agent-runtime/system-prompts/` until the runtime/context move slices land. +- Background prompt assembly and injected-world child-session wiring — `.pi/extensions/subagents/`. +- Prompt-resource manifest selection + tool/method legality — `.pi/extensions/agent-runtime/runtime/` and `src/projections/session/runtime-policy.ts` until the runtime move slice lands. +- Strategy/lens/method prompt-resource skills — currently `.pi/skills/`, moving in a later slice. +- Reusable lossy text/markdown rendering — `renderers/` until agent-visible renderers move. +- Pi tool definitions, lifecycle hooks, UI, and background child-session loading/running — `.pi/extensions/*`. + +## Migration note + +This directory is the first moved content home under `src/agents/`. Pi extension code remains a runtime adapter: it loads foreground bodies and background agent definitions through `src/agents/registry.ts`, not through extension-local paths or directory discovery. `pi-coder` records Pi's `buildSystemPrompt` worked-example baseline while D58-L's augment-vs-replace question stays open. diff --git a/src/agents/prompts/__tests__/prompt-bodies.test.ts b/src/agents/prompts/__tests__/prompt-bodies.test.ts new file mode 100644 index 000000000..1df91287f --- /dev/null +++ b/src/agents/prompts/__tests__/prompt-bodies.test.ts @@ -0,0 +1,65 @@ +import { access, readFile } from 'node:fs/promises'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { describe, expect, it } from 'vitest'; + +const projectRoot = dirname(dirname(dirname(dirname(dirname(fileURLToPath(import.meta.url)))))); + +const agentDefinitionExpectations = [ + { + system: 'src/agents/prompts/elicitor/SYSTEM.md', + legacyFlat: 'src/.pi/agents/elicitor.md', + needles: ['# Agent: elicitor', 'multi-spec discipline'], + }, + { + system: 'src/agents/prompts/orchestrator/SYSTEM.md', + needles: ['# Agent: orchestrator', 'execute mode'], + }, + { + system: 'src/agents/prompts/reviewer/SYSTEM.md', + legacyFlat: 'src/.pi/agents/reviewer.md', + needles: ['name: reviewer', 'checking candidate'], + }, + { + system: 'src/agents/prompts/explorer/SYSTEM.md', + needles: ['name: explorer', 'read-only reconnaissance agent'], + }, + { + system: 'src/agents/prompts/researcher/SYSTEM.md', + needles: ['name: researcher', 'web-research agent'], + }, + { + system: 'src/agents/prompts/projector/SYSTEM.md', + needles: ['name: projector', 'candidate-proposal'], + }, + { + system: 'src/agents/prompts/pi-coder/SYSTEM.md', + needles: [ + 'expert coding assistant operating inside *brunch*', + 'Show file paths clearly when working with files', + ], + }, +]; + +describe('agent prompt bodies', () => { + it('keeps agent body resources under src/agents/prompts//SYSTEM.md', async () => { + for (const expectation of agentDefinitionExpectations) { + const content = await readFile(join(projectRoot, expectation.system), 'utf8'); + for (const needle of expectation.needles) { + expect(content).toContain(needle); + } + if (expectation.legacyFlat) { + await expect(access(join(projectRoot, expectation.legacyFlat))).rejects.toThrow(); + } + } + }); + + it('records the adopted body topology in the local README', async () => { + const readme = await readFile(join(projectRoot, 'src/agents/prompts/README.md'), 'utf8'); + + expect(readme).toContain('SYSTEM.md convention is adopted'); + expect(readme).toContain('Background frontmatter is authoring DX'); + expect(readme).toContain('Unlisted directories are not spawnable'); + }); +}); diff --git a/src/.pi/agents/elicitor/SYSTEM.md b/src/agents/prompts/elicitor/SYSTEM.md similarity index 100% rename from src/.pi/agents/elicitor/SYSTEM.md rename to src/agents/prompts/elicitor/SYSTEM.md diff --git a/src/.pi/agents/explorer/SYSTEM.md b/src/agents/prompts/explorer/SYSTEM.md similarity index 100% rename from src/.pi/agents/explorer/SYSTEM.md rename to src/agents/prompts/explorer/SYSTEM.md diff --git a/src/.pi/agents/orchestrator/SYSTEM.md b/src/agents/prompts/orchestrator/SYSTEM.md similarity index 100% rename from src/.pi/agents/orchestrator/SYSTEM.md rename to src/agents/prompts/orchestrator/SYSTEM.md diff --git a/src/.pi/agents/pi-coder/SYSTEM.md b/src/agents/prompts/pi-coder/SYSTEM.md similarity index 100% rename from src/.pi/agents/pi-coder/SYSTEM.md rename to src/agents/prompts/pi-coder/SYSTEM.md diff --git a/src/.pi/agents/projector/SYSTEM.md b/src/agents/prompts/projector/SYSTEM.md similarity index 100% rename from src/.pi/agents/projector/SYSTEM.md rename to src/agents/prompts/projector/SYSTEM.md diff --git a/src/.pi/agents/researcher/SYSTEM.md b/src/agents/prompts/researcher/SYSTEM.md similarity index 100% rename from src/.pi/agents/researcher/SYSTEM.md rename to src/agents/prompts/researcher/SYSTEM.md diff --git a/src/.pi/agents/reviewer/SYSTEM.md b/src/agents/prompts/reviewer/SYSTEM.md similarity index 100% rename from src/.pi/agents/reviewer/SYSTEM.md rename to src/agents/prompts/reviewer/SYSTEM.md diff --git a/src/agents/registry.ts b/src/agents/registry.ts index 9d4529166..d0cfdc11e 100644 --- a/src/agents/registry.ts +++ b/src/agents/registry.ts @@ -13,18 +13,18 @@ export const BUNDLED_AGENT_BODY_IDS = [ export type BundledAgentBodyId = (typeof BUNDLED_AGENT_BODY_IDS)[number]; export type PromptResourceFamily = 'strategies' | 'lenses' | 'methods'; -/** Current filesystem home for bundled Brunch agent markdown bodies. */ +/** Filesystem home for bundled Brunch agent markdown bodies. */ export function bundledAgentBodyHome(): string { - return fileURLToPath(new URL('../.pi/agents', import.meta.url)); + return fileURLToPath(new URL('./prompts', import.meta.url)); } /** Repo-relative path used by manifest bodies that are read later by the Pi runtime. */ export function bundledAgentBodyRepoPath(id: BundledAgentBodyId): string { - return `src/.pi/agents/${id}/SYSTEM.md`; + return `src/agents/prompts/${id}/SYSTEM.md`; } export function bundledAgentBodyLocation(id: BundledAgentBodyId): string { - return fileURLToPath(new URL(`../.pi/agents/${id}/SYSTEM.md`, import.meta.url)); + return fileURLToPath(new URL(`./prompts/${id}/SYSTEM.md`, import.meta.url)); } /** Agent directory passed to Pi's Agent Skills loader for Brunch prompt resources. */ From 45376a82b4efcf6a4cf7cd9f3db8dbf74289bea0 Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Thu, 25 Jun 2026 18:23:28 +0200 Subject: [PATCH 12/54] Move prompt resources under agents --- memory/PLAN.md | 2 +- memory/REFACTOR.md | 2 +- memory/SPEC.md | 29 ++++--- package.json | 2 +- src/.pi/README.md | 9 +- src/.pi/__tests__/architecture.test.ts | 66 +------------- .../__tests__/prompt-shape-readmes.test.ts | 22 ----- src/.pi/extensions/README.md | 2 +- .../elicitor--auto-floor-gaps-open.md | 24 ++--- .../elicitor--auto-high-coverage.md | 30 +++---- .../elicitor--pinned-strategy-lens.md | 24 ++--- .../__snapshots__/elicitor--pushed-context.md | 24 ++--- .../system-prompts/__tests__/compose.test.ts | 6 +- src/README.md | 15 ++-- src/agents/README.md | 9 +- src/agents/__tests__/registry.test.ts | 4 +- src/agents/prompts/README.md | 2 +- src/agents/registry.ts | 4 +- src/{.pi => agents}/skills/README.md | 14 +-- .../__fixtures__/unlisted-fixture/SKILL.md | 2 +- .../skills/__tests__/prompt-resources.test.ts | 87 +++++++++++++++++++ src/{.pi => agents}/skills/lenses/README.md | 0 .../skills/lenses/design/SKILL.md | 0 .../skills/lenses/intent/SKILL.md | 0 .../skills/lenses/oracle/SKILL.md | 0 .../skills/methods/capture/SKILL.md | 0 .../skills/methods/commit-graph/SKILL.md | 0 .../methods/elicit-by-question/SKILL.md | 0 .../methods/explore-and-characterize/SKILL.md | 0 .../skills/methods/generate-proposal/SKILL.md | 0 .../methods/generate-proposal/probes.md | 0 .../generate-proposal/references/design.md | 0 .../generate-proposal/references/intent.md | 0 .../generate-proposal/references/oracle.md | 0 .../skills/methods/ingest-paste/SKILL.md | 0 .../skills/methods/read-context/SKILL.md | 0 .../read-referenced-documents/SKILL.md | 0 .../skills/methods/review-for-gaps/SKILL.md | 0 .../methods/run-structured-exchange/SKILL.md | 0 .../skills/strategies/README.md | 0 .../skills/strategies/freestyle/SKILL.md | 0 .../step-wise-decision-tree/SKILL.md | 0 .../step-wise-disambiguate/SKILL.md | 0 .../generate-fan-out-witness.test.ts | 10 +-- src/graph/README.md | 2 +- src/graph/schema/generate-ontology-ref.ts | 2 +- 46 files changed, 194 insertions(+), 199 deletions(-) delete mode 100644 src/.pi/__tests__/prompt-shape-readmes.test.ts rename src/{.pi => agents}/skills/README.md (82%) rename src/{.pi => agents}/skills/__fixtures__/unlisted-fixture/SKILL.md (50%) create mode 100644 src/agents/skills/__tests__/prompt-resources.test.ts rename src/{.pi => agents}/skills/lenses/README.md (100%) rename src/{.pi => agents}/skills/lenses/design/SKILL.md (100%) rename src/{.pi => agents}/skills/lenses/intent/SKILL.md (100%) rename src/{.pi => agents}/skills/lenses/oracle/SKILL.md (100%) rename src/{.pi => agents}/skills/methods/capture/SKILL.md (100%) rename src/{.pi => agents}/skills/methods/commit-graph/SKILL.md (100%) rename src/{.pi => agents}/skills/methods/elicit-by-question/SKILL.md (100%) rename src/{.pi => agents}/skills/methods/explore-and-characterize/SKILL.md (100%) rename src/{.pi => agents}/skills/methods/generate-proposal/SKILL.md (100%) rename src/{.pi => agents}/skills/methods/generate-proposal/probes.md (100%) rename src/{.pi => agents}/skills/methods/generate-proposal/references/design.md (100%) rename src/{.pi => agents}/skills/methods/generate-proposal/references/intent.md (100%) rename src/{.pi => agents}/skills/methods/generate-proposal/references/oracle.md (100%) rename src/{.pi => agents}/skills/methods/ingest-paste/SKILL.md (100%) rename src/{.pi => agents}/skills/methods/read-context/SKILL.md (100%) rename src/{.pi => agents}/skills/methods/read-referenced-documents/SKILL.md (100%) rename src/{.pi => agents}/skills/methods/review-for-gaps/SKILL.md (100%) rename src/{.pi => agents}/skills/methods/run-structured-exchange/SKILL.md (100%) rename src/{.pi => agents}/skills/strategies/README.md (100%) rename src/{.pi => agents}/skills/strategies/freestyle/SKILL.md (100%) rename src/{.pi => agents}/skills/strategies/step-wise-decision-tree/SKILL.md (100%) rename src/{.pi => agents}/skills/strategies/step-wise-disambiguate/SKILL.md (100%) diff --git a/memory/PLAN.md b/memory/PLAN.md index 5de35b99b..342c30d65 100644 --- a/memory/PLAN.md +++ b/memory/PLAN.md @@ -155,7 +155,7 @@ context-pipeline/ - Skill bodies cite the new home (D97-L); inlined heuristic copies collapse to one cite-target. - ✓ A drift guard (`check:data-model`, mirroring `check:skills`, wired into `npm run check`) fails if the generated reference diverges from the typed sources. - If `ln-design` splits this into recover-doc / build-generator / subtypes-remodel frontiers, create a `data-model-legibility` arc per §Initiatives. -- **Traceability:** D73-L (domain owns vocabulary), D88-L (`detail` form union), D97-L (heuristic provenance); un-defers the `_generated/` deferral in [`src/.pi/skills/README.md`](src/.pi/skills/README.md); relates to `elicitor-project` (A33-L, shared D97-L rule). +- **Traceability:** D73-L (domain owns vocabulary), D88-L (`detail` form union), D97-L (heuristic provenance); un-defers the `_generated/` deferral in [`src/agents/skills/README.md`](src/agents/skills/README.md); relates to `elicitor-project` (A33-L, shared D97-L rule). ### renderer-golden-coverage diff --git a/memory/REFACTOR.md b/memory/REFACTOR.md index 2dd501057..bbc75a6ea 100644 --- a/memory/REFACTOR.md +++ b/memory/REFACTOR.md @@ -65,7 +65,7 @@ callers after refactor 1. ✓ Add the new `src/agents` topology and README as an empty central owner, and introduce central path/registry helpers that still point at the existing prompt and skill homes. 2. ✓ Move agent prompt bodies into the new prompt home and update foreground/body loading, background subagent loading, build asset copying, and prompt-body tests without changing prompt bytes. -3. Move Brunch prompt-resource skills into the new skills home and update manifest loading, build asset copying, resource-location snapshots, and skill topology docs without changing skill bytes. +3. ✓ Move Brunch prompt-resource skills into the new skills home and update manifest loading, build asset copying, resource-location snapshots, and skill topology docs without changing skill bytes. 4. Move foreground prompt composition and prompt-resource legality code into the new runtime home; leave Pi extension code as a thin hook adapter that imports the central composer. 5. Move per-turn pushed context composition and origination/session-entry seed composition into the new context seed home; update session and app callers to import from the central agent context layer. 6. Move reusable LLM-facing context renderers into the new context home, keeping product-only/human-only renderers outside unless they are deliberately agent-visible. diff --git a/memory/SPEC.md b/memory/SPEC.md index 097790521..d50c314e0 100644 --- a/memory/SPEC.md +++ b/memory/SPEC.md @@ -133,7 +133,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c - **D40-L — Runtime state is transcript-backed Brunch session-agent state, not hidden extension memory.** The architectural commitment is that Brunch session-agent posture remains transcript-backed Pi JSONL state rather than hidden extension memory: posture switches are user/system authority, the foreground session agent is derived from `op_mode`, child axes default back to `AUTO` when invalidated, and tool authority remains `op_mode`-gated rather than prompt-composition-owned. Runtime-state entries are Pi JSONL state-change facts, not assistant/user chat content: init and switch entries should render, when visible, as dim non-chat state rows analogous to Pi thinking/model-change rows, and must not enter LLM context as ordinary conversation. Current materialized state lives in [`src/session/README.md`](src/session/README.md), [`src/projections/README.md`](src/projections/README.md), [`src/.pi/README.md`](src/.pi/README.md), [`src/agents/README.md`](src/agents/README.md), [`src/agents/prompts/README.md`](src/agents/prompts/README.md), and [`src/projections/session/runtime-policy.ts`](src/projections/session/runtime-policy.ts). Depends on: D17-L, D23-L, D25-L, D39-L, D58-L, D59-L. Refined by: D85-L (the AUTO-able objective axes reduce to `strategy` + `lens`; `goal` is dropped from the runtime-state axis set and inlined into the agent role prompt). Supersedes: mode-only vocabulary, extension-local mutable state as authority, storing the foreground role as independent session state, the "runtime bundle / role preset" as one knob deriving model/thinking/resources, and binding prompt-resource location to `src/.pi/context/`. - **D34-L — Command containment separates visibility suppression from effect blocking.** Current Pi extension seams can hide unsupported slash suggestions with autocomplete wrapping and can cancel branch/session effects through lifecycle hooks, but they cannot strictly suppress exact interactive built-in commands before `InteractiveMode` dispatches them. Brunch-owned commands must use product-specific names and route writes through Brunch handlers/`CommandExecutor`; extension command collisions are not an override mechanism. Strict built-in command/keybinding policy is a Pi upstream/API ask, while POC safety relies on hiding generic affordances, blocking dangerous effects (`/fork`, `/clone`, raw session replacement), allowing native `/tree` as inspection/navigation, and failing fast on branched transcripts. Brunch's command-policy code should live in `src/.pi/extensions/commands/policy.ts`, merging branch/session-effect blocking with any product command allow/deny behavior instead of preserving a branch-only module. Depends on: D2-L, D24-L, A18-L. Supersedes: treating extension `input` handlers or command-name collisions as built-in command allowlisting. - **D35-L — Dynamic TUI chrome is a Brunch projection wrapper over Pi UI primitives.** The architectural commitment is that downstream TUI affordances call one Brunch-owned renderer (`renderBrunchChrome` or its successor) with a single activated product-state value rather than scattering raw `ctx.ui.setHeader` / `setFooter` / `setWidget` / title / working-indicator calls; the wrapper is stateless projection over canonical workspace/session/graph facts, never its own mutable state. Chrome is a project-first shell surface with selected-spec context — project name labels the cwd container, spec title labels the selected graph, session label distinguishes transcript instances — and a session label must never replace spec identity or graph truth. Chrome must not consume the status-key namespace for its own summary (`ctx.ui.setStatus` stays a lateral channel for other extensions), must not advertise unwired affordances, and RPC clients must rely only on surfaces Pi actually emits (header/footer/working-indicator are TUI-only in current Pi RPC mode). Current chrome state shape, render surfaces, telemetry/refresh, startup-header behavior, and status-key filtering live in [`src/.pi/extensions/chrome/README.md`](src/.pi/extensions/chrome/README.md); launch/activation wiring lives in [`src/.pi/extensions/README.md`](src/.pi/extensions/README.md). Depends on: D2-L, D21-L, D34-L, A18-L. Supersedes: treating Pi UI methods as direct downstream affordance APIs, rendering placeholder session state such as `unbound` after a session is activated, consuming the status-key namespace for chrome's own static summary, using spec title as the default session label, or allowing two unchanged Brunch-created default names to collide in one cwd, and the earlier resume/open-launches-stay-quiet clause (superseded 2026-06-11: the shipped, test-locked behavior headers every non-cancel activation). -- **D52-L — Source topology targets `src/{app, workspace, scripts, agents, .pi, db, graph, session, projections, renderers, rpc, web}` with directed layer dependencies.** Reusable projection and rendering modules live in top-level `src/projections/` and `src/renderers/` rather than whichever domain or adapter first needed them; `src/agents/` is the Pi-independent owner for Brunch-authored LLM context ingress (currently bundled agent prompt bodies plus the central registry for body and prompt-resource skill file paths, with later skill/runtime/context moves tracked by the LLM-context refactor; the `projections/session/runtime-policy.ts` registry import is a temporary migration edge until runtime policy moves under `agents/runtime/`); domain layers (`graph/`, `session/`) and the reusable `projections` / `renderers` / `agents` layers must not import adapters, transports, app entrypoints, or web code; `graph/` is the only layer that imports `db/`, plus the single sanctioned `db/`→`graph/schema/kinds.ts` taxonomy edge (D73-L). The concrete per-directory ownership, layout sketch, and full import matrix are owned by [`src/README.md`](src/README.md). Depends on: D2-L, D4-L, D39-L, D40-L. Refined by: D73-L. Supersedes: scattering session domain files at `src/` root; treating Pi-only agents as a host-independent top-level `src/.pi/` layer; nesting prompt composition under `src/.pi/context/`; treating reusable `project` / `format` helpers as owned by whichever adapter first needed them; treating `.pi/skills` or retired `.pi/agents` locations as the long-term conceptual owner for Brunch-authored model-facing content. +- **D52-L — Source topology targets `src/{app, workspace, scripts, agents, .pi, db, graph, session, projections, renderers, rpc, web}` with directed layer dependencies.** Reusable projection and rendering modules live in top-level `src/projections/` and `src/renderers/` rather than whichever domain or adapter first needed them; `src/agents/` is the Pi-independent owner for Brunch-authored LLM context ingress (currently bundled agent prompt bodies, prompt-resource skills, and the central registry for their file paths, with later runtime/context moves tracked by the LLM-context refactor; the `projections/session/runtime-policy.ts` registry import is a temporary migration edge until runtime policy moves under `agents/runtime/`); domain layers (`graph/`, `session/`) and the reusable `projections` / `renderers` / `agents` layers must not import adapters, transports, app entrypoints, or web code; `graph/` is the only layer that imports `db/`, plus the single sanctioned `db/`→`graph/schema/kinds.ts` taxonomy edge (D73-L). The concrete per-directory ownership, layout sketch, and full import matrix are owned by [`src/README.md`](src/README.md). Depends on: D2-L, D4-L, D39-L, D40-L. Refined by: D73-L. Supersedes: scattering session domain files at `src/` root; treating Pi-only agents as a host-independent top-level `src/.pi/` layer; nesting prompt composition under `src/.pi/context/`; treating reusable `project` / `format` helpers as owned by whichever adapter first needed them; treating retired `.pi/agents` / `.pi/skills` locations as the long-term conceptual owner for Brunch-authored model-facing content. - **D73-L — Domain enum taxonomy is owned by drizzle-free schema leaves; persistence and adapters are consumers, not the source.** The closed enum `const` arrays that define graph vocabulary — node kinds (`INTENT_KINDS`, `ORACLE_KINDS`, `DESIGN_KINDS`, `PLAN_KINDS`), `NODE_PLANES` (`intent`/`oracle`/`design`/`plan`), `NODE_BASES`, `EDGE_CATEGORIES`, `EDGE_STANCES`, `READINESS_BANDS`, `LENS_AFFINITIES`, `GAP_DISPOSITIONS`, and `GAP_PREDICATE_KINDS` — live in `graph/schema/kinds.ts`, a pure constants leaf that imports nothing (no drizzle, no `graph/atoms`). Both `db/schema.ts` (for `text({ enum })` column constraints, including the previously-inlined `plane` columns) and `graph/` domain modules import the arrays from this leaf; `graph/index.ts` re-exports them from the leaf so non-graph layers still avoid importing `db/` directly (I26-L). Session runtime axis vocabulary mirrors the same ownership direction in `session/schema/kinds.ts`: that leaf imports nothing and owns the `op_mode`, agent-role, `strategy`, `lens`, `auto`, and display-only planned mode choices consumed by `session/runtime-state.ts`, `projections/session/*`, and `.pi/extensions/agent-runtime/runtime/state.ts`; it deliberately contains no `goal` axis and no retired `READINESS_GRADES`. Derivations stay where they are read: `NODE_KIND_METADATA`, `formatGraphNodeCode`, `parseGraphNodeCode`, and `intentKindCategory` remain in `graph/schema/nodes.ts` (D62-L). The motivating defect: because `db/schema.ts` eagerly evaluates `sqliteTable(...)` and `verbatimModuleSyntax` emits even type-only imports at runtime, any value-import path from `web/` into the old taxonomy location pulled Drizzle into the browser bundle. Locating taxonomy in a drizzle-free leaf makes the `web/` build target structurally Drizzle-free (I44-L) and corrects the ownership direction so the domain, not the persistence layer, owns its vocabulary. Vocabulary migration status: `READINESS_GRADES` is retired (readiness is no longer a stored grade, D45-L), `ELICITATION_BACKLOG_STATUSES` is replaced by the `elicitation_gaps` disposition + predicate-shape enums (D65-L), and `READINESS_BANDS` stays. Depends on: D16-L, D52-L, D54-L, D62-L, D63-L, D64-L; I26-L. Supersedes: `db/schema.ts` owning the shared enum `const` arrays and the "enum literals flow outward from `db/schema.ts`" posture; the triplicated inline `['intent','oracle','design','plan']` plane literals. #### Data model & vocabulary @@ -277,7 +277,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c - **D93-L — Operational mode and foreground agent collapse to one op-mode-keyed source of truth; the foreground roster is `elicit` / `execute` / `code`.** A foreground agent and its operational mode are 1:1 (D40-L: the foreground agent is derived from `op_mode`), so the prior **three-record fragmentation** — id enums in `src/session/schema/kinds.ts`, `OPERATIONAL_MODE_DEFINITIONS` + `AGENT_ROLE_DEFINITIONS` + `TOOL_POLICY_DEFINITIONS` in `src/projections/session/runtime-policy.ts`, and `AGENT_PROMPT_DEFINITIONS` in `src/.pi/extensions/agent-runtime/runtime/state.ts` (which duplicated `model`/`thinking`/`allowedStrategies`/`allowedLenses` across two of them) — collapses to a **single op-mode-keyed record**. An operational mode IS `{ foreground AgentManifest (D90-L), tool policy, canDelegate set }`; background agents live in a sibling `AgentManifest` registry, and the per-agent **`canDelegate`** field (D92-L generalized from op_mode-keyed to a manifest field) links a foreground mode to the background agents it may spawn — **code-owned for foreground modes** so the write-safety boundary (I49-L) holds; it also generalizes to background→background nesting. The foreground roster is three modes: **`elicit` → `elicitor`** (current: read-only + graph-writes-via-tools; delegates to read-only gatherers); **`execute` → `orchestrator`** (live initial standup: direct shell/file writes blocked, no delegated workers yet, and one code-owned `orchestrator_stub` grant proves the non-dev runnable path; future worker delegation quarantines writes in spawned high-privilege workers per D92-L); and **`code` → `pi-coder`** (planned direct-coding assistant with direct `read`/`edit`/`write`/`bash`, **augmented to be Brunch-tooling-aware** — it also sees the graph read/capture tools). `execute` and `code` contrast deliberately: the orchestrator does not write directly, while the pi-coder will write directly (and does not delegate by default). `pi-coder` **instantiates** the augment case D58-L anticipated — its `SYSTEM.md` augments Pi's base coding-agent prompt rather than replacing it; whether other foreground roles should instead suppress/replace the base stays open per D58-L. (`code` is provisional naming for the pi-coder mode; rename is a one-token change. `code` remains in `PLANNED_OPERATIONAL_MODE_IDS` until built; `execute` is live.) Depends on: D23-L, D40-L, D58-L, D90-L, D92-L; I49-L. Establishing frontier: `subagent-reconciliation` (model + collapse land here; `execute` stood up here with the stub grant, while `code` remains declarative build-out — see PLAN). Supersedes: the three-record foreground-agent fragmentation as separate sources of truth; `defaultRole`/`allowedRoles` as a flexible many-roles-per-mode model (it is 1:1); the `executor` name for the execute-mode agent (now `orchestrator`). - **D36-L — Spec/session selection is a reusable hierarchical decision model with transport-specific presentations.** Brunch owns a pure spec/session selection model that renders cwd-scoped inventory under the discovered project name without calling the user-created object a “workspace”. In TUI mode, the model may present a fast “continue last session” affordance when `.brunch/workspace.json` points to a valid spec+session; otherwise, or after “other spec/session”, the durable tree is: `create new spec → provide spec name → session created automatically`; `resume existing spec → choose existing spec → create a new session OR resume existing session → choose existing session`. The UI should not list every spec as a top-level action label; “resume existing spec” is the top-level intent, and the spec list is the next screen/scrollable selector. The model returns a product decision (`new spec`, `new session for spec`, `open session`, `continue selected session`, `cancel/quit`) without opening Pi sessions or mutating `.brunch/workspace.json` itself. The `WorkspaceSessionCoordinator` activates that decision and owns all persistence/session-binding effects. TUI startup and in-session paths share branded `pi-tui` components and colocated logo assets under `src/.pi/components/workspace-dialog`; adapters differ only in terminal lifecycle and Pi session-replacement mechanics (`ProcessTerminal`/`TUI.showOverlay` before Pi starts, `ctx.ui.custom(..., { overlay: true })` inside Pi), not in product semantics. RPC/headless transports must not invoke the TUI picker; they expose the same initial-selection requirement and activation decisions as JSON-RPC/product results so CLI JSON-RPC clients can select or create spec/session correctly. Depends on: D11-L, D21-L, D24-L, D33-L. Supersedes: implicit resume of `.brunch/workspace.json` on TUI launch, Pi `/resume`/`/new` as Brunch's product session chooser, one-off startup-only picker implementations, a flat action list that says “workspace” for specs, top-level `resume spec X` labels, and a separate intermediate action chooser for switching. - **D42-L — Session naming is Pi `session_info` presentation metadata, not spec identity.** Brunch-created sessions should be named at creation with neutral workspace-global defaults (`Untitled Session 1`, `Untitled Session 2`, …) so pickers/chrome never show an unnamed Brunch session and unchanged defaults do not collide across specs in the same cwd. These defaults are immediate lifecycle metadata, not LLM-generated summaries and not derived from the selected spec title. Brunch may later use Pi session lifecycle hooks to opportunistically replace a default with a short human-readable name that characterizes what happened in the transcript. The preferred generation trigger is `session_shutdown` for `quit`, `new`, and `resume` replacements because it sees the just-finished transcript and can name it before later picker lists need to distinguish sessions; `session_before_compact` or post-compaction (`session_compact`) may be used to refresh names after major summarization, and a manual/user rename command can force or override naming. The generation call should mirror the model-selection pattern in the local `summarize.ts` extension example: choose a cheap/fast authorized model, extract user/assistant text plus salient tool calls from the current branch, ask for a concise title, and append a Pi `session_info` entry through `SessionManager.appendSessionInfo`. Naming must be best-effort and non-blocking with a tight budget: failures, missing auth, empty transcripts, or shutdown aborts preserve the existing default/user label rather than blocking session replacement or exit. Session display names label sessions in pickers and chrome, but do not affect spec ids, session bindings, graph truth, or replay semantics. Depends on: D6-L, D17-L, D21-L, D35-L. Supersedes: using spec title or session UUID alone as the only durable display label once transcripts have meaningful content, leaving Brunch-created sessions unnamed, spec-local default numbering, or treating generated session names as canonical spec identity. -- **D58-L — Brunch prompt composition is a thin runtime header plus a gated prompt-resource manifest, not eager selection of every objective pack.** The architectural commitment is: composition stays a projection layer, not a behavioral state machine; detailed strategy/lens/method guidance lives in read-on-demand prompt resources rather than eager prompt-pack concatenation; runtime-state-gated availability is Brunch's sealed manifest, not ambient Pi discovery; pinned axes remain visible when role/mode-legal while readiness constrains AUTO menus and gated methods/tools; and the pushed-context slice stays compact, with deeper access governed by D60-L. Current prompt-resource topology, manifest emission, file-owned skill metadata, and ownership split across `agents/prompts/`, `.pi/skills/`, `.pi/extensions/agent-runtime/system-prompts/`, and `.pi/extensions/agent-runtime/runtime/` live in [`src/agents/README.md`](src/agents/README.md), [`src/agents/prompts/README.md`](src/agents/prompts/README.md), [`src/.pi/README.md`](src/.pi/README.md), [`src/.pi/skills/README.md`](src/.pi/skills/README.md), [`src/.pi/extensions/README.md`](src/.pi/extensions/README.md), [`src/.pi/extensions/agent-runtime/system-prompts/compose.ts`](src/.pi/extensions/agent-runtime/system-prompts/compose.ts), and [`src/.pi/extensions/agent-runtime/runtime/state.ts`](src/.pi/extensions/agent-runtime/runtime/state.ts). **Base-prompt relationship (validated 2026-06-18, slice 1):** the `before_agent_start` handler **appends** Brunch's composed block (now led by the agent `SYSTEM.md` body, then runtime header + manifests) to Pi's base system prompt (`${basePrompt}\n\n${composed}`), so a foreground agent currently *augments* Pi's base coding-agent prompt rather than replacing it. Whether a foreground role's `SYSTEM.md` body should suppress or replace that base is **open** and tied to the future `pi-coder` op-mode (which deliberately augments Pi's coding agent); the `elicitor` augmenting a coding base is a known follow-on question, not a settled choice. Refined by: D93-L (the `code`→`pi-coder` foreground mode instantiates the augment case; the replace option for other roles stays open). Composition is projection, not a behavioral state machine. Depends on: D23-L, D25-L, D39-L, D40-L, D52-L, D59-L, D60-L. Refined by: D85-L (implemented 2026-06-18/19: the manifest drops `` — two axes `strategy` + `lens` — and the `goal` body inlines into the `elicitor` role prompt) and by the 2026-06-22 prompt-skill-topology slice (all prompt resources adopt Agent Skills `SKILL.md` topology; `description` becomes file-owned frontmatter; the emitted wrapper becomes `` with per-skill ``). Supersedes: the flat "base + mode + role + strategy + lens + grade + …" layering; the fixed all-packs concatenation in `compose-brunch-prompt.ts`; "role preset / runtime bundle" as the composition unit; direct Layer-2 eager prompt-pack injection as the default mechanism; treating top-level `src/agents/` as Pi-only rather than Brunch LLM-context ingress; and `capability` as a parallel name for `method`. +- **D58-L — Brunch prompt composition is a thin runtime header plus a gated prompt-resource manifest, not eager selection of every objective pack.** The architectural commitment is: composition stays a projection layer, not a behavioral state machine; detailed strategy/lens/method guidance lives in read-on-demand prompt resources rather than eager prompt-pack concatenation; runtime-state-gated availability is Brunch's sealed manifest, not ambient Pi discovery; pinned axes remain visible when role/mode-legal while readiness constrains AUTO menus and gated methods/tools; and the pushed-context slice stays compact, with deeper access governed by D60-L. Current prompt-resource topology, manifest emission, file-owned skill metadata, and ownership split across `agents/prompts/`, `agents/skills/`, `.pi/extensions/agent-runtime/system-prompts/`, and `.pi/extensions/agent-runtime/runtime/` live in [`src/agents/README.md`](src/agents/README.md), [`src/agents/prompts/README.md`](src/agents/prompts/README.md), [`src/.pi/README.md`](src/.pi/README.md), [`src/agents/skills/README.md`](src/agents/skills/README.md), [`src/.pi/extensions/README.md`](src/.pi/extensions/README.md), [`src/.pi/extensions/agent-runtime/system-prompts/compose.ts`](src/.pi/extensions/agent-runtime/system-prompts/compose.ts), and [`src/.pi/extensions/agent-runtime/runtime/state.ts`](src/.pi/extensions/agent-runtime/runtime/state.ts). **Base-prompt relationship (validated 2026-06-18, slice 1):** the `before_agent_start` handler **appends** Brunch's composed block (now led by the agent `SYSTEM.md` body, then runtime header + manifests) to Pi's base system prompt (`${basePrompt}\n\n${composed}`), so a foreground agent currently *augments* Pi's base coding-agent prompt rather than replacing it. Whether a foreground role's `SYSTEM.md` body should suppress or replace that base is **open** and tied to the future `pi-coder` op-mode (which deliberately augments Pi's coding agent); the `elicitor` augmenting a coding base is a known follow-on question, not a settled choice. Refined by: D93-L (the `code`→`pi-coder` foreground mode instantiates the augment case; the replace option for other roles stays open). Composition is projection, not a behavioral state machine. Depends on: D23-L, D25-L, D39-L, D40-L, D52-L, D59-L, D60-L. Refined by: D85-L (implemented 2026-06-18/19: the manifest drops `` — two axes `strategy` + `lens` — and the `goal` body inlines into the `elicitor` role prompt) and by the 2026-06-22 prompt-skill-topology slice (all prompt resources adopt Agent Skills `SKILL.md` topology; `description` becomes file-owned frontmatter; the emitted wrapper becomes `` with per-skill ``). Supersedes: the flat "base + mode + role + strategy + lens + grade + …" layering; the fixed all-packs concatenation in `compose-brunch-prompt.ts`; "role preset / runtime bundle" as the composition unit; direct Layer-2 eager prompt-pack injection as the default mechanism; treating top-level `src/agents/` as Pi-only rather than Brunch LLM-context ingress; and `capability` as a parallel name for `method`. #### Continuity & origination (turn-boundary choreography) @@ -294,7 +294,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c - **D71-L — One `BRUNCH_DEV` switch gates all dev affordances; the main CLI accepts `--cwd`; introspection is present-but-dead in prod.** The over-specific `BRUNCH_DEV_RPC` env var is generalized to a single `BRUNCH_DEV` switch that, when set, enables dev affordances together: dev RPC methods (`dev.*`), registration of the read-only introspection extension (D69-L), and routing of dev-loop artifacts to `.fixtures/scratch/` (D70-L). `runBrunchCli` parses a `--cwd ` flag (defaulting to `process.cwd()`) so a dev session can target a `.fixtures/workbenches/` workspace without `cd`. Two independent prod-safety gates hold: (1) `src/dev/**` is build-excluded by `tsconfig.build.json`, so launchers/harness/alias never ship; (2) the introspection extension, though compiled into `dist` under `src/.pi/`, only *registers* when `createBrunchPiExtensions(..., { introspection: { enabled } })` opts in — and the TUI call site sets `enabled` from `BRUNCH_DEV` only, so absent the switch it is present-but-dead, never wired, honoring D39-L explicit-opt-in sealing (no ambient discovery). Brunch-launched TUI sessions keep Pi startup update suppression on in both product and `BRUNCH_DEV` runs by scoping `PI_OFFLINE=1` through `InteractiveMode.run()` unless the user already set a value; prior `PI_OFFLINE` / `PI_SKIP_VERSION_CHECK` state is restored in `finally`, never as a leaked global `process.env` mutation. Depends on: D39-L, D67-L, D68-L, D69-L, D70-L. Supersedes: the `BRUNCH_DEV_RPC`-only dev gate; relying on the operating cwd to choose the dev workspace; the assumption that the introspection extension needs build-exclusion (runtime opt-in suffices); lifting Pi offline mode in `BRUNCH_DEV` TUI sessions merely to enable live-provider behavior. - **D79-L — Dev DB seeding is explicit, selected, and target-workspace-scoped; `npm run dev` never implies a seed.** A Brunch workspace DB is local runtime state under that launch cwd's `.brunch/`; running `npm run dev` against the repo root or a workbench may create/open that workspace, but it must not silently load reusable seed fixtures. Reusable graph seeds under `.fixtures/seeds//.json` are loaded only by an explicit seed command that names the target workspace and the seed set/slug (or an explicitly requested all-seeds batch); the loader remains a graph-domain utility over `seedFixture`/`CommandExecutor`, so seeded specs get normal `create_spec`/`mutate_graph` change-log entries, spec-local LSNs, elicitation-gap seeding, and structural validation. Workbenches under `.fixtures/workbenches//` are launchable cwd containers, not seed truth: their `.brunch/` may be reset or re-seeded locally, but tracked files must document which seed(s) a human or script should apply. Captured or newly-authored seed JSON is parked until it has at least one named consumer disposition (`test`, `preview`, `manual workbench`, `probe input`, or `parked`); existence under `seeds/` alone does not make it part of the default dev database. Depends on: D16-L, D20-L, D52-L, D70-L, D71-L. Supersedes: the catch-all `npm run seed` mental model that loads every seed into the current shell cwd; treating the repo-root `.brunch/` as canonical dev fixture state; auto-seeding because a dev host starts. - **D59-L — `goal` is a readiness-derived, AUTO-able objective axis, distinct from strategy.** A *goal* is what the session agent currently pursues; a *strategy* is the reusable interaction shape used to pursue it — a goal is pursued *via* a strategy *through* a lens (three orthogonal axes). The goal set is derived from readiness-band coverage (D64-L) rather than a stored grade: `grounding-advance` (fill grounding gaps and raise grounding coverage), `elicit-expand` (expand the elicited specification graph while ambiguity remains productive), `commit-converge` (reduce / lock down reviewable commitments), plus an always-on `capture-posture` (capture or confirm dev `posture`, D45-L). `goal` defaults to the readiness-derived objective (e.g. while grounding coverage is thin, `grounding-advance`), may be pinned, or left `AUTO`; in either case D58-L manifests advertise the legal resource(s) rather than injecting the whole goal body. For now `goal` is **internal/readiness-derived and not part of the user posture-change surface** (it is too contingent to expose as a user-mutable axis); the pin affordance is reserved for system/internal logic, and unlike `strategy`/`lens` the user does not switch it (D40-L, Q4). `elicit-expand` and `commit-converge` intentionally form the diverge/converge pair for the elicitation diamond; `elicit-I` / `elicit-II` are retired because they were phase-like labels, not objectives. "Advance grounding" is a goal, not a strategy — though the `grounding-advance` goal may carry a dedicated default interaction pattern. Depends on: D45-L, D57-L, D58-L, D64-L. Refined by: D85-L (`goal` is dropped as a manifest/runtime axis; the four postures inline into the `elicitor` role prompt, agent-selected by band — goal was internal/readiness-derived anyway). Supersedes: conflating the elicit-lifecycle objective with strategy selection, and deriving the goal set from a stored readiness grade. -- **D66-L — `freestyle` is a structure-optional elicitation strategy; it and generalized free-text capture are one slice.** The architectural commitment is that `freestyle` is an interaction-style strategy, not a new `op_mode` or authority posture: ordinary user-driven turns become allowed without banning structured exchanges, and AUTO must not select `freestyle` — it remains an explicit user/system pin so offer-first does not disappear silently. Current materialized state lives in [`src/.pi/skills/strategies/README.md`](src/.pi/skills/strategies/README.md), [`src/.pi/skills/strategies/freestyle/SKILL.md`](src/.pi/skills/strategies/freestyle/SKILL.md), [`src/.pi/skills/methods/capture/SKILL.md`](src/.pi/skills/methods/capture/SKILL.md), [`src/projections/session/runtime-policy.ts`](src/projections/session/runtime-policy.ts), [`src/.pi/extensions/commands/index.ts`](src/.pi/extensions/commands/index.ts), and [`src/session/README.md`](src/session/README.md). Depends on: D18-L, D25-L, D26-L, D40-L, D45-L, D49-L, D50-L, D59-L, D63-L, D65-L. Refines: R16. Refined by: D80-L, D81-L (2026-06-12 FE-861 grill: the capture half — submit-time capture wiring and the "directly-stated only" commitment line — is superseded by the banded capture sweep and the commitment gradient; capture runs on every elicitor turn over the un-swept tail, resolving the every-turn-vs-on-demand open question). Supersedes: treating offer-first (R16) as a universal per-turn session invariant; treating freestyle as a new operational mode or authority posture. +- **D66-L — `freestyle` is a structure-optional elicitation strategy; it and generalized free-text capture are one slice.** The architectural commitment is that `freestyle` is an interaction-style strategy, not a new `op_mode` or authority posture: ordinary user-driven turns become allowed without banning structured exchanges, and AUTO must not select `freestyle` — it remains an explicit user/system pin so offer-first does not disappear silently. Current materialized state lives in [`src/agents/skills/strategies/README.md`](src/agents/skills/strategies/README.md), [`src/agents/skills/strategies/freestyle/SKILL.md`](src/agents/skills/strategies/freestyle/SKILL.md), [`src/agents/skills/methods/capture/SKILL.md`](src/agents/skills/methods/capture/SKILL.md), [`src/projections/session/runtime-policy.ts`](src/projections/session/runtime-policy.ts), [`src/.pi/extensions/commands/index.ts`](src/.pi/extensions/commands/index.ts), and [`src/session/README.md`](src/session/README.md). Depends on: D18-L, D25-L, D26-L, D40-L, D45-L, D49-L, D50-L, D59-L, D63-L, D65-L. Refines: R16. Refined by: D80-L, D81-L (2026-06-12 FE-861 grill: the capture half — submit-time capture wiring and the "directly-stated only" commitment line — is superseded by the banded capture sweep and the commitment gradient; capture runs on every elicitor turn over the un-swept tail, resolving the every-turn-vs-on-demand open question). Supersedes: treating offer-first (R16) as a universal per-turn session invariant; treating freestyle as a new operational mode or authority posture. - **D80-L — Generalized capture is the elicitor's banded capture sweep: in-turn, synchronous, over the un-swept transcript tail.** Capture is conduct of the foreground elicitor, not product wiring: there is no observer/auditor queue on the primary path (D18-L, reaffirmed — the v1 observer failed on structure-dependence and context starvation), no product-side LLM extraction pass on the submit paths, no gateway/translation/judgment layer between the agent and graph truth, and no capture subagent in the current block. The **banded capture sweep** is one band-ordered pass that walks intent-kind groups (the same typology the `elicitation_gaps` register references via `refersTo: NodeKind`, D65-L/D75-L), committing through the real role-named `mutateGraph` grammar (D53-L/A14-L) and moving gap dispositions through `update_elicitation_gaps`. Its input window is the **un-swept transcript tail** — all conversational and digest content since the last sweep, tracked by a **sweep watermark** (prior art: the I45-L assistant-visible watermark and the own-mutation stamp) — so capture is robust to RPC-submitted messages, interruptions, and multi-message bursts, and probes get a crisp invariant: after any elicitor turn, nothing conversational remains behind the watermark. Default is a single pass; bulk material (pastes, document reads, exploration digests) may engage an **escalation valve** — chunked/iterated sweeping within the same turn — without changing window or watermark semantics. Choreography is **capture-then-ask**: the sweep commits facts and moves gaps *before* the elicitor composes its next question, so the question provably benefits from what was just captured. Consequence: the deterministic labeled-prefix capture core (`graph/capture/structured-response.ts`), its `session.submitMessage`/`session.submitExchangeResponse` wiring, and the `capture-response-to-graph` proof are retired fossils — capture becomes turn-coupled (same agent for RPC transport clients, different moment; no coverage loss). Depends on: A14-L, A22-L, D18-L, D49-L, D53-L, D63-L, D65-L, D66-L; I45-L. Supersedes: submit-time product-side capture (the D66-L "exactly as the structured-response capture tracer does" wiring), the labeled-prefix extraction core, and the capture-quality spike's product-side extraction-pass shape. - **D81-L — Capture commitment gradient: confidence, not directness; low-confidence noticings spawn elicitation gaps.** What the sweep commits is governed by confidence in grounding, not by whether the user uttered the exact words: directly-stated facts commit with `basis: explicit`; confidently-materialized items — including implied edges and structure soundly inferred from stated content — commit with `basis: implicit`, which D63-L already licenses (agent-materialized-from-user-input); low-confidence **noticings** are never committed — the sweep's prompt directs the elicitor to spawn an `elicitation_gap` instead (`basis: implicit`, rationale citing the noticing), so the false-commit guard's positive output *is* the capture-reflection behavior: one prompted discipline discharges both the guard and the gap-writeback obligation, the agenda durably carries what was noticed, and the anti-shadowing line (D65-L) holds because the gap carries question/rationale, never domain content as truth. There is **no structural gate**: the guard is commitment rules in the sweep prompting plus the false-commit scenario matrix re-aimed at the low-confidence line and run at probe tier (some spike implication rows become legitimate implicit commits under the gradient; expected gap-spawns become assertable probe outcomes); CI guards structural legality only at the `CommandExecutor` boundary. Refines: D18-L (low-confidence material now spawns gaps rather than only "folding into later questions"; preface, D47-L, remains the orientation carrier). Depends on: A22-L, D18-L, D47-L, D63-L, D65-L. Supersedes: "implications never become graph truth" as a *directness* rule, and the spike matrix's `shouldCommit` expectations as written. - **D82-L — Ground-material acquisition is a skill-structured layer in front of the sweep; bulk modes interpose a digest; a seeded situating gap routes modes.** Questions and answers are not the only way the graph gains ground material: the elicitor must also accept arbitrary pasted content, read user-referenced workspace documents, and explore-and-characterize a brownfield codebase. These are **acquisition modes** — elicit-by-question, ingest-paste, read-referenced-documents, explore-and-characterize — structured as Brunch prompt-resource skills (D58-L manifest world), each a distinct competence the elicitor reaches for; `read-referenced-documents` and `explore-and-characterize` may use Brunch-owned static `web_fetch`/`web_search` tools registered in the sealed Pi profile (D39-L/D40-L). Acquisition varies, capture stays uniform (`acquire → digest → sweep`): everything acquired lands in the transcript behind the sweep watermark. Bulk modes (exploration, research, large document reads) interpose a **digest** — an assistant-authored characterization of what was read/found (prior art: the v1 preface-of-exchange-tuple, which proved capture should run over the summary, not the raw bulk; D47-L) — and the sweep captures from digests plus conversational content while raw tool results pass behind the watermark as background. A **situating gap** is seeded at spec creation (orientation anchors: new-from-scratch / brownfield codebase / continuation of a prior thread — the grounding-advance anchors promoted from skill prose to agenda), so the opening elicitation itself routes the session into the right acquisition mode; the gap's discharge is what licenses, e.g., explore-and-characterize. Near-future direction (not current block): exploration/research acquisition delegated to **subagents** with the digest as the handback artifact — clean main-elicitor context without observer starvation, because the subagent owns its exploration context and returns only the digest. Depends on: D47-L, D57-L, D58-L, D65-L, D80-L. Supersedes: treating conversational answers as the only capture source. @@ -311,13 +311,13 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c - **Dependencies** — three small leaf libraries (md-pen, `@toon-format/toon`, stringify-tree), each *retiring owned format-generation code* (the md-pen/TOON wrapper seams are already stubbed; the tree library replaces a hand-built formatter), so net owned surface decreases — the trade that justifies them under a dependencies-resist posture. - **Rollout** — incremental: `` and `` context first, then migrate the `graph`, `session`/runtime-frame, and transcript renders onto the house style; the `renderer-golden-coverage` frontier re-scopes around the new dialect. - **Closed audit** — per-session `turnCount` is derived once while inspecting canonical session files and counts only current Pi v3 JSONL message entries (`type: "message"` with `message.role: "user" | "assistant"`); tool/custom entries are excluded, and downstream workspace/specification overview renders reuse that inspected count rather than reparsing the file. -- **D85-L — Brunch prompt-resource axis model: two AUTO axes (`strategy`, `lens`); `goal` inlined into the agent role; graph-write mechanism is method-routed, not a strategy.** A 2026-06-18 grill consolidation of the `.pi/skills/` topology and the D58-L manifest axes, implemented across FE-893, FE-861, and FE-898. Four locked moves: +- **D85-L — Brunch prompt-resource axis model: two AUTO axes (`strategy`, `lens`); `goal` inlined into the agent role; graph-write mechanism is method-routed, not a strategy.** A 2026-06-18 grill consolidation of the `agents/skills/` topology and the D58-L manifest axes, implemented across FE-893, FE-861, and FE-898. Four locked moves: 1. **Two AUTO objective axes, not three.** The runtime manifest advertises only `strategy` and `lens`; **`goal` is dropped as a manifest/runtime axis**. The four goal postures (`grounding-advance`, `elicit-expand`, `commit-converge`, always-on `capture-posture`) **inline into the `elicitor` agent role prompt** (`src/agents/prompts/elicitor/SYSTEM.md`), selected inline by the agent from the pushed readiness-band/posture context. Rationale: `goal` was already internal/readiness-derived and not user-mutable (D59-L), so advertising it as an AUTO-selectable axis was indirection over what is agent-directed-by-band anyway. Consequences for the build: `compose.ts` drops the `` family, `manifestsForState` drops `goals`, `runtime-state.ts` / `runtime-policy.ts` / `affordances.ts` drop the `goal` axis slot, and the runtime header drops the goal line. Capability-readiness (D74-L) is unaffected — it keys on gaps, not goal. 2. **Graph-write mechanism is method-routed, not a strategy-axis member.** `propose-graph` (direct-commit) and `project-graph` (review-set) describe the **graph-write capability ids** (the D26-L commitment mechanisms), not interaction shape; their strategy names are retired rather than rehomed. The existing methods absorb the mechanics: `commit-graph` carries direct-commit mechanics, and `generate-proposal` carries review-set mechanics. The offer→accept / derive→review choreography lives in the inlined `commit-converge` posture, not in method bodies. The graph-write readiness gate was originally placed on those method ids via capability-readiness (**removed by D86-L**: the graph-write methods are floor — readiness is advisory for them, never a tool gate), while the `strategy` axis keeps only genuine interaction shapes: `step-wise-decision-tree`, `step-wise-disambiguate`, and `freestyle` (AUTO-excluded, D66-L). 3. **Gap-reflection conduct belongs to the capture skill, not `review-for-gaps`.** D81-L spawn-on-noticing + close-on-answered is **always-on capture-sweep conduct** (every elicitor turn), so it lives with the D80-L capture skill, not an optionally-selected method. `review-for-gaps` is demoted to the **deliberate-audit sense only** (missing support, contradictions, verification debt). Read/interpret-gap semantics stay on the `read_elicitation_gaps` tool description (tool-local), not duplicated into a skill; the D81-L commitment gradient lives once, in the capture skill, with gap-spawn as its third outlet. 4. **The prompt-content rewrite is design work entangled with live/stubbed seams — not a keyword fossil sweep.** The strategy/method bodies drift and overlap, but audit (2026-06-18) found their suspect tokens are mostly *not* dead history: `tool_meta` is live across every exchange projection; `capture_*` is a live `tool_meta.next` sequencing marker (`present_* → request_* → capture_*`), distinct from the D80-L-retired labeled-prefix capture core; and `present_candidates` + `user_rubric` / `meta_rubric` / `graph_refs` are the **anticipated payload of the live candidate topology stub** (`projections/exchanges/present-candidates.ts`, PLAN-confirmed stubbed), not removable fossils. Only `renderCall` is genuinely unreferenced (confirm against the Pi display API before removal). Rewriting prompt content must reconcile against the candidate stub, the exchange `tool_meta` model, and the D80-L sweep model rather than strip by keyword. Lexicon sweep in the same pass: `elicitation backlog` → `elicitation gaps` / `coverage obligation` (the D65-L rename; the inlined `elicit-expand` posture in `elicitor/SYSTEM.md` still carries the old term after the goal-axis drop relocated it). - Prompt-shape closure (revised 2026-06-22): (a) **`SKILL.md` directory topology is adopted for every strategy/lens/method** because the Agent Skills standard is now the target prompt-resource format; `references/` remains deferred until a concrete skill needs progressive disclosure, and D39-L's code-owned path list remains the availability surface. (b) **`src/agents/prompts//SYSTEM.md`** is adopted for live and named future bodies; no flat agent-body shape remains open. (c) **`[sub]` sub-agent notation** is deferred until the first real delegated sub-agent lands; no empty sub-agent stubs are introduced. (d) **`_generated/` typed-vocab references** are **materialized** (first instance: the kind→band table at `src/graph/schema/_generated/ontology.md`, generated by `npm run generate:ontology` from the typed `graph/schema` sources and drift-checked by `npm run check:data-model`, wired into `npm run check`); they are read-only projections locked separately from authored prompt-resource bodies, and prompt resources cite them rather than restating vocabulary (the data-model-legibility frontier owns the expansion to further tables + the authored judgment layer). Current state: [`src/.pi/skills/README.md`](src/.pi/skills/README.md). Resolved 2026-06-18: the capture home is `methods/capture`, absorbing the former `infer-and-capture` method name; the full D80/D81/D82 conduct body remains FE-861. Depends on: D23-L, D25-L, D26-L, D39-L, D40-L, D58-L, D59-L, D65-L, D73-L, D80-L, D81-L. Refines: D25-L, D26-L, D40-L, D58-L, D59-L. Supersedes: `goal` as an AUTO-able manifest/runtime axis (the "objective axes `strategy`, `lens`, and `goal`" triple in D40-L/D58-L/D59-L → two axes, goal inlined); `propose-graph` / `project-graph` as `strategy`-axis members (D25-L/D26-L list them as strategies); treating gap spawn/close as a `review-for-gaps` method responsibility; `infer-and-capture` as a separate method; treating the `capture_*` / candidate / `tool_meta` prompt-resource references as removable fossils; and the 2026-06-19 deferral of Agent Skills `SKILL.md` topology. + Prompt-shape closure (revised 2026-06-22): (a) **`SKILL.md` directory topology is adopted for every strategy/lens/method** because the Agent Skills standard is now the target prompt-resource format; `references/` remains deferred until a concrete skill needs progressive disclosure, and D39-L's code-owned path list remains the availability surface. (b) **`src/agents/prompts//SYSTEM.md`** is adopted for live and named future bodies; no flat agent-body shape remains open. (c) **`[sub]` sub-agent notation** is deferred until the first real delegated sub-agent lands; no empty sub-agent stubs are introduced. (d) **`_generated/` typed-vocab references** are **materialized** (first instance: the kind→band table at `src/graph/schema/_generated/ontology.md`, generated by `npm run generate:ontology` from the typed `graph/schema` sources and drift-checked by `npm run check:data-model`, wired into `npm run check`); they are read-only projections locked separately from authored prompt-resource bodies, and prompt resources cite them rather than restating vocabulary (the data-model-legibility frontier owns the expansion to further tables + the authored judgment layer). Current state: [`src/agents/skills/README.md`](src/agents/skills/README.md). Resolved 2026-06-18: the capture home is `methods/capture`, absorbing the former `infer-and-capture` method name; the full D80/D81/D82 conduct body remains FE-861. Depends on: D23-L, D25-L, D26-L, D39-L, D40-L, D58-L, D59-L, D65-L, D73-L, D80-L, D81-L. Refines: D25-L, D26-L, D40-L, D58-L, D59-L. Supersedes: `goal` as an AUTO-able manifest/runtime axis (the "objective axes `strategy`, `lens`, and `goal`" triple in D40-L/D58-L/D59-L → two axes, goal inlined); `propose-graph` / `project-graph` as `strategy`-axis members (D25-L/D26-L list them as strategies); treating gap spawn/close as a `review-for-gaps` method responsibility; `infer-and-capture` as a separate method; treating the `capture_*` / candidate / `tool_meta` prompt-resource references as removable fossils; and the 2026-06-19 deferral of Agent Skills `SKILL.md` topology. Depends on: D19-L, D52-L, D60-L, D62-L, D65-L, D75-L. Refines: D60-L (RENDER stage). Supersedes: the ad-hoc `[bracket]`-header + bullet-list render style as the house convention; hand-rolled markdown and tree string generation in renderers; carrying sessions in the `` cwd render. - **D95-L — Elicitor capability spine: `capture` / `generate` / `project` are the three elicitor capabilities, named over the existing skill axes — not a fourth axis or a re-axis.** The elicitor's work decomposes into three capabilities by what each does to the graph: **capture** commits ground material already present in the transcript tail into graph truth (the D80-L banded sweep + D81-L commitment gradient + D82-L acquisition layer, already specced); **generate** proposes new typed graph expressions on a requested plane from grounding plus a lens frame, fanning candidates out and committing the chosen one through review (D96-L); **project** derives nodes on one plane from a subset/plane of the existing graph with connecting cross-plane edges (e.g. requirements→design, design→oracles, A33-L). This is a capability vocabulary layered over D85-L's `strategy` / `lens` / `method` axes (held frozen, A35-L), not a replacement topology: capture is always-on conduct of every elicitor turn (D85-L move 3), while generate and project are method-routed capabilities requested just-in-time and gated only advisorily by capability-readiness (D74-L/D86-L). Background acquisition subagents (D82-L near-future, A34-L) are the `acquire` arm feeding capture, not a fourth capability. Depends on: D74-L, D80-L, D81-L, D82-L, D85-L, D86-L. Supersedes: the proposed `grounding` / `elicitation` / `projection` lifecycle directories as a replacement skill topology. @@ -404,7 +404,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c ### Prompt/runtime profile architecture - Brunch prompt composition is a **runtime-header + gated prompt-resource manifest** composed per agent by `composeAgentPrompt(...)` in `.pi/extensions/agent-runtime/system-prompts/compose.ts` (D58-L). The direct injection is intentionally small: agent control summary, two-axis runtime state (`strategy` + `lens`), a legal `` resource manifest with per-skill `kind`, `name`, `description`, and `location`, router rules for pinned/AUTO axes, and compact context handles/rendered context blocks. Detailed strategy/lens/method bodies are Brunch-owned markdown resources the agent loads with `read` when needed, while the elicitor objective postures are inline in `src/agents/prompts/elicitor/SYSTEM.md`. The old `src/.pi/context/` prompt-pack layout is retired; top-level `src/agents/` is now the Brunch-owned LLM-context ingress home, not a Pi-only agent tree. -- Concrete `agents/prompts` + `.pi/skills` topology (D52-L). The markdown/code boundary falls on the control-plane/behavior split: enforcement and projection are TypeScript under `.pi/extensions/` (`system-prompts/`, `runtime/`); semantic prompting material is markdown under `agents/prompts/{agent-name}/SYSTEM.md` for live agent bodies and `.pi/skills/`. +- Concrete `agents/prompts` + `agents/skills` topology (D52-L). The markdown/code boundary falls on the control-plane/behavior split: enforcement and projection are TypeScript under `.pi/extensions/` (`system-prompts/`, `runtime/`); semantic prompting material is markdown under `agents/prompts/{agent-name}/SYSTEM.md` for live agent bodies and `agents/skills/`. ```text src/agents/ @@ -413,6 +413,13 @@ src/agents/ elicitor/SYSTEM.md [md+] live foreground elicit-mode body reviewer/SYSTEM.md [md] background proposal/commitment review body pi-coder/SYSTEM.md [md] unwired Pi coding-agent baseline / future augmentation body + skills/ + README.md [md] ownership + body-lock ledger + strategies/*/SKILL.md [md] step-wise-decision-tree, step-wise-disambiguate, freestyle + lenses/*/SKILL.md [md] intent, design, oracle (future execute: plan, sync, scope) + methods/*/SKILL.md [md] run-structured-exchange, capture, generate-proposal, + read-context, commit-graph, review-for-gaps, + acquisition/readback methods src/.pi/ extensions/ system-prompts/ @@ -422,15 +429,9 @@ src/.pi/ state.ts [ts] legal (op_mode × strategy × lens) tuple table; code-owned SKILL.md path list + family/kind/legality metadata context/*.ts [ts] D60-L pull-tool context surface (read_workspace_context, read_session_context) - skills/ - strategies/*/SKILL.md [md] step-wise-decision-tree, step-wise-disambiguate, freestyle - lenses/*/SKILL.md [md] intent, design, oracle (future execute: plan, sync, scope) - methods/*/SKILL.md [md] run-structured-exchange, capture, generate-proposal, - read-context, commit-graph, review-for-gaps, - acquisition/readback methods ``` -- Manifest availability is code-owned, not filesystem-discovered: `.pi/extensions/agent-runtime/runtime/state.ts` binds each legal axis value to an explicit `src/.pi/skills///SKILL.md` path and each live agent role to its `src/agents/prompts//SYSTEM.md` location. It loads prompt-resource `name` and `description` from `SKILL.md` frontmatter through pi's loader with `includeDefaults: false` and an explicit `skillPaths` list. `composeAgentPrompt()` emits legal resource bindings; the prompt extension reads the selected agent body explicitly and passes it into the pure composer. This keeps the legal set sealed while making the file body/frontmatter the description source of truth. +- Manifest availability is code-owned, not filesystem-discovered: `.pi/extensions/agent-runtime/runtime/state.ts` binds each legal axis value to an explicit `src/agents/skills///SKILL.md` path and each live agent role to its `src/agents/prompts//SYSTEM.md` location. It loads prompt-resource `name` and `description` from `SKILL.md` frontmatter through pi's loader with `includeDefaults: false` and an explicit `skillPaths` list. `composeAgentPrompt()` emits legal resource bindings; the prompt extension reads the selected agent body explicitly and passes it into the pure composer. This keeps the legal set sealed while making the file body/frontmatter the description source of truth. - The D60-L agent-context orchestration layer (TypeScript) lives in `.pi/extensions/agent-runtime/system-prompts/seed/` (pushed contexts) and `.pi/extensions/brunch-data/context/` (read tools), surfaced as the header's compact pushed context or via the read tools; reusable text renderers live in `renderers/`, and contexts are not part of the `read`-on-demand resource manifest and carry no `` family. - Workspace **posture** is workspace-scoped product state persisted in `.brunch/workspace.json`, not spec state, session state, or graph truth. D57-L keeps it off the spec row and graph; D58-L composition injects known posture values into the runtime header as an axis of agent influence, and the `capture-posture` goal (D59-L) can confirm or refine those values conversationally. - Readiness is judged just-in-time per requested capability, not as a user-facing workflow stepper, a stored grade, a session-local phase, or a graph-node-kind whitelist. There is no `readiness_grade` on the spec row (D45-L); capability-readiness (D74-L) is evaluated over the relevant `elicitation_gaps`, and D64-L readiness bands describe non-exclusive evidence groupings feeding the readiness-estimate rollup, goal selection, and context filtering. The soft readiness estimate may surface in UI but gates nothing. A future structural milestone gate for export/plan/execute op-modes is deferred until such an op-mode exists; before readiness grows beyond the current tracer, Brunch still needs a real evaluator path for `manual` gaps and a more differentiated per-capability map than the shared grounding floor (A27-L). @@ -505,7 +506,7 @@ src/.pi/ | **Brunch Pi Profile** | The sealed programmatic wrapper around embedded Pi: settings policy, resource-loader policy, extension factories, keybinding/command policy, tool policy, and prompt policy. It allows Brunch-owned resources while suppressing ambient `.pi/` behavior. | | **Prompt resource** | A Brunch-owned markdown file under `src/.pi/` containing detailed strategy, lens, method, or agent-definition guidance. Prompt resources are loaded by the agent with `read` when needed; they are product control-plane assets, not ambient Pi prompt templates. | | **Prompt-resource manifest** | The small per-turn D58-L `` block injected into the system prompt, listing only runtime-legal Brunch resources with `kind`, `name`, `description`, and `location`. The legal set and locations are code-owned in `.pi/extensions/agent-runtime/runtime/state.ts` (not filesystem-discovered); `name` and `description` are file-owned `SKILL.md` frontmatter read via pi's loader over the explicit path list. The `system-prompts/seed/` and `.pi/extensions/brunch-data/context/` context renderers are not manifest resources. It mirrors Pi's skill-list element structure but is filtered by Brunch runtime state, capability-readiness, and allow-lists. | -| **Method** | A tool-usage or workflow competence advertised as a Brunch prompt resource (`.pi/skills/methods//SKILL.md`): run structured exchanges, capture (the D80/D81/D82 home; full conduct in FE-861), generate proposals/projections, read context, mutate the graph, review for gaps. Method resources explain when to use a tool family and how to sequence it with other tools; executable tool definitions should stay focused on schemas, authority, and runtime behavior. A method may also be backed by a Pi-native skill, but actual tool authority remains code-owned through `op_mode` policy and active-tool gating. `capability` is retired as a synonym — use `method` and `method` in ``. | +| **Method** | A tool-usage or workflow competence advertised as a Brunch prompt resource (`agents/skills/methods//SKILL.md`): run structured exchanges, capture (the D80/D81/D82 home; full conduct in FE-861), generate proposals/projections, read context, mutate the graph, review for gaps. Method resources explain when to use a tool family and how to sequence it with other tools; executable tool definitions should stay focused on schemas, authority, and runtime behavior. A method may also be backed by a Pi-native skill, but actual tool authority remains code-owned through `op_mode` policy and active-tool gating. `capability` is retired as a synonym — use `method` and `method` in ``. | | **Agent context** | The content the agent reasons over — `cwd`, `graph`, or `node` (D60-L): pulled (typed, read-only) from `graph/`/`session/`, optionally projected when a reusable DTO helps, rendered to LLM-string or JSON, surfaced pushed (compose) or pulled (`read_graph` / `read_workspace_context` / `read_session_context`). Graph context explicitly chooses graph-truth vs active-context reads and may filter by node kind, readiness band, edge category/direction, or absence of an edge category (gap query). Distinct from the **workspace projection** (`workspace.state`), which is product/UI state, not agent content. | | **Context-render house style** | The RENDER-stage convention (D83-L) for LLM-facing agent context: a markdown frame (md-pen) with uniform record sets as TOON (`@toon-format/toon`) and file hierarchy as a fenced ASCII tree (stringify-tree over Brunch's gitignore-aware walk), each top-level block wrapped in an XML-style `
` tag. Format follows reader legibility, not internal shape (prose where structure misleads). Agent context clusters into three scopes mirroring `workspace → spec → session` (D19-L): `` (project / documents / spec-roster, no sessions), `` (spec header / graph / ranked gaps / sessions), `` (runtime posture / mentions / transcript). It is the agent-context *dialect* within `renderers/`; the md-pen substrate is shared with human-facing renders (print/evidence), which do not use the `
` clustering. Distinct from the `workspace.state` product-state projection (D60-L). | | **Readiness estimate** | A soft, derived, live per-band coverage projection over `elicitation_gaps`, for UI surfacing only (D45-L). It is *not* stored, *not* authority, and gates nothing — it may regress honestly. Replaces the retired stored `readiness_grade`. | diff --git a/package.json b/package.json index e365c29fe..1d5ca9466 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "build": "tsc -p tsconfig.build.json && npm run build:info && npm run build:pi-assets && npm run build:web", "build:info": "node scripts/write-build-info.mjs", "prepack": "RELEASE=true npm run build", - "build:pi-assets": "mkdir -p dist/.pi/components/workspace-dialog dist/.pi/skills dist/.pi/extensions/subagents dist/agents/prompts && cp -R src/.pi/components/workspace-dialog/assets dist/.pi/components/workspace-dialog/ && cp -R src/agents/prompts/elicitor src/agents/prompts/explorer src/agents/prompts/orchestrator src/agents/prompts/pi-coder src/agents/prompts/projector src/agents/prompts/researcher src/agents/prompts/reviewer dist/agents/prompts/ && cp -R src/.pi/skills/strategies src/.pi/skills/lenses src/.pi/skills/methods dist/.pi/skills/ && cp src/.pi/extensions/subagents/config.json dist/.pi/extensions/subagents/", + "build:pi-assets": "mkdir -p dist/.pi/components/workspace-dialog dist/.pi/extensions/subagents dist/agents/prompts dist/agents/skills && cp -R src/.pi/components/workspace-dialog/assets dist/.pi/components/workspace-dialog/ && cp -R src/agents/prompts/elicitor src/agents/prompts/explorer src/agents/prompts/orchestrator src/agents/prompts/pi-coder src/agents/prompts/projector src/agents/prompts/researcher src/agents/prompts/reviewer dist/agents/prompts/ && cp -R src/agents/skills/strategies src/agents/skills/lenses src/agents/skills/methods dist/agents/skills/ && cp src/.pi/extensions/subagents/config.json dist/.pi/extensions/subagents/", "build:web": "vite build", "seed": "tsx src/graph/seed-fixtures.ts", "generate:ontology": "tsx src/graph/schema/generate-ontology-ref.ts", diff --git a/src/.pi/README.md b/src/.pi/README.md index 001ac69dc..93f74a3c9 100644 --- a/src/.pi/README.md +++ b/src/.pi/README.md @@ -2,13 +2,11 @@ SPEC decisions: D25-L, D34-L, D35-L, D37-L, D39-L, D40-L, D52-L, D58-L, D59-L, D60-L, D69-L, D90-L, D91-L -This directory is Brunch's sealed Pi-harness surface. It contains product extension registrars, reusable TUI components, and the current Brunch prompt-resource skill files that run inside the embedded Pi coding-agent harness. Agent role bodies have moved to `src/agents/prompts/`; this tree remains the Pi runtime adapter/file home during the migration. +This directory is Brunch's sealed Pi-harness surface. It contains product extension registrars and reusable TUI components that run inside the embedded Pi coding-agent harness. Agent role bodies live in `src/agents/prompts/`, and Brunch prompt-resource skills live in `src/agents/skills/`; this tree remains the Pi runtime adapter home during the migration. ## Owns -- The current Pi-facing file home for runtime prompt-resource skills during the `src/agents/` migration. - Pi extension registration: tools, lifecycle hooks, command handlers, autocomplete, TUI chrome, workspace dialogs, and dev-gated read-only introspection. `extensions/session/lifecycle.ts` adapts Pi session/turn hooks into one ordered Brunch session-boundary pipeline: workspace rebinding first, then continuity preparation steps. `extensions/graph/index.ts` stamps the live watermark carriers for own mutations and full graph-overview reads. -- Brunch-owned strategy/lens/method skill files that the agent reads on demand after the runtime manifest advertises them; `src/agents/registry.ts` owns the central path registry. - Reusable Pi TUI components used by those extensions. ## Does NOT own @@ -27,10 +25,6 @@ This directory is Brunch's sealed Pi-harness surface. It contains product extens ├── settings.json dev Pi settings for local `.pi` iteration ├── brunch-pi-settings.ts sealed Pi settings/resource-loader policy ├── brunch-pi-extensions.ts explicit Brunch extension factory; no ambient discovery -├── skills/ Agent Skills-standard prompt resources read by the agent -│ ├── strategies//SKILL.md -│ ├── lenses//SKILL.md -│ └── methods//SKILL.md ├── components/ reusable Pi TUI/message components └── extensions/ Pi registrars and runtime adapters ``` @@ -39,7 +33,6 @@ This directory is Brunch's sealed Pi-harness surface. It contains product extens ```pseudo rules: - .pi/skills/ x> TypeScript imports [SKILL.md resources only] .pi/extensions/ -> agents/, .pi/components/, graph/, session/, rpc/ [adapter imports] .pi/extensions/ x> db/ [no direct storage] graph/, session/ x> .pi/ [domain layers never import Pi] diff --git a/src/.pi/__tests__/architecture.test.ts b/src/.pi/__tests__/architecture.test.ts index c1b7d5903..822ed3a76 100644 --- a/src/.pi/__tests__/architecture.test.ts +++ b/src/.pi/__tests__/architecture.test.ts @@ -32,73 +32,9 @@ const runtimeRegistryExpectations = [ }, ]; -const resourceExpectations = [ - { - file: 'src/.pi/skills/methods/run-structured-exchange/SKILL.md', - needles: ['details.schema', 'schema` plus `v', 'answered`, `cancelled`, or `unavailable`'], - }, - { - file: 'src/.pi/skills/methods/capture/SKILL.md', - needles: ['single home', 'FE-861', 'Gap close/spawn responsibility belongs here'], - }, - { - file: 'src/.pi/skills/methods/generate-proposal/SKILL.md', - needles: ['legibility_cost_of_knowing', 'core_bet', 'graph_refs', '`{ node_id: string }` only'], - }, -]; - -const generateProposalDisclosureExpectations = { - skill: 'src/.pi/skills/methods/generate-proposal/SKILL.md', - references: [ - { - file: 'src/.pi/skills/methods/generate-proposal/references/intent.md', - needles: ['intent plane', 'single pick', 'present_candidates'], - }, - { - file: 'src/.pi/skills/methods/generate-proposal/references/design.md', - needles: ['design plane', 'synthesize', 'present_review_set'], - }, - { - file: 'src/.pi/skills/methods/generate-proposal/references/oracle.md', - needles: ['oracle plane', 'compose', 'blind spots'], - }, - ], - probes: 'src/.pi/skills/methods/generate-proposal/probes.md', -}; - describe('agents topology', () => { - it('keeps prompt guidance in .pi resources and removes the legacy .pi context source', async () => { + it('removes the legacy .pi context source', async () => { await expect(readdir(legacyContextPath)).rejects.toThrow(); - - for (const expectation of resourceExpectations) { - const content = await readFile(join(projectRoot, expectation.file), 'utf8'); - for (const needle of expectation.needles) { - expect(content).toContain(needle); - } - } - }); - - it('keeps generate-proposal plane details behind explicit disclosed references', async () => { - const skill = await readFile(join(projectRoot, generateProposalDisclosureExpectations.skill), 'utf8'); - expect(skill).toContain('references/intent.md'); - expect(skill).toContain('references/design.md'); - expect(skill).toContain('references/oracle.md'); - expect(skill).toContain('Do not write picked intent candidates to the graph'); - expect(skill).toContain('Cite existing ontology/render surfaces'); - - for (const expectation of generateProposalDisclosureExpectations.references) { - const content = await readFile(join(projectRoot, expectation.file), 'utf8'); - for (const needle of expectation.needles) { - expect(content).toContain(needle); - } - } - - const probes = await readFile(join(projectRoot, generateProposalDisclosureExpectations.probes), 'utf8'); - expect(probes).toContain('Model: GPT-5.5 Last run: 2026-06-24'); - expect(probes).toContain('intent-pick'); - expect(probes).toContain('design-synthesize'); - expect(probes).toContain('oracle-compose'); - expect(probes).toContain('should NOT fire'); }); it('keeps named future agent bodies out of the runtime registry', async () => { diff --git a/src/.pi/__tests__/prompt-shape-readmes.test.ts b/src/.pi/__tests__/prompt-shape-readmes.test.ts deleted file mode 100644 index eaf58c25e..000000000 --- a/src/.pi/__tests__/prompt-shape-readmes.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { readFileSync } from 'node:fs'; -import { fileURLToPath } from 'node:url'; - -import { describe, expect, it } from 'vitest'; - -function readRepoFile(relativePath: string): string { - return readFileSync(fileURLToPath(new URL(`../../../${relativePath}`, import.meta.url)), 'utf8'); -} - -describe('prompt-skill shape decisions', () => { - it('records adopted prompt-skill topology and remaining deferred prompt-skill triggers in the local README', () => { - const skillsReadme = readRepoFile('src/.pi/skills/README.md'); - - expect(skillsReadme).toContain('Agent Skills-standard prompt resources'); - expect(skillsReadme).toContain('/SKILL.md'); - expect(skillsReadme).toContain('references/` subfiles'); - expect(skillsReadme).toContain('progressive disclosure'); - expect(skillsReadme).toContain('_generated/ typed-vocab references'); - expect(skillsReadme).toContain('concrete citing need appears'); - expect(skillsReadme).toContain('drift-checked'); - }); -}); diff --git a/src/.pi/extensions/README.md b/src/.pi/extensions/README.md index 0f8f7dd05..0a869477d 100644 --- a/src/.pi/extensions/README.md +++ b/src/.pi/extensions/README.md @@ -8,7 +8,7 @@ Pi-facing registration and adaptation only: lifecycle hooks, agent tool definiti ## Does NOT own -- Agent role prompt definitions and skill resource bodies (markdown) — `agents/prompts/` and `.pi/skills/`. Prompt composition and prompt-resource legality live in `agent-runtime/` until their move slice lands. +- Agent role prompt definitions and skill resource bodies (markdown) — `agents/prompts/` and `agents/skills/`. Prompt composition and prompt-resource legality live in `agent-runtime/` until their move slice lands. - Graph truth, graph mutation policy, or graph readers — top-level `graph/`. - Pi JSONL/session semantics, runtime-state projection, workspace coordination, or transcript exchange projection — top-level `session/`, `projections/`, and related domain seams. - Reusable DTO projection or reusable markdown/text rendering — top-level `projections/` and `renderers/`. diff --git a/src/.pi/extensions/agent-runtime/system-prompts/__snapshots__/elicitor--auto-floor-gaps-open.md b/src/.pi/extensions/agent-runtime/system-prompts/__snapshots__/elicitor--auto-floor-gaps-open.md index ba766a356..b75114959 100644 --- a/src/.pi/extensions/agent-runtime/system-prompts/__snapshots__/elicitor--auto-floor-gaps-open.md +++ b/src/.pi/extensions/agent-runtime/system-prompts/__snapshots__/elicitor--auto-floor-gaps-open.md @@ -35,73 +35,73 @@ When a skill file references a relative path, resolve it against the skill direc strategy step-wise-decision-tree Ask one structured question at a time and branch from the answer. - /src/.pi/skills/strategies/step-wise-decision-tree/SKILL.md + /src/agents/skills/strategies/step-wise-decision-tree/SKILL.md strategy step-wise-disambiguate Use contrastive examples to collapse meaningful ambiguity. - /src/.pi/skills/strategies/step-wise-disambiguate/SKILL.md + /src/agents/skills/strategies/step-wise-disambiguate/SKILL.md lens intent Focus on intent-plane claims: goals, terms, assumptions, constraints, and decisions. - /src/.pi/skills/lenses/intent/SKILL.md + /src/agents/skills/lenses/intent/SKILL.md method run-structured-exchange Present typed Brunch exchanges and request typed responses. - /src/.pi/skills/methods/run-structured-exchange/SKILL.md + /src/agents/skills/methods/run-structured-exchange/SKILL.md method capture Capture selected-spec facts and gap noticings through the deferred FE-861 sweep conduct. - /src/.pi/skills/methods/capture/SKILL.md + /src/agents/skills/methods/capture/SKILL.md method commit-graph Commit graph truth only through Brunch graph tools and CommandExecutor-backed results. - /src/.pi/skills/methods/commit-graph/SKILL.md + /src/agents/skills/methods/commit-graph/SKILL.md method elicit-by-question Acquire missing material by asking the human one focused question. - /src/.pi/skills/methods/elicit-by-question/SKILL.md + /src/agents/skills/methods/elicit-by-question/SKILL.md method ingest-paste Acquire user-provided pasted material as conversational transcript content. - /src/.pi/skills/methods/ingest-paste/SKILL.md + /src/agents/skills/methods/ingest-paste/SKILL.md method read-referenced-documents Read bounded user-referenced documents and digest them before capture. - /src/.pi/skills/methods/read-referenced-documents/SKILL.md + /src/agents/skills/methods/read-referenced-documents/SKILL.md method explore-and-characterize Explore a bounded brownfield area and write a characterization digest before capture. - /src/.pi/skills/methods/explore-and-characterize/SKILL.md + /src/agents/skills/methods/explore-and-characterize/SKILL.md method read-context Use pushed context handles and read-only context tools for selected-spec context. - /src/.pi/skills/methods/read-context/SKILL.md + /src/agents/skills/methods/read-context/SKILL.md method generate-proposal Generate reviewable candidate graph material: intent-pick, design-synthesize, or oracle-compose. Not for extractive intent/design/oracle lenses that ask or interpret without proposing graph drafts. - /src/.pi/skills/methods/generate-proposal/SKILL.md + /src/agents/skills/methods/generate-proposal/SKILL.md diff --git a/src/.pi/extensions/agent-runtime/system-prompts/__snapshots__/elicitor--auto-high-coverage.md b/src/.pi/extensions/agent-runtime/system-prompts/__snapshots__/elicitor--auto-high-coverage.md index 9cb6838e9..b3ee23b73 100644 --- a/src/.pi/extensions/agent-runtime/system-prompts/__snapshots__/elicitor--auto-high-coverage.md +++ b/src/.pi/extensions/agent-runtime/system-prompts/__snapshots__/elicitor--auto-high-coverage.md @@ -30,91 +30,91 @@ When a skill file references a relative path, resolve it against the skill direc strategy step-wise-decision-tree Ask one structured question at a time and branch from the answer. - /src/.pi/skills/strategies/step-wise-decision-tree/SKILL.md + /src/agents/skills/strategies/step-wise-decision-tree/SKILL.md strategy step-wise-disambiguate Use contrastive examples to collapse meaningful ambiguity. - /src/.pi/skills/strategies/step-wise-disambiguate/SKILL.md + /src/agents/skills/strategies/step-wise-disambiguate/SKILL.md lens intent Focus on intent-plane claims: goals, terms, assumptions, constraints, and decisions. - /src/.pi/skills/lenses/intent/SKILL.md + /src/agents/skills/lenses/intent/SKILL.md lens design Focus on design implications and module/interface boundaries. - /src/.pi/skills/lenses/design/SKILL.md + /src/agents/skills/lenses/design/SKILL.md lens oracle Focus on verification obligations, checks, evidence, and blind spots. - /src/.pi/skills/lenses/oracle/SKILL.md + /src/agents/skills/lenses/oracle/SKILL.md method run-structured-exchange Present typed Brunch exchanges and request typed responses. - /src/.pi/skills/methods/run-structured-exchange/SKILL.md + /src/agents/skills/methods/run-structured-exchange/SKILL.md method capture Capture selected-spec facts and gap noticings through the deferred FE-861 sweep conduct. - /src/.pi/skills/methods/capture/SKILL.md + /src/agents/skills/methods/capture/SKILL.md method commit-graph Commit graph truth only through Brunch graph tools and CommandExecutor-backed results. - /src/.pi/skills/methods/commit-graph/SKILL.md + /src/agents/skills/methods/commit-graph/SKILL.md method elicit-by-question Acquire missing material by asking the human one focused question. - /src/.pi/skills/methods/elicit-by-question/SKILL.md + /src/agents/skills/methods/elicit-by-question/SKILL.md method ingest-paste Acquire user-provided pasted material as conversational transcript content. - /src/.pi/skills/methods/ingest-paste/SKILL.md + /src/agents/skills/methods/ingest-paste/SKILL.md method read-referenced-documents Read bounded user-referenced documents and digest them before capture. - /src/.pi/skills/methods/read-referenced-documents/SKILL.md + /src/agents/skills/methods/read-referenced-documents/SKILL.md method explore-and-characterize Explore a bounded brownfield area and write a characterization digest before capture. - /src/.pi/skills/methods/explore-and-characterize/SKILL.md + /src/agents/skills/methods/explore-and-characterize/SKILL.md method read-context Use pushed context handles and read-only context tools for selected-spec context. - /src/.pi/skills/methods/read-context/SKILL.md + /src/agents/skills/methods/read-context/SKILL.md method generate-proposal Generate reviewable candidate graph material: intent-pick, design-synthesize, or oracle-compose. Not for extractive intent/design/oracle lenses that ask or interpret without proposing graph drafts. - /src/.pi/skills/methods/generate-proposal/SKILL.md + /src/agents/skills/methods/generate-proposal/SKILL.md method review-for-gaps Review commitments for gaps, conflicts, and verification debt. - /src/.pi/skills/methods/review-for-gaps/SKILL.md + /src/agents/skills/methods/review-for-gaps/SKILL.md diff --git a/src/.pi/extensions/agent-runtime/system-prompts/__snapshots__/elicitor--pinned-strategy-lens.md b/src/.pi/extensions/agent-runtime/system-prompts/__snapshots__/elicitor--pinned-strategy-lens.md index 37c18c1b4..c7ff32d7f 100644 --- a/src/.pi/extensions/agent-runtime/system-prompts/__snapshots__/elicitor--pinned-strategy-lens.md +++ b/src/.pi/extensions/agent-runtime/system-prompts/__snapshots__/elicitor--pinned-strategy-lens.md @@ -30,73 +30,73 @@ When a skill file references a relative path, resolve it against the skill direc strategy step-wise-disambiguate Use contrastive examples to collapse meaningful ambiguity. - /src/.pi/skills/strategies/step-wise-disambiguate/SKILL.md + /src/agents/skills/strategies/step-wise-disambiguate/SKILL.md lens design Focus on design implications and module/interface boundaries. - /src/.pi/skills/lenses/design/SKILL.md + /src/agents/skills/lenses/design/SKILL.md method run-structured-exchange Present typed Brunch exchanges and request typed responses. - /src/.pi/skills/methods/run-structured-exchange/SKILL.md + /src/agents/skills/methods/run-structured-exchange/SKILL.md method capture Capture selected-spec facts and gap noticings through the deferred FE-861 sweep conduct. - /src/.pi/skills/methods/capture/SKILL.md + /src/agents/skills/methods/capture/SKILL.md method commit-graph Commit graph truth only through Brunch graph tools and CommandExecutor-backed results. - /src/.pi/skills/methods/commit-graph/SKILL.md + /src/agents/skills/methods/commit-graph/SKILL.md method elicit-by-question Acquire missing material by asking the human one focused question. - /src/.pi/skills/methods/elicit-by-question/SKILL.md + /src/agents/skills/methods/elicit-by-question/SKILL.md method ingest-paste Acquire user-provided pasted material as conversational transcript content. - /src/.pi/skills/methods/ingest-paste/SKILL.md + /src/agents/skills/methods/ingest-paste/SKILL.md method read-referenced-documents Read bounded user-referenced documents and digest them before capture. - /src/.pi/skills/methods/read-referenced-documents/SKILL.md + /src/agents/skills/methods/read-referenced-documents/SKILL.md method explore-and-characterize Explore a bounded brownfield area and write a characterization digest before capture. - /src/.pi/skills/methods/explore-and-characterize/SKILL.md + /src/agents/skills/methods/explore-and-characterize/SKILL.md method read-context Use pushed context handles and read-only context tools for selected-spec context. - /src/.pi/skills/methods/read-context/SKILL.md + /src/agents/skills/methods/read-context/SKILL.md method generate-proposal Generate reviewable candidate graph material: intent-pick, design-synthesize, or oracle-compose. Not for extractive intent/design/oracle lenses that ask or interpret without proposing graph drafts. - /src/.pi/skills/methods/generate-proposal/SKILL.md + /src/agents/skills/methods/generate-proposal/SKILL.md method review-for-gaps Review commitments for gaps, conflicts, and verification debt. - /src/.pi/skills/methods/review-for-gaps/SKILL.md + /src/agents/skills/methods/review-for-gaps/SKILL.md diff --git a/src/.pi/extensions/agent-runtime/system-prompts/__snapshots__/elicitor--pushed-context.md b/src/.pi/extensions/agent-runtime/system-prompts/__snapshots__/elicitor--pushed-context.md index 69a2e11bd..6d95ec63c 100644 --- a/src/.pi/extensions/agent-runtime/system-prompts/__snapshots__/elicitor--pushed-context.md +++ b/src/.pi/extensions/agent-runtime/system-prompts/__snapshots__/elicitor--pushed-context.md @@ -40,73 +40,73 @@ When a skill file references a relative path, resolve it against the skill direc strategy step-wise-decision-tree Ask one structured question at a time and branch from the answer. - /src/.pi/skills/strategies/step-wise-decision-tree/SKILL.md + /src/agents/skills/strategies/step-wise-decision-tree/SKILL.md strategy step-wise-disambiguate Use contrastive examples to collapse meaningful ambiguity. - /src/.pi/skills/strategies/step-wise-disambiguate/SKILL.md + /src/agents/skills/strategies/step-wise-disambiguate/SKILL.md lens intent Focus on intent-plane claims: goals, terms, assumptions, constraints, and decisions. - /src/.pi/skills/lenses/intent/SKILL.md + /src/agents/skills/lenses/intent/SKILL.md method run-structured-exchange Present typed Brunch exchanges and request typed responses. - /src/.pi/skills/methods/run-structured-exchange/SKILL.md + /src/agents/skills/methods/run-structured-exchange/SKILL.md method capture Capture selected-spec facts and gap noticings through the deferred FE-861 sweep conduct. - /src/.pi/skills/methods/capture/SKILL.md + /src/agents/skills/methods/capture/SKILL.md method commit-graph Commit graph truth only through Brunch graph tools and CommandExecutor-backed results. - /src/.pi/skills/methods/commit-graph/SKILL.md + /src/agents/skills/methods/commit-graph/SKILL.md method elicit-by-question Acquire missing material by asking the human one focused question. - /src/.pi/skills/methods/elicit-by-question/SKILL.md + /src/agents/skills/methods/elicit-by-question/SKILL.md method ingest-paste Acquire user-provided pasted material as conversational transcript content. - /src/.pi/skills/methods/ingest-paste/SKILL.md + /src/agents/skills/methods/ingest-paste/SKILL.md method read-referenced-documents Read bounded user-referenced documents and digest them before capture. - /src/.pi/skills/methods/read-referenced-documents/SKILL.md + /src/agents/skills/methods/read-referenced-documents/SKILL.md method explore-and-characterize Explore a bounded brownfield area and write a characterization digest before capture. - /src/.pi/skills/methods/explore-and-characterize/SKILL.md + /src/agents/skills/methods/explore-and-characterize/SKILL.md method read-context Use pushed context handles and read-only context tools for selected-spec context. - /src/.pi/skills/methods/read-context/SKILL.md + /src/agents/skills/methods/read-context/SKILL.md method generate-proposal Generate reviewable candidate graph material: intent-pick, design-synthesize, or oracle-compose. Not for extractive intent/design/oracle lenses that ask or interpret without proposing graph drafts. - /src/.pi/skills/methods/generate-proposal/SKILL.md + /src/agents/skills/methods/generate-proposal/SKILL.md diff --git a/src/.pi/extensions/agent-runtime/system-prompts/__tests__/compose.test.ts b/src/.pi/extensions/agent-runtime/system-prompts/__tests__/compose.test.ts index ecf658d0d..caac2cb45 100644 --- a/src/.pi/extensions/agent-runtime/system-prompts/__tests__/compose.test.ts +++ b/src/.pi/extensions/agent-runtime/system-prompts/__tests__/compose.test.ts @@ -327,7 +327,7 @@ describe('composeAgentPrompt', () => { ]); }); - it('advertises only readable code-owned .pi prompt resources without filesystem discovery', async () => { + it('advertises only readable code-owned prompt resources without filesystem discovery', async () => { const result = composeAgentPrompt({ agentId: 'elicitor', sessionState: projectBrunchAgentState([]), @@ -338,7 +338,7 @@ describe('composeAgentPrompt', () => { }); for (const entry of Object.values(result.manifests).flat()) { - expect(relative(projectRoot, entry.location).startsWith('src/.pi/')).toBe(true); + expect(relative(projectRoot, entry.location).startsWith('src/agents/')).toBe(true); await expect(access(entry.location)).resolves.toBeUndefined(); } expect(result.prompt).not.toContain('unlisted-fixture'); @@ -357,7 +357,7 @@ describe('composeAgentPrompt', () => { ]; for (const entry of entries) { - expect(relative(projectRoot, entry.location).startsWith('src/.pi/skills/')).toBe(true); + expect(relative(projectRoot, entry.location).startsWith('src/agents/skills/')).toBe(true); expect(entry.location.endsWith(`/${entry.name}/SKILL.md`)).toBe(true); const raw = await readFile(entry.location, 'utf8'); const { frontmatter, body } = parseFrontmatter(raw); diff --git a/src/README.md b/src/README.md index 2e6909eb6..951cb7f4e 100644 --- a/src/README.md +++ b/src/README.md @@ -1,6 +1,6 @@ # src/ — Brunch source topology -Decision D52-L in `memory/SPEC.md` locks the target layout. The current LLM-context ingress refactor introduces `agents/` as the Pi-independent owner for Brunch-authored agent context; agent prompt bodies now live there, while prompt-resource skills still live under `.pi` until their move slice lands. +Decision D52-L in `memory/SPEC.md` locks the target layout. The current LLM-context ingress refactor introduces `agents/` as the Pi-independent owner for Brunch-authored agent context; agent prompt bodies and prompt-resource skills now live there. ```text src/ @@ -9,10 +9,10 @@ src/ ├── scripts/ Local executable utilities │ ├── agents/ Pi-independent owner for Brunch-authored LLM context ingress -│ └── prompts/ agent role body markdown resources +│ ├── prompts/ agent role body markdown resources +│ └── skills/ prompt-resource markdown resources │ ├── .pi/ Sealed Pi-harness runtime surface -│ ├── skills/ current prompt-resource file home during migration │ ├── components/ reusable Pi TUI/message components │ └── extensions/ Pi registrars: tools, hooks, commands, TUI affordances │ @@ -45,7 +45,7 @@ rules: workspace/ -> constants/ or workspace-local files only projections/* -> agents/, graph/, session/, workspace/ [read/domain imports allowed; agents/ is temporary registry edge] renderers/* -> projections/, graph/, session/, workspace/ as needed for input types - agents/ -> .pi/skills/ [current prompt-resource registry only] + agents/ -> constants/ [prompt/skill markdown + registry only] .pi/ -> agents/, graph/, session/, projections/, renderers/ [Pi runtime adapters/resources] rpc/ -> graph/, session/, projections/, renderers/ app/ -> graph/, session/, projections/, renderers/ @@ -59,10 +59,9 @@ Rules: - `workspace/` owns cwd-scoped identity, inventory, and workspace default-state persistence. It must not import Pi, session, graph, DB, projection, renderer, adapter, transport, app, or web modules. - `graph/` imports from `db/`. No other layer imports `db/` directly. -- `agents/` owns the Brunch-authored LLM-context ingress seam. Today it hosts agent prompt bodies and centralizes the file registry for prompt bodies and prompt-resource skills; later slices move prompt-resource skills, runtime composition, seeds, and agent-visible rendering under this seam. The current `projections/session/runtime-policy.ts` import of this registry is a migration edge only: once runtime policy moves under `agents/runtime/`, projections should stop depending on `agents/`. -- `.pi/` owns Pi-harness extensions/components and temporarily hosts the existing prompt-resource skill files while the LLM-context ingress refactor proceeds. +- `agents/` owns the Brunch-authored LLM-context ingress seam. Today it hosts agent prompt bodies, prompt-resource skills, and their central file registry; later slices move runtime composition, seeds, and agent-visible rendering under this seam. The current `projections/session/runtime-policy.ts` import of this registry is a migration edge only: once runtime policy moves under `agents/runtime/`, projections should stop depending on `agents/`. +- `.pi/` owns Pi-harness extensions/components and no longer hosts Brunch-authored prompt bodies or prompt-resource skills. - `.pi/extensions/` registers Pi tools/hooks/UI affordances and delegates product semantics outward. -- `.pi/skills/` is the current file home, not the long-term conceptual owner, for Brunch-authored read-on-demand markdown resources. - `projections/` owns reusable structured output; `renderers/` owns reusable lossy text output. - `web/` is a separate Vite build target. @@ -76,4 +75,4 @@ The old domain-local `src/{graph,session,structured-exchange}/format/` folders a Runtime-state transcript entry facts live in `session/runtime-state.ts`; reusable flattened runtime-state projection/policy now lives in `projections/session/runtime-state.ts` and `projections/session/runtime-policy.ts`. -The earlier `src/agents/` top-level prompt subtree had moved under `src/.pi/{agents,skills}/`; the new `src/agents/` seam reclaims the name for Pi-independent LLM context ingress. Agent bodies have moved to `src/agents/prompts/`; prompt-resource skills still move later. The old `src/.pi/context/` prompt-pack subtree remains retired. +The earlier `src/agents/` top-level prompt subtree had moved under `src/.pi/{agents,skills}/`; the new `src/agents/` seam reclaims the name for Pi-independent LLM context ingress. Agent bodies have moved to `src/agents/prompts/`; prompt-resource skills have moved to `src/agents/skills/`. The old `src/.pi/context/` prompt-pack subtree remains retired. diff --git a/src/agents/README.md b/src/agents/README.md index 6c6a5ef03..ad66ccff7 100644 --- a/src/agents/README.md +++ b/src/agents/README.md @@ -4,13 +4,14 @@ SPEC decisions: D39-L, D40-L, D52-L, D60-L, D85-L, D90-L, D91-L, D93-L ## Owns -`src/agents/` is the Pi-independent home for Brunch-authored model-facing context. It now owns bundled agent prompt bodies plus the central registry for body and Brunch prompt-resource skill paths; prompt-resource skills still live under `src/.pi/` until their move slice lands. +`src/agents/` is the Pi-independent home for Brunch-authored model-facing context. It now owns bundled agent prompt bodies, Brunch prompt-resource skills, and the central registry for their paths. ```text agents/ ├── README.md ├── prompts/ bundled foreground/background agent body markdown -├── registry.ts path registry for bundled agent bodies and current prompt-resource skills +├── skills/ strategy/lens/method prompt-resource markdown +├── registry.ts path registry for bundled agent bodies and prompt-resource skills └── __tests__/ registry/topology tests ``` @@ -19,7 +20,7 @@ agents/ ```pseudo rules: agents/registry.ts -> agents/prompts/*/SYSTEM.md [body file locations] - agents/registry.ts -> .pi/skills/*/*/SKILL.md [current prompt-resource locations] + agents/registry.ts -> agents/skills/*/*/SKILL.md [prompt-resource locations] .pi/extensions/* -> agents/registry.ts [adapters ask for Brunch-authored context locations] projections/session/runtime-policy.ts -> agents/registry.ts [temporary roster-location edge] agents/ x> Pi extension hooks [no registration side effects] @@ -27,4 +28,4 @@ rules: ## Migration note -This directory is intentionally mid-migration. Agent prompt bodies have moved here byte-stably. Later slices move skills, prompt composition, seed context, runtime policy, and agent-visible renderers here; Pi extensions remain runtime adapters that register hooks/tools and call this layer. +This directory is intentionally mid-migration. Agent prompt bodies and prompt-resource skills have moved here byte-stably. Later slices move prompt composition, seed context, runtime policy, and agent-visible renderers here; Pi extensions remain runtime adapters that register hooks/tools and call this layer. diff --git a/src/agents/__tests__/registry.test.ts b/src/agents/__tests__/registry.test.ts index fa5c92b99..1f6acaead 100644 --- a/src/agents/__tests__/registry.test.ts +++ b/src/agents/__tests__/registry.test.ts @@ -24,8 +24,8 @@ describe('agent context registry', () => { expect(bundledAgentBodyLocation('reviewer')).toMatch(/src\/agents\/prompts\/reviewer\/SYSTEM\.md$/); expect(bundledAgentBodyHome()).toMatch(/src\/agents\/prompts$/); expect(promptResourceLocation('methods', 'generate-proposal')).toMatch( - /src\/\.pi\/skills\/methods\/generate-proposal\/SKILL\.md$/, + /src\/agents\/skills\/methods\/generate-proposal\/SKILL\.md$/, ); - expect(promptResourceAgentDir()).toMatch(/src\/\.pi$/); + expect(promptResourceAgentDir()).toMatch(/src\/agents\/?$/); }); }); diff --git a/src/agents/prompts/README.md b/src/agents/prompts/README.md index d02630585..c6d1d2678 100644 --- a/src/agents/prompts/README.md +++ b/src/agents/prompts/README.md @@ -30,7 +30,7 @@ This directory is markdown-only. It carries no TypeScript and registers no Pi ho - Foreground prompt composition + pushed seed contexts — `.pi/extensions/agent-runtime/system-prompts/` until the runtime/context move slices land. - Background prompt assembly and injected-world child-session wiring — `.pi/extensions/subagents/`. - Prompt-resource manifest selection + tool/method legality — `.pi/extensions/agent-runtime/runtime/` and `src/projections/session/runtime-policy.ts` until the runtime move slice lands. -- Strategy/lens/method prompt-resource skills — currently `.pi/skills/`, moving in a later slice. +- Strategy/lens/method prompt-resource skills — `src/agents/skills/`. - Reusable lossy text/markdown rendering — `renderers/` until agent-visible renderers move. - Pi tool definitions, lifecycle hooks, UI, and background child-session loading/running — `.pi/extensions/*`. diff --git a/src/agents/registry.ts b/src/agents/registry.ts index d0cfdc11e..2f35ac6d9 100644 --- a/src/agents/registry.ts +++ b/src/agents/registry.ts @@ -29,9 +29,9 @@ export function bundledAgentBodyLocation(id: BundledAgentBodyId): string { /** Agent directory passed to Pi's Agent Skills loader for Brunch prompt resources. */ export function promptResourceAgentDir(): string { - return fileURLToPath(new URL('../.pi', import.meta.url)); + return fileURLToPath(new URL('.', import.meta.url)); } export function promptResourceLocation(family: PromptResourceFamily, id: string): string { - return fileURLToPath(new URL(`../.pi/skills/${family}/${id}/SKILL.md`, import.meta.url)); + return fileURLToPath(new URL(`./skills/${family}/${id}/SKILL.md`, import.meta.url)); } diff --git a/src/.pi/skills/README.md b/src/agents/skills/README.md similarity index 82% rename from src/.pi/skills/README.md rename to src/agents/skills/README.md index 09f144ec6..c38942226 100644 --- a/src/.pi/skills/README.md +++ b/src/agents/skills/README.md @@ -1,12 +1,12 @@ -# .pi/skills/ — Brunch prompt-resource skills +# agents/skills/ — Brunch prompt-resource skills SPEC decisions: D25-L, D39-L, D52-L, D58-L, D59-L, D85-L ## Owns -The current file home for Agent Skills-standard prompt resources the Brunch Pi session agent reads on demand after `.pi/extensions/agent-runtime/runtime/state.ts` advertises them in a runtime-filtered `` manifest. +Agent Skills-standard prompt resources the Brunch Pi session agent reads on demand after `.pi/extensions/agent-runtime/runtime/state.ts` advertises them in a runtime-filtered `` manifest. -These are Brunch-authored model-facing prompt resources, not product data models and not ambient filesystem discovery inputs. Conceptual ownership is migrating to `src/agents/`; `src/agents/registry.ts` now owns the central path registry while files still live here. +These are Brunch-authored model-facing prompt resources, not product data models and not ambient filesystem discovery inputs. ## Layout @@ -26,13 +26,13 @@ Each live resource is a directory whose `SKILL.md` has YAML frontmatter (`name`, ```pseudo rules: - .pi/extensions/agent-runtime/runtime/state.ts -> .pi/skills/*/*/SKILL.md [explicit code-owned path list] + .pi/extensions/agent-runtime/runtime/state.ts -> agents/skills/*/*/SKILL.md [explicit code-owned path list via agents/registry.ts] .pi/extensions/agent-runtime/runtime/state.ts -> pi loadSkills(includeDefaults:false, skillPaths=[...]) - .pi/skills/**/SKILL.md x> TypeScript imports [read-only prompt resources] - .pi/skills/ x> graph mutation [guidance only] + agents/skills/**/SKILL.md x> TypeScript imports [read-only prompt resources] + agents/skills/ x> graph mutation [guidance only] ``` -The legal set is sealed by the code-owned path list in `.pi/extensions/agent-runtime/runtime/state.ts`; adding a `SKILL.md` does not make it available until that table enumerates it. `src/agents/registry.ts` owns current file locations. Frontmatter owns `name` and `description`; code owns axis family, legality, and location enumeration. The former `goals/` family is retired by D85-L; the elicitor objective postures are inline in `src/.pi/agents/elicitor/SYSTEM.md`. +The legal set is sealed by the code-owned path list in `.pi/extensions/agent-runtime/runtime/state.ts`; adding a `SKILL.md` does not make it available until that table enumerates it. `src/agents/registry.ts` owns file locations. Frontmatter owns `name` and `description`; code owns axis family, legality, and location enumeration. The former `goals/` family is retired by D85-L; the elicitor objective postures are inline in `src/agents/prompts/elicitor/SYSTEM.md`. ## Prompt-resource sub-shapes diff --git a/src/.pi/skills/__fixtures__/unlisted-fixture/SKILL.md b/src/agents/skills/__fixtures__/unlisted-fixture/SKILL.md similarity index 50% rename from src/.pi/skills/__fixtures__/unlisted-fixture/SKILL.md rename to src/agents/skills/__fixtures__/unlisted-fixture/SKILL.md index 962b75d59..04b3f950a 100644 --- a/src/.pi/skills/__fixtures__/unlisted-fixture/SKILL.md +++ b/src/agents/skills/__fixtures__/unlisted-fixture/SKILL.md @@ -5,4 +5,4 @@ description: "Fixture skill that proves Brunch does not advertise unlisted files # Unlisted fixture -This fixture intentionally lives under `src/.pi/skills/` but is not in the code-owned prompt-resource path list. It must never appear in composed Brunch manifests. +This fixture intentionally lives under `src/agents/skills/` but is not in the code-owned prompt-resource path list. It must never appear in composed Brunch manifests. diff --git a/src/agents/skills/__tests__/prompt-resources.test.ts b/src/agents/skills/__tests__/prompt-resources.test.ts new file mode 100644 index 000000000..f909e22b8 --- /dev/null +++ b/src/agents/skills/__tests__/prompt-resources.test.ts @@ -0,0 +1,87 @@ +import { readFile } from 'node:fs/promises'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { describe, expect, it } from 'vitest'; + +const projectRoot = dirname(dirname(dirname(dirname(dirname(fileURLToPath(import.meta.url)))))); + +const resourceExpectations = [ + { + file: 'src/agents/skills/methods/run-structured-exchange/SKILL.md', + needles: ['details.schema', 'schema` plus `v', 'answered`, `cancelled`, or `unavailable`'], + }, + { + file: 'src/agents/skills/methods/capture/SKILL.md', + needles: ['single home', 'FE-861', 'Gap close/spawn responsibility belongs here'], + }, + { + file: 'src/agents/skills/methods/generate-proposal/SKILL.md', + needles: ['legibility_cost_of_knowing', 'core_bet', 'graph_refs', '`{ node_id: string }` only'], + }, +]; + +const generateProposalDisclosureExpectations = { + skill: 'src/agents/skills/methods/generate-proposal/SKILL.md', + references: [ + { + file: 'src/agents/skills/methods/generate-proposal/references/intent.md', + needles: ['intent plane', 'single pick', 'present_candidates'], + }, + { + file: 'src/agents/skills/methods/generate-proposal/references/design.md', + needles: ['design plane', 'synthesize', 'present_review_set'], + }, + { + file: 'src/agents/skills/methods/generate-proposal/references/oracle.md', + needles: ['oracle plane', 'compose', 'blind spots'], + }, + ], + probes: 'src/agents/skills/methods/generate-proposal/probes.md', +}; + +describe('prompt-resource skills', () => { + it('keeps prompt-resource guidance in skill resources', async () => { + for (const expectation of resourceExpectations) { + const content = await readFile(join(projectRoot, expectation.file), 'utf8'); + for (const needle of expectation.needles) { + expect(content).toContain(needle); + } + } + }); + + it('keeps generate-proposal plane details behind explicit disclosed references', async () => { + const skill = await readFile(join(projectRoot, generateProposalDisclosureExpectations.skill), 'utf8'); + expect(skill).toContain('references/intent.md'); + expect(skill).toContain('references/design.md'); + expect(skill).toContain('references/oracle.md'); + expect(skill).toContain('Do not write picked intent candidates to the graph'); + expect(skill).toContain('Cite existing ontology/render surfaces'); + + for (const expectation of generateProposalDisclosureExpectations.references) { + const content = await readFile(join(projectRoot, expectation.file), 'utf8'); + for (const needle of expectation.needles) { + expect(content).toContain(needle); + } + } + + const probes = await readFile(join(projectRoot, generateProposalDisclosureExpectations.probes), 'utf8'); + expect(probes).toContain('Model: GPT-5.5 Last run: 2026-06-24'); + expect(probes).toContain('intent-pick'); + expect(probes).toContain('design-synthesize'); + expect(probes).toContain('oracle-compose'); + expect(probes).toContain('should NOT fire'); + }); + + it('records adopted prompt-skill topology and deferred prompt-skill triggers in the local README', async () => { + const readme = await readFile(join(projectRoot, 'src/agents/skills/README.md'), 'utf8'); + + expect(readme).toContain('Agent Skills-standard prompt resources'); + expect(readme).toContain('/SKILL.md'); + expect(readme).toContain('references/` subfiles'); + expect(readme).toContain('progressive disclosure'); + expect(readme).toContain('_generated/ typed-vocab references'); + expect(readme).toContain('concrete citing need appears'); + expect(readme).toContain('drift-checked'); + }); +}); diff --git a/src/.pi/skills/lenses/README.md b/src/agents/skills/lenses/README.md similarity index 100% rename from src/.pi/skills/lenses/README.md rename to src/agents/skills/lenses/README.md diff --git a/src/.pi/skills/lenses/design/SKILL.md b/src/agents/skills/lenses/design/SKILL.md similarity index 100% rename from src/.pi/skills/lenses/design/SKILL.md rename to src/agents/skills/lenses/design/SKILL.md diff --git a/src/.pi/skills/lenses/intent/SKILL.md b/src/agents/skills/lenses/intent/SKILL.md similarity index 100% rename from src/.pi/skills/lenses/intent/SKILL.md rename to src/agents/skills/lenses/intent/SKILL.md diff --git a/src/.pi/skills/lenses/oracle/SKILL.md b/src/agents/skills/lenses/oracle/SKILL.md similarity index 100% rename from src/.pi/skills/lenses/oracle/SKILL.md rename to src/agents/skills/lenses/oracle/SKILL.md diff --git a/src/.pi/skills/methods/capture/SKILL.md b/src/agents/skills/methods/capture/SKILL.md similarity index 100% rename from src/.pi/skills/methods/capture/SKILL.md rename to src/agents/skills/methods/capture/SKILL.md diff --git a/src/.pi/skills/methods/commit-graph/SKILL.md b/src/agents/skills/methods/commit-graph/SKILL.md similarity index 100% rename from src/.pi/skills/methods/commit-graph/SKILL.md rename to src/agents/skills/methods/commit-graph/SKILL.md diff --git a/src/.pi/skills/methods/elicit-by-question/SKILL.md b/src/agents/skills/methods/elicit-by-question/SKILL.md similarity index 100% rename from src/.pi/skills/methods/elicit-by-question/SKILL.md rename to src/agents/skills/methods/elicit-by-question/SKILL.md diff --git a/src/.pi/skills/methods/explore-and-characterize/SKILL.md b/src/agents/skills/methods/explore-and-characterize/SKILL.md similarity index 100% rename from src/.pi/skills/methods/explore-and-characterize/SKILL.md rename to src/agents/skills/methods/explore-and-characterize/SKILL.md diff --git a/src/.pi/skills/methods/generate-proposal/SKILL.md b/src/agents/skills/methods/generate-proposal/SKILL.md similarity index 100% rename from src/.pi/skills/methods/generate-proposal/SKILL.md rename to src/agents/skills/methods/generate-proposal/SKILL.md diff --git a/src/.pi/skills/methods/generate-proposal/probes.md b/src/agents/skills/methods/generate-proposal/probes.md similarity index 100% rename from src/.pi/skills/methods/generate-proposal/probes.md rename to src/agents/skills/methods/generate-proposal/probes.md diff --git a/src/.pi/skills/methods/generate-proposal/references/design.md b/src/agents/skills/methods/generate-proposal/references/design.md similarity index 100% rename from src/.pi/skills/methods/generate-proposal/references/design.md rename to src/agents/skills/methods/generate-proposal/references/design.md diff --git a/src/.pi/skills/methods/generate-proposal/references/intent.md b/src/agents/skills/methods/generate-proposal/references/intent.md similarity index 100% rename from src/.pi/skills/methods/generate-proposal/references/intent.md rename to src/agents/skills/methods/generate-proposal/references/intent.md diff --git a/src/.pi/skills/methods/generate-proposal/references/oracle.md b/src/agents/skills/methods/generate-proposal/references/oracle.md similarity index 100% rename from src/.pi/skills/methods/generate-proposal/references/oracle.md rename to src/agents/skills/methods/generate-proposal/references/oracle.md diff --git a/src/.pi/skills/methods/ingest-paste/SKILL.md b/src/agents/skills/methods/ingest-paste/SKILL.md similarity index 100% rename from src/.pi/skills/methods/ingest-paste/SKILL.md rename to src/agents/skills/methods/ingest-paste/SKILL.md diff --git a/src/.pi/skills/methods/read-context/SKILL.md b/src/agents/skills/methods/read-context/SKILL.md similarity index 100% rename from src/.pi/skills/methods/read-context/SKILL.md rename to src/agents/skills/methods/read-context/SKILL.md diff --git a/src/.pi/skills/methods/read-referenced-documents/SKILL.md b/src/agents/skills/methods/read-referenced-documents/SKILL.md similarity index 100% rename from src/.pi/skills/methods/read-referenced-documents/SKILL.md rename to src/agents/skills/methods/read-referenced-documents/SKILL.md diff --git a/src/.pi/skills/methods/review-for-gaps/SKILL.md b/src/agents/skills/methods/review-for-gaps/SKILL.md similarity index 100% rename from src/.pi/skills/methods/review-for-gaps/SKILL.md rename to src/agents/skills/methods/review-for-gaps/SKILL.md diff --git a/src/.pi/skills/methods/run-structured-exchange/SKILL.md b/src/agents/skills/methods/run-structured-exchange/SKILL.md similarity index 100% rename from src/.pi/skills/methods/run-structured-exchange/SKILL.md rename to src/agents/skills/methods/run-structured-exchange/SKILL.md diff --git a/src/.pi/skills/strategies/README.md b/src/agents/skills/strategies/README.md similarity index 100% rename from src/.pi/skills/strategies/README.md rename to src/agents/skills/strategies/README.md diff --git a/src/.pi/skills/strategies/freestyle/SKILL.md b/src/agents/skills/strategies/freestyle/SKILL.md similarity index 100% rename from src/.pi/skills/strategies/freestyle/SKILL.md rename to src/agents/skills/strategies/freestyle/SKILL.md diff --git a/src/.pi/skills/strategies/step-wise-decision-tree/SKILL.md b/src/agents/skills/strategies/step-wise-decision-tree/SKILL.md similarity index 100% rename from src/.pi/skills/strategies/step-wise-decision-tree/SKILL.md rename to src/agents/skills/strategies/step-wise-decision-tree/SKILL.md diff --git a/src/.pi/skills/strategies/step-wise-disambiguate/SKILL.md b/src/agents/skills/strategies/step-wise-disambiguate/SKILL.md similarity index 100% rename from src/.pi/skills/strategies/step-wise-disambiguate/SKILL.md rename to src/agents/skills/strategies/step-wise-disambiguate/SKILL.md diff --git a/src/dev/__tests__/generate-fan-out-witness.test.ts b/src/dev/__tests__/generate-fan-out-witness.test.ts index e29f93dc3..a7b4324be 100644 --- a/src/dev/__tests__/generate-fan-out-witness.test.ts +++ b/src/dev/__tests__/generate-fan-out-witness.test.ts @@ -93,8 +93,8 @@ describe('generate fan-out witness report', () => { it('passes only from transcript-observed oracle pointer, candidates, and no graph write', () => { const sessionText = [ oracleBranchEntry(), - readEntry('src/.pi/skills/methods/generate-proposal/SKILL.md'), - readEntry('src/.pi/skills/methods/generate-proposal/references/oracle.md'), + readEntry('src/agents/skills/methods/generate-proposal/SKILL.md'), + readEntry('src/agents/skills/methods/generate-proposal/references/oracle.md'), presentCandidatesEntry(), ].join('\n'); @@ -131,8 +131,8 @@ describe('generate fan-out witness report', () => { it('fails closed when candidates appear after a graph write marker', () => { const sessionText = [ oracleBranchEntry(), - readEntry('src/.pi/skills/methods/generate-proposal/SKILL.md'), - readEntry('src/.pi/skills/methods/generate-proposal/references/oracle.md'), + readEntry('src/agents/skills/methods/generate-proposal/SKILL.md'), + readEntry('src/agents/skills/methods/generate-proposal/references/oracle.md'), toolResultEntry('mutate_graph', { status: 'success', lsn: 4 }), presentCandidatesEntry(), ].join('\n'); @@ -183,7 +183,7 @@ describe('generate fan-out witness report', () => { it('writes scratch artifact references portably', async () => { const fixtureRoot = await mkdtemp(join(tmpdir(), 'brunch-generate-fan-out-artifacts-')); - const sessionText = [readEntry('src/.pi/skills/methods/generate-proposal/SKILL.md')].join('\n'); + const sessionText = [readEntry('src/agents/skills/methods/generate-proposal/SKILL.md')].join('\n'); const report: GenerateFanOutWitnessReport = summarizeGenerateFanOutWitness({ runId: 'artifact-run', generatedAt: '2026-06-24T00:00:00.000Z', diff --git a/src/graph/README.md b/src/graph/README.md index 3d6c16a9f..fd4302ab8 100644 --- a/src/graph/README.md +++ b/src/graph/README.md @@ -32,7 +32,7 @@ SPEC decisions: D4-L, D20-L, D27-L, D45-L, D51-L, D52-L, D53-L, D54-L, D60-L, D6 - **Capture** — the submit-time `capture/` structured-response translator was deleted 2026-06-19 (D80-L fossil retirement). Capture is now elicitor - turn-boundary sweep conduct in `src/.pi/skills/methods/capture.md`; the graph + turn-boundary sweep conduct in `src/agents/skills/methods/capture.md`; the graph layer owns only the `mutate_graph` / `update_elicitation_gaps` mutation/gap boundary that sweep conduct routes through, not a product-side extraction pass. - **Readers / query functions** (`queries.ts`) — graph reads at multiple diff --git a/src/graph/schema/generate-ontology-ref.ts b/src/graph/schema/generate-ontology-ref.ts index f3cce413f..2bfa723fd 100644 --- a/src/graph/schema/generate-ontology-ref.ts +++ b/src/graph/schema/generate-ontology-ref.ts @@ -5,7 +5,7 @@ * truth (D73-L); the emitted file is never hand-edited. * * First materialization of the `_generated/` mechanism deferred in - * `src/.pi/skills/README.md`. This slice generates only the kind→band table; + * `src/agents/skills/README.md`. This slice generates only the kind→band table; * further vocabulary tables (edge categories, detail forms) are added when a * concrete citing need appears, not speculatively. * From b14e603f6281a48efac938a0800fe3105bce2013 Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Thu, 25 Jun 2026 18:29:33 +0200 Subject: [PATCH 13/54] Move agent runtime policy under agents --- memory/PLAN.md | 2 +- memory/REFACTOR.md | 2 +- memory/SPEC.md | 35 ++++++++++--------- src/.pi/__tests__/prompting.test.ts | 2 +- src/.pi/extensions/README.md | 8 ++--- .../extensions/agent-runtime/runtime/index.ts | 4 +-- .../agent-runtime/system-prompts/index.ts | 2 +- .../brunch-data/reconciliation/index.test.ts | 2 +- .../extensions/subagents/prompt-assembly.ts | 5 ++- src/README.md | 5 +-- src/agents/README.md | 3 +- src/agents/runtime/README.md | 33 +++++++++++++++++ .../elicitor--auto-floor-gaps-open.md | 0 .../elicitor--auto-high-coverage.md | 0 .../elicitor--pinned-strategy-lens.md | 0 .../__snapshots__/elicitor--pushed-context.md | 0 .../runtime}/__tests__/compose.test.ts | 16 ++++----- .../runtime/__tests__}/state.test.ts | 14 ++++---- .../runtime}/compose.ts | 12 +++---- .../runtime}/prompt-skills.ts | 6 +--- .../agent-runtime => agents}/runtime/state.ts | 14 ++++---- src/agents/skills/README.md | 10 +++--- src/projections/README.md | 2 +- .../__tests__/readiness-estimate.test.ts | 2 +- src/session/README.md | 2 +- src/session/agent-context-seed.ts | 2 +- 26 files changed, 106 insertions(+), 77 deletions(-) create mode 100644 src/agents/runtime/README.md rename src/{.pi/extensions/agent-runtime/system-prompts => agents/runtime}/__snapshots__/elicitor--auto-floor-gaps-open.md (100%) rename src/{.pi/extensions/agent-runtime/system-prompts => agents/runtime}/__snapshots__/elicitor--auto-high-coverage.md (100%) rename src/{.pi/extensions/agent-runtime/system-prompts => agents/runtime}/__snapshots__/elicitor--pinned-strategy-lens.md (100%) rename src/{.pi/extensions/agent-runtime/system-prompts => agents/runtime}/__snapshots__/elicitor--pushed-context.md (100%) rename src/{.pi/extensions/agent-runtime/system-prompts => agents/runtime}/__tests__/compose.test.ts (97%) rename src/{.pi/extensions/agent-runtime/runtime => agents/runtime/__tests__}/state.test.ts (95%) rename src/{.pi/extensions/agent-runtime/system-prompts => agents/runtime}/compose.ts (91%) rename src/{.pi/extensions/agent-runtime/system-prompts => agents/runtime}/prompt-skills.ts (96%) rename src/{.pi/extensions/agent-runtime => agents}/runtime/state.ts (92%) diff --git a/memory/PLAN.md b/memory/PLAN.md index 342c30d65..23f352c44 100644 --- a/memory/PLAN.md +++ b/memory/PLAN.md @@ -34,7 +34,7 @@ Brunch-next has delivered the original composition spine: the host, sealed Pi pr - **Goals:** (1) populate the skills the elicitor needs; (2) weed dead-code / stub skills; (3) isolate + lock graph schema, descriptions, tips, and heuristics as context. - **Members:** FE-893, FE-861, FE-898, FE-1052 (all done). -- **Done-definition:** legal skill set sealed by the `.pi/extensions/agent-runtime/runtime/state.ts` path list; no dead stubs (the `__fixtures__` sealing fixture excepted); heuristics distilled + locked into `SKILL.md` bodies, not duplicated in topology READMEs. ✓ — final `strategies/` + `lenses/` README reconciliation discharged 2026-06-25 (dead `INTENT_GRAPH_SEMANTICS.md` pointer + stale "M5 input" tables removed). +- **Done-definition:** legal skill set sealed by the `agents/runtime/state.ts` path list; no dead stubs (the `__fixtures__` sealing fixture excepted); heuristics distilled + locked into `SKILL.md` bodies, not duplicated in topology READMEs. ✓ — final `strategies/` + `lenses/` README reconciliation discharged 2026-06-25 (dead `INTENT_GRAPH_SEMANTICS.md` pointer + stale "M5 input" tables removed). - **Anchors:** D85-L (axis populate / weed), D97-L (heuristic-provenance lock), A35-L (axes frozen under the capability spine). ### elicitor-capability-spine — ◐ active diff --git a/memory/REFACTOR.md b/memory/REFACTOR.md index bbc75a6ea..bcefe1340 100644 --- a/memory/REFACTOR.md +++ b/memory/REFACTOR.md @@ -66,7 +66,7 @@ callers after refactor 1. ✓ Add the new `src/agents` topology and README as an empty central owner, and introduce central path/registry helpers that still point at the existing prompt and skill homes. 2. ✓ Move agent prompt bodies into the new prompt home and update foreground/body loading, background subagent loading, build asset copying, and prompt-body tests without changing prompt bytes. 3. ✓ Move Brunch prompt-resource skills into the new skills home and update manifest loading, build asset copying, resource-location snapshots, and skill topology docs without changing skill bytes. -4. Move foreground prompt composition and prompt-resource legality code into the new runtime home; leave Pi extension code as a thin hook adapter that imports the central composer. +4. ✓ Move foreground prompt composition and prompt-resource legality code into the new runtime home; leave Pi extension code as a thin hook adapter that imports the central composer. 5. Move per-turn pushed context composition and origination/session-entry seed composition into the new context seed home; update session and app callers to import from the central agent context layer. 6. Move reusable LLM-facing context renderers into the new context home, keeping product-only/human-only renderers outside unless they are deliberately agent-visible. 7. Promote adapter-local LLM text formatting into the new context home: graph mutation result text, related-node text, elicitation agenda/update text, and reconciliation agenda/update text. diff --git a/memory/SPEC.md b/memory/SPEC.md index d50c314e0..7fdf5fd10 100644 --- a/memory/SPEC.md +++ b/memory/SPEC.md @@ -133,8 +133,8 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c - **D40-L — Runtime state is transcript-backed Brunch session-agent state, not hidden extension memory.** The architectural commitment is that Brunch session-agent posture remains transcript-backed Pi JSONL state rather than hidden extension memory: posture switches are user/system authority, the foreground session agent is derived from `op_mode`, child axes default back to `AUTO` when invalidated, and tool authority remains `op_mode`-gated rather than prompt-composition-owned. Runtime-state entries are Pi JSONL state-change facts, not assistant/user chat content: init and switch entries should render, when visible, as dim non-chat state rows analogous to Pi thinking/model-change rows, and must not enter LLM context as ordinary conversation. Current materialized state lives in [`src/session/README.md`](src/session/README.md), [`src/projections/README.md`](src/projections/README.md), [`src/.pi/README.md`](src/.pi/README.md), [`src/agents/README.md`](src/agents/README.md), [`src/agents/prompts/README.md`](src/agents/prompts/README.md), and [`src/projections/session/runtime-policy.ts`](src/projections/session/runtime-policy.ts). Depends on: D17-L, D23-L, D25-L, D39-L, D58-L, D59-L. Refined by: D85-L (the AUTO-able objective axes reduce to `strategy` + `lens`; `goal` is dropped from the runtime-state axis set and inlined into the agent role prompt). Supersedes: mode-only vocabulary, extension-local mutable state as authority, storing the foreground role as independent session state, the "runtime bundle / role preset" as one knob deriving model/thinking/resources, and binding prompt-resource location to `src/.pi/context/`. - **D34-L — Command containment separates visibility suppression from effect blocking.** Current Pi extension seams can hide unsupported slash suggestions with autocomplete wrapping and can cancel branch/session effects through lifecycle hooks, but they cannot strictly suppress exact interactive built-in commands before `InteractiveMode` dispatches them. Brunch-owned commands must use product-specific names and route writes through Brunch handlers/`CommandExecutor`; extension command collisions are not an override mechanism. Strict built-in command/keybinding policy is a Pi upstream/API ask, while POC safety relies on hiding generic affordances, blocking dangerous effects (`/fork`, `/clone`, raw session replacement), allowing native `/tree` as inspection/navigation, and failing fast on branched transcripts. Brunch's command-policy code should live in `src/.pi/extensions/commands/policy.ts`, merging branch/session-effect blocking with any product command allow/deny behavior instead of preserving a branch-only module. Depends on: D2-L, D24-L, A18-L. Supersedes: treating extension `input` handlers or command-name collisions as built-in command allowlisting. - **D35-L — Dynamic TUI chrome is a Brunch projection wrapper over Pi UI primitives.** The architectural commitment is that downstream TUI affordances call one Brunch-owned renderer (`renderBrunchChrome` or its successor) with a single activated product-state value rather than scattering raw `ctx.ui.setHeader` / `setFooter` / `setWidget` / title / working-indicator calls; the wrapper is stateless projection over canonical workspace/session/graph facts, never its own mutable state. Chrome is a project-first shell surface with selected-spec context — project name labels the cwd container, spec title labels the selected graph, session label distinguishes transcript instances — and a session label must never replace spec identity or graph truth. Chrome must not consume the status-key namespace for its own summary (`ctx.ui.setStatus` stays a lateral channel for other extensions), must not advertise unwired affordances, and RPC clients must rely only on surfaces Pi actually emits (header/footer/working-indicator are TUI-only in current Pi RPC mode). Current chrome state shape, render surfaces, telemetry/refresh, startup-header behavior, and status-key filtering live in [`src/.pi/extensions/chrome/README.md`](src/.pi/extensions/chrome/README.md); launch/activation wiring lives in [`src/.pi/extensions/README.md`](src/.pi/extensions/README.md). Depends on: D2-L, D21-L, D34-L, A18-L. Supersedes: treating Pi UI methods as direct downstream affordance APIs, rendering placeholder session state such as `unbound` after a session is activated, consuming the status-key namespace for chrome's own static summary, using spec title as the default session label, or allowing two unchanged Brunch-created default names to collide in one cwd, and the earlier resume/open-launches-stay-quiet clause (superseded 2026-06-11: the shipped, test-locked behavior headers every non-cancel activation). -- **D52-L — Source topology targets `src/{app, workspace, scripts, agents, .pi, db, graph, session, projections, renderers, rpc, web}` with directed layer dependencies.** Reusable projection and rendering modules live in top-level `src/projections/` and `src/renderers/` rather than whichever domain or adapter first needed them; `src/agents/` is the Pi-independent owner for Brunch-authored LLM context ingress (currently bundled agent prompt bodies, prompt-resource skills, and the central registry for their file paths, with later runtime/context moves tracked by the LLM-context refactor; the `projections/session/runtime-policy.ts` registry import is a temporary migration edge until runtime policy moves under `agents/runtime/`); domain layers (`graph/`, `session/`) and the reusable `projections` / `renderers` / `agents` layers must not import adapters, transports, app entrypoints, or web code; `graph/` is the only layer that imports `db/`, plus the single sanctioned `db/`→`graph/schema/kinds.ts` taxonomy edge (D73-L). The concrete per-directory ownership, layout sketch, and full import matrix are owned by [`src/README.md`](src/README.md). Depends on: D2-L, D4-L, D39-L, D40-L. Refined by: D73-L. Supersedes: scattering session domain files at `src/` root; treating Pi-only agents as a host-independent top-level `src/.pi/` layer; nesting prompt composition under `src/.pi/context/`; treating reusable `project` / `format` helpers as owned by whichever adapter first needed them; treating retired `.pi/agents` / `.pi/skills` locations as the long-term conceptual owner for Brunch-authored model-facing content. -- **D73-L — Domain enum taxonomy is owned by drizzle-free schema leaves; persistence and adapters are consumers, not the source.** The closed enum `const` arrays that define graph vocabulary — node kinds (`INTENT_KINDS`, `ORACLE_KINDS`, `DESIGN_KINDS`, `PLAN_KINDS`), `NODE_PLANES` (`intent`/`oracle`/`design`/`plan`), `NODE_BASES`, `EDGE_CATEGORIES`, `EDGE_STANCES`, `READINESS_BANDS`, `LENS_AFFINITIES`, `GAP_DISPOSITIONS`, and `GAP_PREDICATE_KINDS` — live in `graph/schema/kinds.ts`, a pure constants leaf that imports nothing (no drizzle, no `graph/atoms`). Both `db/schema.ts` (for `text({ enum })` column constraints, including the previously-inlined `plane` columns) and `graph/` domain modules import the arrays from this leaf; `graph/index.ts` re-exports them from the leaf so non-graph layers still avoid importing `db/` directly (I26-L). Session runtime axis vocabulary mirrors the same ownership direction in `session/schema/kinds.ts`: that leaf imports nothing and owns the `op_mode`, agent-role, `strategy`, `lens`, `auto`, and display-only planned mode choices consumed by `session/runtime-state.ts`, `projections/session/*`, and `.pi/extensions/agent-runtime/runtime/state.ts`; it deliberately contains no `goal` axis and no retired `READINESS_GRADES`. Derivations stay where they are read: `NODE_KIND_METADATA`, `formatGraphNodeCode`, `parseGraphNodeCode`, and `intentKindCategory` remain in `graph/schema/nodes.ts` (D62-L). The motivating defect: because `db/schema.ts` eagerly evaluates `sqliteTable(...)` and `verbatimModuleSyntax` emits even type-only imports at runtime, any value-import path from `web/` into the old taxonomy location pulled Drizzle into the browser bundle. Locating taxonomy in a drizzle-free leaf makes the `web/` build target structurally Drizzle-free (I44-L) and corrects the ownership direction so the domain, not the persistence layer, owns its vocabulary. Vocabulary migration status: `READINESS_GRADES` is retired (readiness is no longer a stored grade, D45-L), `ELICITATION_BACKLOG_STATUSES` is replaced by the `elicitation_gaps` disposition + predicate-shape enums (D65-L), and `READINESS_BANDS` stays. Depends on: D16-L, D52-L, D54-L, D62-L, D63-L, D64-L; I26-L. Supersedes: `db/schema.ts` owning the shared enum `const` arrays and the "enum literals flow outward from `db/schema.ts`" posture; the triplicated inline `['intent','oracle','design','plan']` plane literals. +- **D52-L — Source topology targets `src/{app, workspace, scripts, agents, .pi, db, graph, session, projections, renderers, rpc, web}` with directed layer dependencies.** Reusable projection and rendering modules live in top-level `src/projections/` and `src/renderers/` rather than whichever domain or adapter first needed them; `src/agents/` is the Pi-independent owner for Brunch-authored LLM context ingress (currently bundled agent prompt bodies, prompt-resource skills, prompt composition, prompt-resource/tool legality, and the central registry for their file paths, with later context-rendering moves tracked by the LLM-context refactor; the `projections/session/runtime-policy.ts` registry import is a temporary migration edge until the foreground roster moves under `agents/runtime/`); domain layers (`graph/`, `session/`) and the reusable `projections` / `renderers` / `agents` layers must not import adapters, transports, app entrypoints, or web code; `graph/` is the only layer that imports `db/`, plus the single sanctioned `db/`→`graph/schema/kinds.ts` taxonomy edge (D73-L). The concrete per-directory ownership, layout sketch, and full import matrix are owned by [`src/README.md`](src/README.md). Depends on: D2-L, D4-L, D39-L, D40-L. Refined by: D73-L. Supersedes: scattering session domain files at `src/` root; treating Pi-only agents as a host-independent top-level `src/.pi/` layer; nesting prompt composition under `src/.pi/context/`; treating reusable `project` / `format` helpers as owned by whichever adapter first needed them; treating retired `.pi/agents` / `.pi/skills` locations as the long-term conceptual owner for Brunch-authored model-facing content. +- **D73-L — Domain enum taxonomy is owned by drizzle-free schema leaves; persistence and adapters are consumers, not the source.** The closed enum `const` arrays that define graph vocabulary — node kinds (`INTENT_KINDS`, `ORACLE_KINDS`, `DESIGN_KINDS`, `PLAN_KINDS`), `NODE_PLANES` (`intent`/`oracle`/`design`/`plan`), `NODE_BASES`, `EDGE_CATEGORIES`, `EDGE_STANCES`, `READINESS_BANDS`, `LENS_AFFINITIES`, `GAP_DISPOSITIONS`, and `GAP_PREDICATE_KINDS` — live in `graph/schema/kinds.ts`, a pure constants leaf that imports nothing (no drizzle, no `graph/atoms`). Both `db/schema.ts` (for `text({ enum })` column constraints, including the previously-inlined `plane` columns) and `graph/` domain modules import the arrays from this leaf; `graph/index.ts` re-exports them from the leaf so non-graph layers still avoid importing `db/` directly (I26-L). Session runtime axis vocabulary mirrors the same ownership direction in `session/schema/kinds.ts`: that leaf imports nothing and owns the `op_mode`, agent-role, `strategy`, `lens`, `auto`, and display-only planned mode choices consumed by `session/runtime-state.ts`, `projections/session/*`, and `agents/runtime/state.ts`; it deliberately contains no `goal` axis and no retired `READINESS_GRADES`. Derivations stay where they are read: `NODE_KIND_METADATA`, `formatGraphNodeCode`, `parseGraphNodeCode`, and `intentKindCategory` remain in `graph/schema/nodes.ts` (D62-L). The motivating defect: because `db/schema.ts` eagerly evaluates `sqliteTable(...)` and `verbatimModuleSyntax` emits even type-only imports at runtime, any value-import path from `web/` into the old taxonomy location pulled Drizzle into the browser bundle. Locating taxonomy in a drizzle-free leaf makes the `web/` build target structurally Drizzle-free (I44-L) and corrects the ownership direction so the domain, not the persistence layer, owns its vocabulary. Vocabulary migration status: `READINESS_GRADES` is retired (readiness is no longer a stored grade, D45-L), `ELICITATION_BACKLOG_STATUSES` is replaced by the `elicitation_gaps` disposition + predicate-shape enums (D65-L), and `READINESS_BANDS` stays. Depends on: D16-L, D52-L, D54-L, D62-L, D63-L, D64-L; I26-L. Supersedes: `db/schema.ts` owning the shared enum `const` arrays and the "enum literals flow outward from `db/schema.ts`" posture; the triplicated inline `['intent','oracle','design','plan']` plane literals. #### Data model & vocabulary @@ -152,9 +152,9 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c - **D63-L — Graph `basis` records item-level approval strength, not the mutation pathway.** Accepted nodes and edges use `basis ∈ explicit | implicit`. `explicit` means the user directly stated the graph item or approved the exact node/edge in a review set; `implicit` means the user accepted a concept/proposal and the agent materialized specific graph items to match it without per-item review (the `propose-graph` direct-commit path). The mutation pathway lives in `change_log.operation` and payload (`mutate_graph`, `accept_review_set`, post-exchange capture, etc.), while epistemic attribution lives in `Node.source` and proposal UI metadata may still carry `epistemic_status`. Low-confidence inferred material is still not graph truth; it remains in preface/capture analysis/review drafts/reconciliation needs until clarified or accepted. More abstractly, `basis` is a *provenance-directness* marker — directly from the user (`explicit`) versus agent-materialized from user input (`implicit`) — of which item-level approval strength is the claim-flavored reading; this lets the same `explicit | implicit` distinction apply to non-claim registers such as `elicitation_gaps` (user-raised vs agent-inferred, D65-L). Depends on: D26-L, D27-L, D53-L, D54-L, D55-L. Supersedes: `basis = accepted_review_set` as a persisted graph enum value and any interpretation of `basis` as a provenance/path field. - **D64-L — Readiness bands are the coarse advisory coverage axis; D94-L materializes the current four-band derived model.** Bands are `grounding`, `elicitation`, `projection`, and `commitment`; they are non-exclusive node-kind groupings derived by `bandsForKind(kind)` in `src/graph/schema/nodes.ts`, not stored per-kind metadata and not structural legality gates. The derivation is `design` + `oracle` → `projection`, `plan` → `commitment`, and intent-plane kinds via a hand-maintained bisection: `goal`/`thesis` → `grounding`, `story`/`unknown`/`assumption`/`invariant`/`decision` → `elicitation`, `requirement`/`criterion` → `commitment`, `context` + `constraint` → dual `grounding` + `elicitation`, with `example`/`sketch`/`term` explicitly band-less. Two carriers must stay separate (I50-L): the asking agenda and soft readiness estimate read `gap.band`, while graph filters/rendering/thresholds read derived node band membership. Bands guide what the elicitor is trying to complete, what graph filters and rendered context show, the per-band **readiness estimate** rollup (D45-L), and which gaps a capability-readiness judgment weighs (D74-L). The `CommandExecutor` must not reject a clear later-band kind merely because of band; readiness controls objectives and capability judgment, not what graph truth may contain (I31-L). Depends on: D45-L, D56-L, D57-L, D59-L, D60-L, D65-L. Refined by: D94-L (four bands with two super-types; node band membership is derived from `plane` rather than declared per-kind; the asking-agenda reader reads `gap.band`, not the node-kind table). Supersedes: treating the intent `basic | structural | reasoning` category as the readiness taxonomy, treating readiness as a per-kind creation whitelist, treating bands as a grade rubric for a stored grade, or treating the earlier design doc's duplicated readiness table as the source of truth. - **D65-L — `elicitation_gaps` are typed coverage *obligations* (typologies) — the elicitor's prospective-memory agenda and the substrate of capability-readiness judgment; they guide and modulate, they never hard-gate.** Renamed and reconceived from `elicitation_backlog`. A gap is an obligation register entry, not domain content: the anti-shadowing line remains that the table holds obligation / disposition / meta only, never graph truth. Importance remains pre-answer weight and coverage remains post-answer derived strength; they must not collapse into one ambiguous field. `basis` still follows provenance-directness (D63-L), and `not_applicable` / `irrelevant` / `reopened` remain legitimate disposition semantics. The process-vs-domain split also remains: these are elicitation-process gaps, not domain-gap graph nodes, and an open grounding gap must never wall the candidate-proposal / disambiguation UX that fills it. Current materialized register shape, ownership, seeding, and predicate/disposition mechanics live in [`src/graph/README.md`](src/graph/README.md) and [`src/graph/schema/elicitation-gaps.ts`](src/graph/schema/elicitation-gaps.ts). Still open: whether the register eventually thins the `goal` axis (D59-L); capture-reflection writeback is now *designed* (D81-L: low-confidence noticings spawn gaps; the sweep closes answered gaps) with implementation pending in FE-861. Depends on: D8-L, D30-L, D45-L, D57-L, D59-L, D60-L, D63-L, D64-L, D74-L. Refined by: D75-L (gaps reference graph node kinds via `refersTo: NodeKind`; the parallel grounding-typology catalog and the closed gap-`name` enum are retired — substrate, predicate union, disposition, and anti-shadowing line are unchanged); D81-L (noticings-spawn-gaps is the committed capture-reflection writeback); D82-L (seeding gains the situating gap — orientation anchors routing acquisition modes). Supersedes: the `elicitation_backlog` name and its question-instance / `open | closed`-status model, treating `unknown` as a graph node kind, and any readiness-grade-projection-over-open-counts as authority. -- **D74-L — Capability-readiness is a just-in-time, capability-relative judgment over relevant gaps — it replaces the standing grade gate.** When a capability is requested (a generative lens, `propose-graph`, `project-graph`, commitment review, eventual export), the agent evaluates readiness *for that capability* against the `elicitation_gaps` (D65-L) declared relevant to it. The `capability → relevant gaps` map is **explicit** and subsumes the retired `STRATEGY_MIN_GRADE` / `GOAL_MIN_GRADE` / `LENS_MIN_GRADE` thresholds in `runtime-policy.ts` plus the retired prompt-manifest/tool `METHOD_MIN_GRADE` thresholds in `.pi/extensions/agent-runtime/runtime/state.ts`, which were lossy grade-proxies for "enough grounding". Structurally-obvious relevant gaps (`presence` / `field` / `coverage`) are checked **mechanically** (cheap, no LLM); non-obvious (`manual`) ones consume an **LLM satisficiency judgment** (D57-L). The outcome is one of **proceed**, **proceed at low epistemic status** (density-scaled, D30-L), or **negotiate** — surface an `establishment_offer` ("I can, but answer X and Y first", D32-L). Explicit user/system pins stay legible when they are role/mode-legal; readiness negotiation narrows AUTO choices and gated methods/tools rather than erasing the pin or crashing prompt composition. Capability-readiness fires **on request, reactive-primary** (proactive nudges are a separate later concern) and is the **only readiness gate**: it never bars attempting work, it scales/negotiates. This resolves the prior "lens is never gated" (`ELICITATION_LENSES.md`) vs `LENS_MIN_GRADE` contradiction (lenses are not grade-gated; readiness is JIT-judged) and dissolves the grade-ratchet / two-value problem (the soft `readiness estimate`, D45-L, gates nothing and may regress honestly). A future structural milestone gate for export/plan/execute op-modes is deferred (D45-L) until such an op-mode exists. Depends on: D25-L, D26-L, D30-L, D32-L, D45-L, D57-L, D59-L, D65-L. Refined by: D75-L (the `capability → relevant gaps` map references node kinds, not a closed typology-name enum); D86-L (the "narrows … gated methods/tools" clause no longer applies to graph-write tools — `mutate_graph` and review-set tools are floor; readiness is advisory for them). Supersedes: `GRADE_RANK`-based `MIN_GRADE` hard gating of goal/strategy/lens/method prompt resources and method-coupled tools, and a standing readiness scalar as the authority for capability availability. +- **D74-L — Capability-readiness is a just-in-time, capability-relative judgment over relevant gaps — it replaces the standing grade gate.** When a capability is requested (a generative lens, `propose-graph`, `project-graph`, commitment review, eventual export), the agent evaluates readiness *for that capability* against the `elicitation_gaps` (D65-L) declared relevant to it. The `capability → relevant gaps` map is **explicit** and subsumes the retired `STRATEGY_MIN_GRADE` / `GOAL_MIN_GRADE` / `LENS_MIN_GRADE` thresholds in `runtime-policy.ts` plus the retired prompt-manifest/tool `METHOD_MIN_GRADE` thresholds in `agents/runtime/state.ts`, which were lossy grade-proxies for "enough grounding". Structurally-obvious relevant gaps (`presence` / `field` / `coverage`) are checked **mechanically** (cheap, no LLM); non-obvious (`manual`) ones consume an **LLM satisficiency judgment** (D57-L). The outcome is one of **proceed**, **proceed at low epistemic status** (density-scaled, D30-L), or **negotiate** — surface an `establishment_offer` ("I can, but answer X and Y first", D32-L). Explicit user/system pins stay legible when they are role/mode-legal; readiness negotiation narrows AUTO choices and gated methods/tools rather than erasing the pin or crashing prompt composition. Capability-readiness fires **on request, reactive-primary** (proactive nudges are a separate later concern) and is the **only readiness gate**: it never bars attempting work, it scales/negotiates. This resolves the prior "lens is never gated" (`ELICITATION_LENSES.md`) vs `LENS_MIN_GRADE` contradiction (lenses are not grade-gated; readiness is JIT-judged) and dissolves the grade-ratchet / two-value problem (the soft `readiness estimate`, D45-L, gates nothing and may regress honestly). A future structural milestone gate for export/plan/execute op-modes is deferred (D45-L) until such an op-mode exists. Depends on: D25-L, D26-L, D30-L, D32-L, D45-L, D57-L, D59-L, D65-L. Refined by: D75-L (the `capability → relevant gaps` map references node kinds, not a closed typology-name enum); D86-L (the "narrows … gated methods/tools" clause no longer applies to graph-write tools — `mutate_graph` and review-set tools are floor; readiness is advisory for them). Supersedes: `GRADE_RANK`-based `MIN_GRADE` hard gating of goal/strategy/lens/method prompt resources and method-coupled tools, and a standing readiness scalar as the authority for capability availability. - **D75-L — `elicitation_gaps` reference graph node kinds; the parallel grounding-typology vocabulary is retired.** The commitment is architectural: Brunch has one closed ontology here (`NodeKind`), not a second closed grounding-typology vocabulary; gap naming must stay on the kind layer, while question phrasing remains open and situated. This retires the denormalized grounding catalog and the closed gap-name vocabulary, preserves the anti-shadowing line from D65-L, and keeps example question phrasing as priming rather than schema. Current node-kind-keyed gap shape, grounding-floor seeding, capability-readiness mapping, and priming catalogs live in [`src/graph/README.md`](src/graph/README.md), [`src/graph/schema/elicitation-gaps.ts`](src/graph/schema/elicitation-gaps.ts), [`src/projections/README.md`](src/projections/README.md), [`src/db/README.md`](src/db/README.md), and [`docs/design/ELICITATION_QUESTIONS.md`](docs/design/ELICITATION_QUESTIONS.md). Depends on: D54-L, D56-L, D57-L, D64-L, D65-L, D73-L, D74-L; A27-L (and validated A24-L). Refines: D30-L, D65-L, D74-L. Supersedes: the grounding typology catalog as a parallel closed gap vocabulary; the closed gap-`name` typology enum and the `RelevantGapName` union; and the retired refactor plan to enshrine `GROUNDING_GAP_TYPOLOGIES` as a canonical const. -- **D86-L — Capability-readiness never withholds a graph-write tool; `negotiate` is advisory, not a tool gate.** Readiness modulates: it scales epistemic status (D30-L), narrows AUTO *axis* menus (`strategy`/`lens`), and surfaces the `establishment_offer` — but it must never remove a graph-write tool from the active set. `mutate_graph` (direct commit) and the review-set tools (`present_review_set` / `request_response`) are **floor** capabilities in `elicit` mode whenever gaps exist; the agent always retains the means to commit graph truth and may proceed at low epistemic status when grounding is thin. This re-asserts I31-L ("readiness never bars graph truth or work") against the contrary reading. Motivating reductio: gating `mutate_graph` behind `propose-graph` readiness created a **bootstrap deadlock** — a fresh or foundation-light spec can never establish its `context`/`thesis`/`goal`/`constraint` frame, because the only tool that can write those nodes was gated on those nodes already existing (a developed but foundation-light spec such as `beta-commitments` was likewise unwritable). The `establishment_offer` is the correct *soft* mechanism; hard tool-withholding was over-anticipation (the same over-gating smell as a method withholding its own answer surface). Structural legality at the `CommandExecutor` (D64-L) is unchanged — illegal writes still fail loud; only the readiness-based *tool* withholding is removed. Implementation decouples the graph-write methods (`commit-graph`, `generate-proposal`) from the `propose-graph` / `project-graph` capability gate; current tool-legality and capability-readiness policy live in [`src/projections/README.md`](src/projections/README.md) (`session/capability-readiness`, `session/runtime-policy`) and [`src/.pi/extensions/agent-runtime/runtime/state.ts`](src/.pi/extensions/agent-runtime/runtime/state.ts) (`activeToolNamesForPosture`, `METHOD_CAPABILITY`, `METHOD_TOOL_NAMES`). Depends on: D30-L, D32-L, D74-L, D81-L, D85-L; I31-L. Refines: D74-L, D85-L. Supersedes: D85-L move 2's "the graph-write readiness gate lives on those method ids via capability-readiness"; the D74-L clause "readiness negotiation narrows … gated methods/tools" insofar as it withholds *graph-write* tools (AUTO axis-menu narrowing for `strategy`/`lens` is unaffected). +- **D86-L — Capability-readiness never withholds a graph-write tool; `negotiate` is advisory, not a tool gate.** Readiness modulates: it scales epistemic status (D30-L), narrows AUTO *axis* menus (`strategy`/`lens`), and surfaces the `establishment_offer` — but it must never remove a graph-write tool from the active set. `mutate_graph` (direct commit) and the review-set tools (`present_review_set` / `request_response`) are **floor** capabilities in `elicit` mode whenever gaps exist; the agent always retains the means to commit graph truth and may proceed at low epistemic status when grounding is thin. This re-asserts I31-L ("readiness never bars graph truth or work") against the contrary reading. Motivating reductio: gating `mutate_graph` behind `propose-graph` readiness created a **bootstrap deadlock** — a fresh or foundation-light spec can never establish its `context`/`thesis`/`goal`/`constraint` frame, because the only tool that can write those nodes was gated on those nodes already existing (a developed but foundation-light spec such as `beta-commitments` was likewise unwritable). The `establishment_offer` is the correct *soft* mechanism; hard tool-withholding was over-anticipation (the same over-gating smell as a method withholding its own answer surface). Structural legality at the `CommandExecutor` (D64-L) is unchanged — illegal writes still fail loud; only the readiness-based *tool* withholding is removed. Implementation decouples the graph-write methods (`commit-graph`, `generate-proposal`) from the `propose-graph` / `project-graph` capability gate; current tool-legality and capability-readiness policy live in [`src/projections/README.md`](src/projections/README.md) (`session/capability-readiness`, `session/runtime-policy`) and [`src/agents/runtime/state.ts`](src/agents/runtime/state.ts) (`activeToolNamesForPosture`, `METHOD_CAPABILITY`, `METHOD_TOOL_NAMES`). Depends on: D30-L, D32-L, D74-L, D81-L, D85-L; I31-L. Refines: D74-L, D85-L. Supersedes: D85-L move 2's "the graph-write readiness gate lives on those method ids via capability-readiness"; the D74-L clause "readiness negotiation narrows … gated methods/tools" insofar as it withholds *graph-write* tools (AUTO axis-menu narrowing for `strategy`/`lens` is unaffected). - **D87-L — Multi-method ontology revision: methods are validation lenses, not sources of kinds; the locked kind set reopens once for a small batch.** The ontology must host BDD, EDD, and formal-spec/verification flows on one model, cheapest to establish now before change costs rise. The governing result — validated against BDD/Gherkin and formal verification in [`docs/design/ONTOLOGY_REVIEW_PROTOCOL.md`](docs/design/ONTOLOGY_REVIEW_PROTOCOL.md) — is the **closure rule**: a method = `spec.kind` (D89-L) + `detail.form` (D88-L) + a renderer + a heuristic-set; no method earns its own node/edge kind, and a method term with no clean mapping is a *finding about our model*, not a licence to add a kind. This reopens the D54-L/D56-L node lock and the D51-L edge set once, deliberately, for one batch (implemented in the FE-1052 frontier; the schema enums changed during that build and `GRAPH_MODEL.md` was retired): - **Edges 8 → 9** (renames preserve behavior incl. stance): `proof → witness`, `support → rationale`, `boundary → exclusion`, `association → cross_reference`; **add `refinement`** (generality → specificity; present reader is formal refinement, abstract model ⊑ concrete implementation, distinct from `realization`). `stance ∈ for | against` stays valid only on the renamed `witness`/`rationale`; a counterexample is `witness:against`. The *edge* `proof` becomes `witness` while the *node* `evidence` is unchanged (renaming the edge to `evidence` would collide with the node; the relation reads as a verb). @@ -274,10 +274,10 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c - **D90-L — Foreground and background agents share one manifest model; background discovery is code-owned (frontmatter is authoring DX, not a second agent model).** Agent definitions project into one `AgentManifest` (`id`, `kind`, `description`, `model`, `thinking`, body at the canonical `src/agents/prompts//SYSTEM.md` convention, a skills grant, a tools grant, and a `canDelegate` set naming the background agents it may spawn — D92-L/D93-L) discriminated by `kind: "foreground" | "background"` — the execution **lifecycle/host**, not a noun: a foreground agent is a live op_mode-derived Pi session; a background agent is a spawned-to-completion sealed child. The kinds keep **distinct authority sources**: a foreground agent's identity is derived from `op_mode` (D40-L) and its tool/skill legality is dynamic (op_mode policy + live gaps); a background agent's identity is caller-chosen (`{agent, task}`) and its skills/tools come from its authored manifest. DX-vs-strictness is reconciled by keeping **frontmatter as the authoring surface** for background agents while making **discovery code-owned**: the `readdir` scan over `agents/*.md` is retired for an explicit registry id list (mirroring how `state.ts` loads foreground bodies/skills through `loadSkills({ skillPaths, includeDefaults: false })`), so D39-L "no filesystem discovery" holds and frontmatter authoring survives. "subagent" stays the tool/UX noun (the main-agent tool call), not the kind name. Depends on: D39-L, D40-L, D44-L, D58-L. Refines: D44-L (the parallel frontmatter-discovered format collapses into the shared manifest; background agent bodies migrated from `src/.pi/extensions/subagents/agents/*.md` onto the canonical `src/agents/prompts//SYSTEM.md` convention, so SPEC carries one agent-body layout — D44-L and the `src/.pi/extensions/subagents/README.md` topology notes reconcile to that path). Establishing frontier: `subagent-reconciliation`. Supersedes: `readdir` filesystem discovery of subagent definitions; the standalone subagent frontmatter format as a second, separate agent model. - **D91-L — Background subagents run a semi-permeable seal: explicitly-injected parent world handles plus an assembled (not verbatim) prompt; ambient leakage stays closed.** This deliberately reopens the D44-L/I29-L "no graph access, no Brunch RPC, no inherited context" clause. The seal stays closed against **ambient** leakage (in-memory auth/settings/session, no `~/.pi` discovery — D39-L intact) but opens to **explicitly injected** parent world handles the app root (`src/app/pi-subagents.ts`) supplies at spawn: the same `GraphReaders` the foreground uses scoped to the parent's `specId`, the spec/workspace context seed, and a bounded **session digest** (the parent branch flattened via `sessionManager.getBranch()`, the pattern in pi's `summarize.ts` example). The child's system prompt becomes **assembled, not verbatim**: body + a background control header (sealed child, delegated task, snapshot view) + world snapshot + a `` manifest built from the manifest's skills grant + router rules — reusing the foreground composer's extracted prompt-skill core (`renderBrunchSkills`, the skill-manifest loader) plus the selected workspace/spec seed renderer from `src/session/agent-context-seed.ts`, minus the foreground-only elicitation-recommendation block. World binding is **snapshot-at-spawn** (the child runs to completion against a fixed view) where the foreground is live-per-turn. Read access is asymmetric **by design**: the **session digest** is a snapshot block baked into the prompt (expensive, rarely re-pulled), while the **graph** is exposed as Brunch read tools (`read_graph` now; `read_session_context`, `read_elicitation_gaps`, … remain future grants) the child calls on demand (a recon agent iterates on graph). Return to the main agent is the ordinary tool-call result: findings re-enter main-agent context as the tool-result `content`; the structured `details` payload (`{ agent, status, text, … }`) is render-only via custom `renderCall`/`renderResult`, never model context. Write-capable children stay deferred (gated by D92-L); when they land, a `mutate_graph` against the parent's `specId` is a real side effect crossing back *outside* the tool result, and is named here so the write slice does not surprise. Depends on: D39-L, D43-L, D44-L, D58-L, D60-L, D82-L. Establishing frontier: `subagent-reconciliation`. Supersedes: the D44-L/I29-L clause that subagents have no graph access, no Brunch RPC/graph reads, no inherited world context, and a verbatim-body system prompt. - **D92-L — Background tool grants are sovereign per-agent ceilings gated by a code-owned, op_mode-keyed delegatable-set allowlist — not parent-subset containment.** The earlier containment invariant (child tools ⊆ the parent's current legal set) is rejected: delegation is **capability-inverting on purpose** — a low-privilege orchestrator (the `execute`-mode foreground agent holding only data-layer tools) may spawn a narrow high-privilege child (e.g. a file-writing worker) so writes are quarantined in a child that does one job and exits. Each background agent's tool grant is therefore **sovereign** (authored in its manifest; may exceed the parent's). The surviving safety boundary is not a tool subset but **which background agents an op_mode may spawn**: a **code-owned, op_mode-keyed delegatable-set allowlist** living beside `AGENT_PROMPT_DEFINITIONS` / the op_mode policy, *not* authored in frontmatter (otherwise a manifest could self-advertise into a read-only mode). This lifts D40-L's registration ≠ advertisement from tools to agents: every background agent is registered; op_mode decides which are advertised as spawnable. A read-only `elicit` session is write-safe because elicit's delegatable set **excludes** write-capable agents, not because children are subset-bounded. Enabling write tools later = author the write-capable worker manifest + add it to the relevant op_mode's delegatable set (an advertisement change), not a re-derivation of parent authority. Depends on: D39-L, D40-L, D44-L. Establishing frontier: `subagent-reconciliation`. Refined by: D93-L (the delegatable-set allowlist becomes a per-agent `AgentManifest` `canDelegate` field; for a foreground mode it is that mode's code-owned delegatable set, and it generalizes to background→background nesting). Supersedes: the parent-subset tool-containment model for subagents; D44-L's "read-only/no-tool allowlist" as the only background tool posture; the framing that write-capable subagents wait on an execute mode raising both parent and child ceilings together. -- **D93-L — Operational mode and foreground agent collapse to one op-mode-keyed source of truth; the foreground roster is `elicit` / `execute` / `code`.** A foreground agent and its operational mode are 1:1 (D40-L: the foreground agent is derived from `op_mode`), so the prior **three-record fragmentation** — id enums in `src/session/schema/kinds.ts`, `OPERATIONAL_MODE_DEFINITIONS` + `AGENT_ROLE_DEFINITIONS` + `TOOL_POLICY_DEFINITIONS` in `src/projections/session/runtime-policy.ts`, and `AGENT_PROMPT_DEFINITIONS` in `src/.pi/extensions/agent-runtime/runtime/state.ts` (which duplicated `model`/`thinking`/`allowedStrategies`/`allowedLenses` across two of them) — collapses to a **single op-mode-keyed record**. An operational mode IS `{ foreground AgentManifest (D90-L), tool policy, canDelegate set }`; background agents live in a sibling `AgentManifest` registry, and the per-agent **`canDelegate`** field (D92-L generalized from op_mode-keyed to a manifest field) links a foreground mode to the background agents it may spawn — **code-owned for foreground modes** so the write-safety boundary (I49-L) holds; it also generalizes to background→background nesting. The foreground roster is three modes: **`elicit` → `elicitor`** (current: read-only + graph-writes-via-tools; delegates to read-only gatherers); **`execute` → `orchestrator`** (live initial standup: direct shell/file writes blocked, no delegated workers yet, and one code-owned `orchestrator_stub` grant proves the non-dev runnable path; future worker delegation quarantines writes in spawned high-privilege workers per D92-L); and **`code` → `pi-coder`** (planned direct-coding assistant with direct `read`/`edit`/`write`/`bash`, **augmented to be Brunch-tooling-aware** — it also sees the graph read/capture tools). `execute` and `code` contrast deliberately: the orchestrator does not write directly, while the pi-coder will write directly (and does not delegate by default). `pi-coder` **instantiates** the augment case D58-L anticipated — its `SYSTEM.md` augments Pi's base coding-agent prompt rather than replacing it; whether other foreground roles should instead suppress/replace the base stays open per D58-L. (`code` is provisional naming for the pi-coder mode; rename is a one-token change. `code` remains in `PLANNED_OPERATIONAL_MODE_IDS` until built; `execute` is live.) Depends on: D23-L, D40-L, D58-L, D90-L, D92-L; I49-L. Establishing frontier: `subagent-reconciliation` (model + collapse land here; `execute` stood up here with the stub grant, while `code` remains declarative build-out — see PLAN). Supersedes: the three-record foreground-agent fragmentation as separate sources of truth; `defaultRole`/`allowedRoles` as a flexible many-roles-per-mode model (it is 1:1); the `executor` name for the execute-mode agent (now `orchestrator`). +- **D93-L — Operational mode and foreground agent collapse to one op-mode-keyed source of truth; the foreground roster is `elicit` / `execute` / `code`.** A foreground agent and its operational mode are 1:1 (D40-L: the foreground agent is derived from `op_mode`), so the prior **three-record fragmentation** — id enums in `src/session/schema/kinds.ts`, `OPERATIONAL_MODE_DEFINITIONS` + `AGENT_ROLE_DEFINITIONS` + `TOOL_POLICY_DEFINITIONS` in `src/projections/session/runtime-policy.ts`, and `AGENT_PROMPT_DEFINITIONS` in `src/agents/runtime/state.ts` (which duplicated `model`/`thinking`/`allowedStrategies`/`allowedLenses` across two of them) — collapses to a **single op-mode-keyed record**. An operational mode IS `{ foreground AgentManifest (D90-L), tool policy, canDelegate set }`; background agents live in a sibling `AgentManifest` registry, and the per-agent **`canDelegate`** field (D92-L generalized from op_mode-keyed to a manifest field) links a foreground mode to the background agents it may spawn — **code-owned for foreground modes** so the write-safety boundary (I49-L) holds; it also generalizes to background→background nesting. The foreground roster is three modes: **`elicit` → `elicitor`** (current: read-only + graph-writes-via-tools; delegates to read-only gatherers); **`execute` → `orchestrator`** (live initial standup: direct shell/file writes blocked, no delegated workers yet, and one code-owned `orchestrator_stub` grant proves the non-dev runnable path; future worker delegation quarantines writes in spawned high-privilege workers per D92-L); and **`code` → `pi-coder`** (planned direct-coding assistant with direct `read`/`edit`/`write`/`bash`, **augmented to be Brunch-tooling-aware** — it also sees the graph read/capture tools). `execute` and `code` contrast deliberately: the orchestrator does not write directly, while the pi-coder will write directly (and does not delegate by default). `pi-coder` **instantiates** the augment case D58-L anticipated — its `SYSTEM.md` augments Pi's base coding-agent prompt rather than replacing it; whether other foreground roles should instead suppress/replace the base stays open per D58-L. (`code` is provisional naming for the pi-coder mode; rename is a one-token change. `code` remains in `PLANNED_OPERATIONAL_MODE_IDS` until built; `execute` is live.) Depends on: D23-L, D40-L, D58-L, D90-L, D92-L; I49-L. Establishing frontier: `subagent-reconciliation` (model + collapse land here; `execute` stood up here with the stub grant, while `code` remains declarative build-out — see PLAN). Supersedes: the three-record foreground-agent fragmentation as separate sources of truth; `defaultRole`/`allowedRoles` as a flexible many-roles-per-mode model (it is 1:1); the `executor` name for the execute-mode agent (now `orchestrator`). - **D36-L — Spec/session selection is a reusable hierarchical decision model with transport-specific presentations.** Brunch owns a pure spec/session selection model that renders cwd-scoped inventory under the discovered project name without calling the user-created object a “workspace”. In TUI mode, the model may present a fast “continue last session” affordance when `.brunch/workspace.json` points to a valid spec+session; otherwise, or after “other spec/session”, the durable tree is: `create new spec → provide spec name → session created automatically`; `resume existing spec → choose existing spec → create a new session OR resume existing session → choose existing session`. The UI should not list every spec as a top-level action label; “resume existing spec” is the top-level intent, and the spec list is the next screen/scrollable selector. The model returns a product decision (`new spec`, `new session for spec`, `open session`, `continue selected session`, `cancel/quit`) without opening Pi sessions or mutating `.brunch/workspace.json` itself. The `WorkspaceSessionCoordinator` activates that decision and owns all persistence/session-binding effects. TUI startup and in-session paths share branded `pi-tui` components and colocated logo assets under `src/.pi/components/workspace-dialog`; adapters differ only in terminal lifecycle and Pi session-replacement mechanics (`ProcessTerminal`/`TUI.showOverlay` before Pi starts, `ctx.ui.custom(..., { overlay: true })` inside Pi), not in product semantics. RPC/headless transports must not invoke the TUI picker; they expose the same initial-selection requirement and activation decisions as JSON-RPC/product results so CLI JSON-RPC clients can select or create spec/session correctly. Depends on: D11-L, D21-L, D24-L, D33-L. Supersedes: implicit resume of `.brunch/workspace.json` on TUI launch, Pi `/resume`/`/new` as Brunch's product session chooser, one-off startup-only picker implementations, a flat action list that says “workspace” for specs, top-level `resume spec X` labels, and a separate intermediate action chooser for switching. - **D42-L — Session naming is Pi `session_info` presentation metadata, not spec identity.** Brunch-created sessions should be named at creation with neutral workspace-global defaults (`Untitled Session 1`, `Untitled Session 2`, …) so pickers/chrome never show an unnamed Brunch session and unchanged defaults do not collide across specs in the same cwd. These defaults are immediate lifecycle metadata, not LLM-generated summaries and not derived from the selected spec title. Brunch may later use Pi session lifecycle hooks to opportunistically replace a default with a short human-readable name that characterizes what happened in the transcript. The preferred generation trigger is `session_shutdown` for `quit`, `new`, and `resume` replacements because it sees the just-finished transcript and can name it before later picker lists need to distinguish sessions; `session_before_compact` or post-compaction (`session_compact`) may be used to refresh names after major summarization, and a manual/user rename command can force or override naming. The generation call should mirror the model-selection pattern in the local `summarize.ts` extension example: choose a cheap/fast authorized model, extract user/assistant text plus salient tool calls from the current branch, ask for a concise title, and append a Pi `session_info` entry through `SessionManager.appendSessionInfo`. Naming must be best-effort and non-blocking with a tight budget: failures, missing auth, empty transcripts, or shutdown aborts preserve the existing default/user label rather than blocking session replacement or exit. Session display names label sessions in pickers and chrome, but do not affect spec ids, session bindings, graph truth, or replay semantics. Depends on: D6-L, D17-L, D21-L, D35-L. Supersedes: using spec title or session UUID alone as the only durable display label once transcripts have meaningful content, leaving Brunch-created sessions unnamed, spec-local default numbering, or treating generated session names as canonical spec identity. -- **D58-L — Brunch prompt composition is a thin runtime header plus a gated prompt-resource manifest, not eager selection of every objective pack.** The architectural commitment is: composition stays a projection layer, not a behavioral state machine; detailed strategy/lens/method guidance lives in read-on-demand prompt resources rather than eager prompt-pack concatenation; runtime-state-gated availability is Brunch's sealed manifest, not ambient Pi discovery; pinned axes remain visible when role/mode-legal while readiness constrains AUTO menus and gated methods/tools; and the pushed-context slice stays compact, with deeper access governed by D60-L. Current prompt-resource topology, manifest emission, file-owned skill metadata, and ownership split across `agents/prompts/`, `agents/skills/`, `.pi/extensions/agent-runtime/system-prompts/`, and `.pi/extensions/agent-runtime/runtime/` live in [`src/agents/README.md`](src/agents/README.md), [`src/agents/prompts/README.md`](src/agents/prompts/README.md), [`src/.pi/README.md`](src/.pi/README.md), [`src/agents/skills/README.md`](src/agents/skills/README.md), [`src/.pi/extensions/README.md`](src/.pi/extensions/README.md), [`src/.pi/extensions/agent-runtime/system-prompts/compose.ts`](src/.pi/extensions/agent-runtime/system-prompts/compose.ts), and [`src/.pi/extensions/agent-runtime/runtime/state.ts`](src/.pi/extensions/agent-runtime/runtime/state.ts). **Base-prompt relationship (validated 2026-06-18, slice 1):** the `before_agent_start` handler **appends** Brunch's composed block (now led by the agent `SYSTEM.md` body, then runtime header + manifests) to Pi's base system prompt (`${basePrompt}\n\n${composed}`), so a foreground agent currently *augments* Pi's base coding-agent prompt rather than replacing it. Whether a foreground role's `SYSTEM.md` body should suppress or replace that base is **open** and tied to the future `pi-coder` op-mode (which deliberately augments Pi's coding agent); the `elicitor` augmenting a coding base is a known follow-on question, not a settled choice. Refined by: D93-L (the `code`→`pi-coder` foreground mode instantiates the augment case; the replace option for other roles stays open). Composition is projection, not a behavioral state machine. Depends on: D23-L, D25-L, D39-L, D40-L, D52-L, D59-L, D60-L. Refined by: D85-L (implemented 2026-06-18/19: the manifest drops `` — two axes `strategy` + `lens` — and the `goal` body inlines into the `elicitor` role prompt) and by the 2026-06-22 prompt-skill-topology slice (all prompt resources adopt Agent Skills `SKILL.md` topology; `description` becomes file-owned frontmatter; the emitted wrapper becomes `` with per-skill ``). Supersedes: the flat "base + mode + role + strategy + lens + grade + …" layering; the fixed all-packs concatenation in `compose-brunch-prompt.ts`; "role preset / runtime bundle" as the composition unit; direct Layer-2 eager prompt-pack injection as the default mechanism; treating top-level `src/agents/` as Pi-only rather than Brunch LLM-context ingress; and `capability` as a parallel name for `method`. +- **D58-L — Brunch prompt composition is a thin runtime header plus a gated prompt-resource manifest, not eager selection of every objective pack.** The architectural commitment is: composition stays a projection layer, not a behavioral state machine; detailed strategy/lens/method guidance lives in read-on-demand prompt resources rather than eager prompt-pack concatenation; runtime-state-gated availability is Brunch's sealed manifest, not ambient Pi discovery; pinned axes remain visible when role/mode-legal while readiness constrains AUTO menus and gated methods/tools; and the pushed-context slice stays compact, with deeper access governed by D60-L. Current prompt-resource topology, manifest emission, file-owned skill metadata, and ownership split across `agents/prompts/`, `agents/skills/`, `agents/runtime/`, and `.pi/extensions/agent-runtime/` live in [`src/agents/README.md`](src/agents/README.md), [`src/agents/prompts/README.md`](src/agents/prompts/README.md), [`src/agents/skills/README.md`](src/agents/skills/README.md), [`src/agents/runtime/README.md`](src/agents/runtime/README.md), [`src/.pi/README.md`](src/.pi/README.md), [`src/.pi/extensions/README.md`](src/.pi/extensions/README.md), [`src/agents/runtime/compose.ts`](src/agents/runtime/compose.ts), and [`src/agents/runtime/state.ts`](src/agents/runtime/state.ts). **Base-prompt relationship (validated 2026-06-18, slice 1):** the `before_agent_start` handler **appends** Brunch's composed block (now led by the agent `SYSTEM.md` body, then runtime header + manifests) to Pi's base system prompt (`${basePrompt}\n\n${composed}`), so a foreground agent currently *augments* Pi's base coding-agent prompt rather than replacing it. Whether a foreground role's `SYSTEM.md` body should suppress or replace that base is **open** and tied to the future `pi-coder` op-mode (which deliberately augments Pi's coding agent); the `elicitor` augmenting a coding base is a known follow-on question, not a settled choice. Refined by: D93-L (the `code`→`pi-coder` foreground mode instantiates the augment case; the replace option for other roles stays open). Composition is projection, not a behavioral state machine. Depends on: D23-L, D25-L, D39-L, D40-L, D52-L, D59-L, D60-L. Refined by: D85-L (implemented 2026-06-18/19: the manifest drops `` — two axes `strategy` + `lens` — and the `goal` body inlines into the `elicitor` role prompt) and by the 2026-06-22 prompt-skill-topology slice (all prompt resources adopt Agent Skills `SKILL.md` topology; `description` becomes file-owned frontmatter; the emitted wrapper becomes `` with per-skill ``). Supersedes: the flat "base + mode + role + strategy + lens + grade + …" layering; the fixed all-packs concatenation in `compose-brunch-prompt.ts`; "role preset / runtime bundle" as the composition unit; direct Layer-2 eager prompt-pack injection as the default mechanism; treating top-level `src/agents/` as Pi-only rather than Brunch LLM-context ingress; and `capability` as a parallel name for `method`. #### Continuity & origination (turn-boundary choreography) @@ -403,8 +403,8 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c ### Prompt/runtime profile architecture -- Brunch prompt composition is a **runtime-header + gated prompt-resource manifest** composed per agent by `composeAgentPrompt(...)` in `.pi/extensions/agent-runtime/system-prompts/compose.ts` (D58-L). The direct injection is intentionally small: agent control summary, two-axis runtime state (`strategy` + `lens`), a legal `` resource manifest with per-skill `kind`, `name`, `description`, and `location`, router rules for pinned/AUTO axes, and compact context handles/rendered context blocks. Detailed strategy/lens/method bodies are Brunch-owned markdown resources the agent loads with `read` when needed, while the elicitor objective postures are inline in `src/agents/prompts/elicitor/SYSTEM.md`. The old `src/.pi/context/` prompt-pack layout is retired; top-level `src/agents/` is now the Brunch-owned LLM-context ingress home, not a Pi-only agent tree. -- Concrete `agents/prompts` + `agents/skills` topology (D52-L). The markdown/code boundary falls on the control-plane/behavior split: enforcement and projection are TypeScript under `.pi/extensions/` (`system-prompts/`, `runtime/`); semantic prompting material is markdown under `agents/prompts/{agent-name}/SYSTEM.md` for live agent bodies and `agents/skills/`. +- Brunch prompt composition is a **runtime-header + gated prompt-resource manifest** composed per agent by `composeAgentPrompt(...)` in `src/agents/runtime/compose.ts` (D58-L). The direct injection is intentionally small: agent control summary, two-axis runtime state (`strategy` + `lens`), a legal `` resource manifest with per-skill `kind`, `name`, `description`, and `location`, router rules for pinned/AUTO axes, and compact context handles/rendered context blocks. Detailed strategy/lens/method bodies are Brunch-owned markdown resources the agent loads with `read` when needed, while the elicitor objective postures are inline in `src/agents/prompts/elicitor/SYSTEM.md`. The old `src/.pi/context/` prompt-pack layout is retired; top-level `src/agents/` is now the Brunch-owned LLM-context ingress home, not a Pi-only agent tree. +- Concrete `agents/prompts` + `agents/skills` + `agents/runtime` topology (D52-L). The markdown/code boundary falls on the control-plane/behavior split: enforcement and projection are TypeScript under `agents/runtime/`; `.pi/extensions/agent-runtime/` is the hook/tool adapter. Semantic prompting material is markdown under `agents/prompts/{agent-name}/SYSTEM.md` for live agent bodies and `agents/skills/`. ```text src/agents/ @@ -420,18 +420,19 @@ src/agents/ methods/*/SKILL.md [md] run-structured-exchange, capture, generate-proposal, read-context, commit-graph, review-for-gaps, acquisition/readback methods + runtime/ + compose.ts [ts] projection -> runtime header + gated manifest (not a state machine) + prompt-skills.ts [ts] SKILL.md manifest loader/renderer + state.ts [ts] legal (op_mode × strategy × lens) tuple table; code-owned + SKILL.md path list + family/kind/legality metadata src/.pi/ extensions/ - system-prompts/ - compose.ts [ts] projection -> runtime header + gated manifest (not a state machine) - seed/*.ts [ts] D60-L pushed seed contexts (renderWorkspaceSeed, renderGraphSeed) - runtime/ - state.ts [ts] legal (op_mode × strategy × lens) tuple table; code-owned - SKILL.md path list + family/kind/legality metadata - context/*.ts [ts] D60-L pull-tool context surface (read_workspace_context, read_session_context) + system-prompts/ [ts] before_agent_start hook adapter + world-read cache + runtime/ [ts] Pi active-tool adapter over agents/runtime policy + context/*.ts [ts] D60-L pull-tool context surface (read_workspace_context, read_session_context) ``` -- Manifest availability is code-owned, not filesystem-discovered: `.pi/extensions/agent-runtime/runtime/state.ts` binds each legal axis value to an explicit `src/agents/skills///SKILL.md` path and each live agent role to its `src/agents/prompts//SYSTEM.md` location. It loads prompt-resource `name` and `description` from `SKILL.md` frontmatter through pi's loader with `includeDefaults: false` and an explicit `skillPaths` list. `composeAgentPrompt()` emits legal resource bindings; the prompt extension reads the selected agent body explicitly and passes it into the pure composer. This keeps the legal set sealed while making the file body/frontmatter the description source of truth. +- Manifest availability is code-owned, not filesystem-discovered: `agents/runtime/state.ts` binds each legal axis value to an explicit `src/agents/skills///SKILL.md` path and each live agent role to its `src/agents/prompts//SYSTEM.md` location. It loads prompt-resource `name` and `description` from `SKILL.md` frontmatter through pi's loader with `includeDefaults: false` and an explicit `skillPaths` list. `composeAgentPrompt()` emits legal resource bindings; the prompt extension reads the selected agent body explicitly and passes it into the pure composer. This keeps the legal set sealed while making the file body/frontmatter the description source of truth. - The D60-L agent-context orchestration layer (TypeScript) lives in `.pi/extensions/agent-runtime/system-prompts/seed/` (pushed contexts) and `.pi/extensions/brunch-data/context/` (read tools), surfaced as the header's compact pushed context or via the read tools; reusable text renderers live in `renderers/`, and contexts are not part of the `read`-on-demand resource manifest and carry no `` family. - Workspace **posture** is workspace-scoped product state persisted in `.brunch/workspace.json`, not spec state, session state, or graph truth. D57-L keeps it off the spec row and graph; D58-L composition injects known posture values into the runtime header as an axis of agent influence, and the `capture-posture` goal (D59-L) can confirm or refine those values conversationally. - Readiness is judged just-in-time per requested capability, not as a user-facing workflow stepper, a stored grade, a session-local phase, or a graph-node-kind whitelist. There is no `readiness_grade` on the spec row (D45-L); capability-readiness (D74-L) is evaluated over the relevant `elicitation_gaps`, and D64-L readiness bands describe non-exclusive evidence groupings feeding the readiness-estimate rollup, goal selection, and context filtering. The soft readiness estimate may surface in UI but gates nothing. A future structural milestone gate for export/plan/execute op-modes is deferred until such an op-mode exists; before readiness grows beyond the current tracer, Brunch still needs a real evaluator path for `manual` gaps and a more differentiated per-capability map than the shared grounding floor (A27-L). @@ -505,7 +506,7 @@ src/.pi/ | **AUTO** | The unpinned state of a runtime prompt-resource axis (`strategy` / `lens`): composition advertises the legal choices in the current prompt-resource manifest and instructs the agent to self-select from that manifest only, reading the selected resource when detail matters (D58-L). | | **Brunch Pi Profile** | The sealed programmatic wrapper around embedded Pi: settings policy, resource-loader policy, extension factories, keybinding/command policy, tool policy, and prompt policy. It allows Brunch-owned resources while suppressing ambient `.pi/` behavior. | | **Prompt resource** | A Brunch-owned markdown file under `src/.pi/` containing detailed strategy, lens, method, or agent-definition guidance. Prompt resources are loaded by the agent with `read` when needed; they are product control-plane assets, not ambient Pi prompt templates. | -| **Prompt-resource manifest** | The small per-turn D58-L `` block injected into the system prompt, listing only runtime-legal Brunch resources with `kind`, `name`, `description`, and `location`. The legal set and locations are code-owned in `.pi/extensions/agent-runtime/runtime/state.ts` (not filesystem-discovered); `name` and `description` are file-owned `SKILL.md` frontmatter read via pi's loader over the explicit path list. The `system-prompts/seed/` and `.pi/extensions/brunch-data/context/` context renderers are not manifest resources. It mirrors Pi's skill-list element structure but is filtered by Brunch runtime state, capability-readiness, and allow-lists. | +| **Prompt-resource manifest** | The small per-turn D58-L `` block injected into the system prompt, listing only runtime-legal Brunch resources with `kind`, `name`, `description`, and `location`. The legal set and locations are code-owned in `agents/runtime/state.ts` (not filesystem-discovered); `name` and `description` are file-owned `SKILL.md` frontmatter read via pi's loader over the explicit path list. The `system-prompts/seed/` and `.pi/extensions/brunch-data/context/` context renderers are not manifest resources. It mirrors Pi's skill-list element structure but is filtered by Brunch runtime state, capability-readiness, and allow-lists. | | **Method** | A tool-usage or workflow competence advertised as a Brunch prompt resource (`agents/skills/methods//SKILL.md`): run structured exchanges, capture (the D80/D81/D82 home; full conduct in FE-861), generate proposals/projections, read context, mutate the graph, review for gaps. Method resources explain when to use a tool family and how to sequence it with other tools; executable tool definitions should stay focused on schemas, authority, and runtime behavior. A method may also be backed by a Pi-native skill, but actual tool authority remains code-owned through `op_mode` policy and active-tool gating. `capability` is retired as a synonym — use `method` and `method` in ``. | | **Agent context** | The content the agent reasons over — `cwd`, `graph`, or `node` (D60-L): pulled (typed, read-only) from `graph/`/`session/`, optionally projected when a reusable DTO helps, rendered to LLM-string or JSON, surfaced pushed (compose) or pulled (`read_graph` / `read_workspace_context` / `read_session_context`). Graph context explicitly chooses graph-truth vs active-context reads and may filter by node kind, readiness band, edge category/direction, or absence of an edge category (gap query). Distinct from the **workspace projection** (`workspace.state`), which is product/UI state, not agent content. | | **Context-render house style** | The RENDER-stage convention (D83-L) for LLM-facing agent context: a markdown frame (md-pen) with uniform record sets as TOON (`@toon-format/toon`) and file hierarchy as a fenced ASCII tree (stringify-tree over Brunch's gitignore-aware walk), each top-level block wrapped in an XML-style `
` tag. Format follows reader legibility, not internal shape (prose where structure misleads). Agent context clusters into three scopes mirroring `workspace → spec → session` (D19-L): `` (project / documents / spec-roster, no sessions), `` (spec header / graph / ranked gaps / sessions), `` (runtime posture / mentions / transcript). It is the agent-context *dialect* within `renderers/`; the md-pen substrate is shared with human-facing renders (print/evidence), which do not use the `
` clustering. Distinct from the `workspace.state` product-state projection (D60-L). | diff --git a/src/.pi/__tests__/prompting.test.ts b/src/.pi/__tests__/prompting.test.ts index fb21dea57..feb110ef3 100644 --- a/src/.pi/__tests__/prompting.test.ts +++ b/src/.pi/__tests__/prompting.test.ts @@ -4,6 +4,7 @@ import { fileURLToPath } from 'node:url'; import { describe, expect, it } from 'vitest'; +import { composeAgentPrompt } from '../../agents/runtime/compose.js'; import { createBrunchPiExtensions } from '../../app/pi-extensions.js'; import { groundingFloorGaps } from '../../graph/schema/elicitation-gap-fixtures.js'; import type { ElicitationGap } from '../../graph/schema/elicitation-gaps.js'; @@ -17,7 +18,6 @@ import { type BrunchAgentStateEntryData, registerBrunchOperationalModePolicy, } from '../extensions/agent-runtime/runtime/index.js'; -import { composeAgentPrompt } from '../extensions/agent-runtime/system-prompts/compose.js'; import { registerBrunchPrompting } from '../extensions/agent-runtime/system-prompts/index.js'; import { BRUNCH_INTROSPECT_QUERY_TOOL } from '../extensions/dev-mode/introspect-query/index.js'; import { createInMemoryBrunchIntrospectionStore } from '../extensions/dev-mode/introspection/index.js'; diff --git a/src/.pi/extensions/README.md b/src/.pi/extensions/README.md index 0a869477d..3c2fa0058 100644 --- a/src/.pi/extensions/README.md +++ b/src/.pi/extensions/README.md @@ -8,7 +8,7 @@ Pi-facing registration and adaptation only: lifecycle hooks, agent tool definiti ## Does NOT own -- Agent role prompt definitions and skill resource bodies (markdown) — `agents/prompts/` and `agents/skills/`. Prompt composition and prompt-resource legality live in `agent-runtime/` until their move slice lands. +- Agent role prompt definitions, skill resource bodies, prompt composition, and prompt-resource legality — `agents/`. `agent-runtime/` is now only the Pi hook/tool adapter for that central policy. - Graph truth, graph mutation policy, or graph readers — top-level `graph/`. - Pi JSONL/session semantics, runtime-state projection, workspace coordination, or transcript exchange projection — top-level `session/`, `projections/`, and related domain seams. - Reusable DTO projection or reusable markdown/text rendering — top-level `projections/` and `renderers/`. @@ -19,9 +19,9 @@ Pi-facing registration and adaptation only: lifecycle hooks, agent tool definiti ```text extensions/ ├── README.md -├── agent-runtime/ foreground prompt composition, active-tool policy, prompt-resource legality, execute-mode stub -│ ├── runtime/ -│ ├── system-prompts/ +├── agent-runtime/ Pi adapter for central agent runtime policy plus execute-mode stub +│ ├── runtime/ operational-mode Pi tool activation adapter +│ ├── system-prompts/ before_agent_start hook adapter │ └── orchestrator-stub/ ├── brunch-data/ Pi tools over selected Brunch graph/spec/workspace/session data │ ├── graph/ mutate_graph/read_graph tools + selected-spec graph read seam diff --git a/src/.pi/extensions/agent-runtime/runtime/index.ts b/src/.pi/extensions/agent-runtime/runtime/index.ts index c0ba1a617..d9b8ee50b 100644 --- a/src/.pi/extensions/agent-runtime/runtime/index.ts +++ b/src/.pi/extensions/agent-runtime/runtime/index.ts @@ -17,15 +17,15 @@ import { } from '@earendil-works/pi-coding-agent'; import { Text } from '@earendil-works/pi-tui'; +import { activeToolNamesForPosture } from '../../../../agents/runtime/state.js'; import { groundingFloorGaps } from '../../../../graph/schema/elicitation-gap-fixtures.js'; import type { ElicitationGap } from '../../../../graph/schema/elicitation-gaps.js'; import { isToolBlockedForRuntimeState, toolPolicyForRuntimeState, } from '../../../../projections/session/runtime-policy.js'; -import { activeToolNamesForPosture } from './state.js'; -export { agentBodyResourceLocation } from './state.js'; +export { agentBodyResourceLocation } from '../../../../agents/runtime/state.js'; export { DEFAULT_BRUNCH_AGENT_STATE, diff --git a/src/.pi/extensions/agent-runtime/system-prompts/index.ts b/src/.pi/extensions/agent-runtime/system-prompts/index.ts index 39ee68447..3af1ba981 100644 --- a/src/.pi/extensions/agent-runtime/system-prompts/index.ts +++ b/src/.pi/extensions/agent-runtime/system-prompts/index.ts @@ -2,6 +2,7 @@ import { readFile } from 'node:fs/promises'; import type { ExtensionAPI } from '@earendil-works/pi-coding-agent'; +import { composeAgentPrompt, type AgentPromptContextBundle } from '../../../../agents/runtime/compose.js'; import { composeAgentContextSeed, type AgentPromptSessionContext, @@ -14,7 +15,6 @@ import { agentBodyResourceLocation, projectBrunchAgentState, } from '../runtime/index.js'; -import { composeAgentPrompt, type AgentPromptContextBundle } from './compose.js'; import { createWorldReadCache, type WorldReads } from './world-reads.js'; type BrunchAgentStateEntries = Parameters[0]; diff --git a/src/.pi/extensions/brunch-data/reconciliation/index.test.ts b/src/.pi/extensions/brunch-data/reconciliation/index.test.ts index 15e147798..fa0415d51 100644 --- a/src/.pi/extensions/brunch-data/reconciliation/index.test.ts +++ b/src/.pi/extensions/brunch-data/reconciliation/index.test.ts @@ -1,6 +1,7 @@ import { eq } from 'drizzle-orm'; import { describe, expect, it } from 'vitest'; +import { activeToolNamesForPosture } from '../../../../agents/runtime/state.js'; import { createDb } from '../../../../db/connection.js'; import * as schema from '../../../../db/schema.js'; import { @@ -10,7 +11,6 @@ import { } from '../../../../graph/index.js'; import { groundingFloorGaps } from '../../../../graph/schema/elicitation-gap-fixtures.js'; import { projectBrunchAgentState } from '../../agent-runtime/runtime/index.js'; -import { activeToolNamesForPosture } from '../../agent-runtime/runtime/state.js'; import { READ_RECONCILIATION_NEEDS_TOOL, registerBrunchReconciliation, diff --git a/src/.pi/extensions/subagents/prompt-assembly.ts b/src/.pi/extensions/subagents/prompt-assembly.ts index d8dfb9f1b..13f5e06ea 100644 --- a/src/.pi/extensions/subagents/prompt-assembly.ts +++ b/src/.pi/extensions/subagents/prompt-assembly.ts @@ -1,3 +1,5 @@ +import { renderBrunchSkills, type PromptManifests } from '../../../agents/runtime/prompt-skills.js'; +import { LENS_RESOURCES, METHOD_RESOURCES, STRATEGY_RESOURCES } from '../../../agents/runtime/state.js'; import type { ElicitationGap } from '../../../graph/schema/elicitation-gaps.js'; import type { AgentPromptSpecContext, @@ -5,9 +7,6 @@ import type { AgentPromptSessionContext, } from '../../../session/agent-context-seed.js'; import { renderWorkspaceSeed } from '../../../session/agent-context-seed.js'; -import { LENS_RESOURCES, METHOD_RESOURCES, STRATEGY_RESOURCES } from '../agent-runtime/runtime/state.js'; -import type { PromptManifests } from '../agent-runtime/system-prompts/prompt-skills.js'; -import { renderBrunchSkills } from '../agent-runtime/system-prompts/prompt-skills.js'; import type { SubagentDefinition } from './agents.js'; export interface BackgroundWorldSnapshot { diff --git a/src/README.md b/src/README.md index 951cb7f4e..823ec77d9 100644 --- a/src/README.md +++ b/src/README.md @@ -10,7 +10,8 @@ src/ │ ├── agents/ Pi-independent owner for Brunch-authored LLM context ingress │ ├── prompts/ agent role body markdown resources -│ └── skills/ prompt-resource markdown resources +│ ├── skills/ prompt-resource markdown resources +│ └── runtime/ prompt composition and prompt-resource/tool legality │ ├── .pi/ Sealed Pi-harness runtime surface │ ├── components/ reusable Pi TUI/message components @@ -59,7 +60,7 @@ Rules: - `workspace/` owns cwd-scoped identity, inventory, and workspace default-state persistence. It must not import Pi, session, graph, DB, projection, renderer, adapter, transport, app, or web modules. - `graph/` imports from `db/`. No other layer imports `db/` directly. -- `agents/` owns the Brunch-authored LLM-context ingress seam. Today it hosts agent prompt bodies, prompt-resource skills, and their central file registry; later slices move runtime composition, seeds, and agent-visible rendering under this seam. The current `projections/session/runtime-policy.ts` import of this registry is a migration edge only: once runtime policy moves under `agents/runtime/`, projections should stop depending on `agents/`. +- `agents/` owns the Brunch-authored LLM-context ingress seam. Today it hosts agent prompt bodies, prompt-resource skills, prompt composition, prompt-resource/tool legality, and their central file registry; later slices move seeds and agent-visible rendering under this seam. The current `projections/session/runtime-policy.ts` import of this registry is a migration edge only: once the foreground roster moves under `agents/runtime/`, projections should stop depending on `agents/`. - `.pi/` owns Pi-harness extensions/components and no longer hosts Brunch-authored prompt bodies or prompt-resource skills. - `.pi/extensions/` registers Pi tools/hooks/UI affordances and delegates product semantics outward. - `projections/` owns reusable structured output; `renderers/` owns reusable lossy text output. diff --git a/src/agents/README.md b/src/agents/README.md index ad66ccff7..9c6a3b140 100644 --- a/src/agents/README.md +++ b/src/agents/README.md @@ -11,6 +11,7 @@ agents/ ├── README.md ├── prompts/ bundled foreground/background agent body markdown ├── skills/ strategy/lens/method prompt-resource markdown +├── runtime/ prompt composition and prompt-resource/tool legality ├── registry.ts path registry for bundled agent bodies and prompt-resource skills └── __tests__/ registry/topology tests ``` @@ -28,4 +29,4 @@ rules: ## Migration note -This directory is intentionally mid-migration. Agent prompt bodies and prompt-resource skills have moved here byte-stably. Later slices move prompt composition, seed context, runtime policy, and agent-visible renderers here; Pi extensions remain runtime adapters that register hooks/tools and call this layer. +This directory is intentionally mid-migration. Agent prompt bodies, prompt-resource skills, prompt composition, and prompt-resource/tool legality have moved here byte-stably. Later slices move seed context and agent-visible renderers here; Pi extensions remain runtime adapters that register hooks/tools and call this layer. diff --git a/src/agents/runtime/README.md b/src/agents/runtime/README.md new file mode 100644 index 000000000..04173ab71 --- /dev/null +++ b/src/agents/runtime/README.md @@ -0,0 +1,33 @@ +# agents/runtime/ — agent prompt runtime policy + +SPEC decisions: D40-L, D52-L, D58-L, D85-L, D90-L, D93-L + +## Owns + +Runtime prompt policy that is Pi-independent: foreground prompt composition, prompt-resource manifest rendering/loading, active method/tool derivation, and agent body location lookup. + +```text +runtime/ +├── README.md +├── compose.ts pure prompt composer: agent body + runtime header + context + manifest +├── prompt-skills.ts prompt-resource manifest loader/renderer +├── state.ts runtime-state-to-manifest/tool policy projection +├── __tests__/ prompt/runtime policy tests +└── __snapshots__/ Vitest file snapshots for full composed prompts +``` + +## Boundary rules + +```pseudo +rules: + agents/runtime -> agents/registry, agents/prompts, agents/skills + agents/runtime -> graph/, projections/, renderers/, session/ [read/projection types and helpers] + .pi/extensions/agent-runtime/* -> agents/runtime [adapter calls central policy] + agents/runtime x> .pi extension hooks/tools [no Pi registration side effects] +``` + +Pi extensions remain the runtime adapter: they gather the current Pi session state, graph reads, active tool registry, and base system prompt, then call this layer to produce Brunch-authored model-facing text and tool/resource legality. + +## Migration note + +This directory was moved from `.pi/extensions/agent-runtime/{runtime,system-prompts}` during the LLM-context ingress refactor. The remaining `.pi/extensions/agent-runtime/` files should stay thin: hook registration, Pi API calls, and adapter-specific tool activation only. diff --git a/src/.pi/extensions/agent-runtime/system-prompts/__snapshots__/elicitor--auto-floor-gaps-open.md b/src/agents/runtime/__snapshots__/elicitor--auto-floor-gaps-open.md similarity index 100% rename from src/.pi/extensions/agent-runtime/system-prompts/__snapshots__/elicitor--auto-floor-gaps-open.md rename to src/agents/runtime/__snapshots__/elicitor--auto-floor-gaps-open.md diff --git a/src/.pi/extensions/agent-runtime/system-prompts/__snapshots__/elicitor--auto-high-coverage.md b/src/agents/runtime/__snapshots__/elicitor--auto-high-coverage.md similarity index 100% rename from src/.pi/extensions/agent-runtime/system-prompts/__snapshots__/elicitor--auto-high-coverage.md rename to src/agents/runtime/__snapshots__/elicitor--auto-high-coverage.md diff --git a/src/.pi/extensions/agent-runtime/system-prompts/__snapshots__/elicitor--pinned-strategy-lens.md b/src/agents/runtime/__snapshots__/elicitor--pinned-strategy-lens.md similarity index 100% rename from src/.pi/extensions/agent-runtime/system-prompts/__snapshots__/elicitor--pinned-strategy-lens.md rename to src/agents/runtime/__snapshots__/elicitor--pinned-strategy-lens.md diff --git a/src/.pi/extensions/agent-runtime/system-prompts/__snapshots__/elicitor--pushed-context.md b/src/agents/runtime/__snapshots__/elicitor--pushed-context.md similarity index 100% rename from src/.pi/extensions/agent-runtime/system-prompts/__snapshots__/elicitor--pushed-context.md rename to src/agents/runtime/__snapshots__/elicitor--pushed-context.md diff --git a/src/.pi/extensions/agent-runtime/system-prompts/__tests__/compose.test.ts b/src/agents/runtime/__tests__/compose.test.ts similarity index 97% rename from src/.pi/extensions/agent-runtime/system-prompts/__tests__/compose.test.ts rename to src/agents/runtime/__tests__/compose.test.ts index caac2cb45..a34411a6b 100644 --- a/src/.pi/extensions/agent-runtime/system-prompts/__tests__/compose.test.ts +++ b/src/agents/runtime/__tests__/compose.test.ts @@ -5,21 +5,19 @@ import { fileURLToPath } from 'node:url'; import { parseFrontmatter } from '@earendil-works/pi-coding-agent'; import { describe, expect, it } from 'vitest'; -import { groundingFloorGaps } from '../../../../../graph/schema/elicitation-gap-fixtures.js'; -import type { ElicitationGap } from '../../../../../graph/schema/elicitation-gaps.js'; -import type { NodeKind } from '../../../../../graph/schema/nodes.js'; +import { groundingFloorGaps } from '../../../graph/schema/elicitation-gap-fixtures.js'; +import type { ElicitationGap } from '../../../graph/schema/elicitation-gaps.js'; +import type { NodeKind } from '../../../graph/schema/nodes.js'; import { DEFAULT_BRUNCH_AGENT_STATE, projectBrunchAgentState, -} from '../../../../../projections/session/runtime-state.js'; -import type { WorkspacePostureState } from '../../../../../session/workspace-session-coordinator.js'; -import { LENS_RESOURCES, METHOD_RESOURCES, STRATEGY_RESOURCES } from '../../runtime/state.js'; +} from '../../../projections/session/runtime-state.js'; +import type { WorkspacePostureState } from '../../../session/workspace-session-coordinator.js'; import { composeAgentPrompt, type ComposeAgentPromptInput } from '../compose.js'; import { renderBrunchSkills } from '../prompt-skills.js'; +import { LENS_RESOURCES, METHOD_RESOURCES, STRATEGY_RESOURCES } from '../state.js'; -const projectRoot = dirname( - dirname(dirname(dirname(dirname(dirname(dirname(fileURLToPath(import.meta.url))))))), -); +const projectRoot = dirname(dirname(dirname(dirname(dirname(fileURLToPath(import.meta.url)))))); const groundingSpec = { id: 1, diff --git a/src/.pi/extensions/agent-runtime/runtime/state.test.ts b/src/agents/runtime/__tests__/state.test.ts similarity index 95% rename from src/.pi/extensions/agent-runtime/runtime/state.test.ts rename to src/agents/runtime/__tests__/state.test.ts index 3bba30aac..fdc582055 100644 --- a/src/.pi/extensions/agent-runtime/runtime/state.test.ts +++ b/src/agents/runtime/__tests__/state.test.ts @@ -3,15 +3,15 @@ import { fileURLToPath } from 'node:url'; import { describe, expect, it } from 'vitest'; -import { bundledAgentBodyLocation } from '../../../../agents/registry.js'; -import { groundingFloorGaps } from '../../../../graph/schema/elicitation-gap-fixtures.js'; +import { BRUNCH_ORCHESTRATOR_STUB_TOOL } from '../../../.pi/extensions/agent-runtime/orchestrator-stub/index.js'; +import { groundingFloorGaps } from '../../../graph/schema/elicitation-gap-fixtures.js'; import { FOREGROUND_AGENT_ROSTER, delegatableAgentsForRuntimeState, -} from '../../../../projections/session/runtime-policy.js'; -import { projectBrunchAgentState } from '../../../../projections/session/runtime-state.js'; -import { BRUNCH_ORCHESTRATOR_STUB_TOOL } from '../orchestrator-stub/index.js'; -import { activeToolNamesForPosture, agentBodyResourceLocation, manifestsForState } from './state.js'; +} from '../../../projections/session/runtime-policy.js'; +import { projectBrunchAgentState } from '../../../projections/session/runtime-state.js'; +import { bundledAgentBodyLocation } from '../../registry.js'; +import { activeToolNamesForPosture, agentBodyResourceLocation, manifestsForState } from '../state.js'; const registeredToolNames = [ 'read', @@ -295,7 +295,7 @@ describe('agent posture policy', () => { }); it('keeps state.ts free of grade-gate symbols', () => { - const source = readFileSync(fileURLToPath(new URL('./state.ts', import.meta.url)), 'utf8'); + const source = readFileSync(fileURLToPath(new URL('../state.ts', import.meta.url)), 'utf8'); expect(source).not.toMatch(/ReadinessGrade|GRADE_RANK|MIN_GRADE|isGradeLegal/); }); }); diff --git a/src/.pi/extensions/agent-runtime/system-prompts/compose.ts b/src/agents/runtime/compose.ts similarity index 91% rename from src/.pi/extensions/agent-runtime/system-prompts/compose.ts rename to src/agents/runtime/compose.ts index 40b87ac0a..e674a0e8c 100644 --- a/src/.pi/extensions/agent-runtime/system-prompts/compose.ts +++ b/src/agents/runtime/compose.ts @@ -1,13 +1,13 @@ -import { selectElicitationGap } from '../../../../graph/elicitation-driver.js'; -import type { ElicitationGap } from '../../../../graph/schema/elicitation-gaps.js'; -import type { ResolvedBrunchAgentState } from '../../../../projections/session/runtime-state.js'; -import { renderSoftReadinessEstimate } from '../../../../renderers/session/readiness-estimate.js'; +import { selectElicitationGap } from '../../graph/elicitation-driver.js'; +import type { ElicitationGap } from '../../graph/schema/elicitation-gaps.js'; +import type { ResolvedBrunchAgentState } from '../../projections/session/runtime-state.js'; +import { renderSoftReadinessEstimate } from '../../renderers/session/readiness-estimate.js'; import type { AgentPromptSpecContext, AgentPromptWorkspaceContext, -} from '../../../../session/agent-context-seed.js'; -import { manifestsForState } from '../runtime/state.js'; +} from '../../session/agent-context-seed.js'; import { renderBrunchSkills, type PromptManifests } from './prompt-skills.js'; +import { manifestsForState } from './state.js'; export interface AgentPromptContextBundle { contextHandles?: readonly string[]; diff --git a/src/.pi/extensions/agent-runtime/system-prompts/prompt-skills.ts b/src/agents/runtime/prompt-skills.ts similarity index 96% rename from src/.pi/extensions/agent-runtime/system-prompts/prompt-skills.ts rename to src/agents/runtime/prompt-skills.ts index 51c495ea9..e6d8370a9 100644 --- a/src/.pi/extensions/agent-runtime/system-prompts/prompt-skills.ts +++ b/src/agents/runtime/prompt-skills.ts @@ -2,11 +2,7 @@ import { basename, dirname } from 'node:path'; import { loadSkills, type Skill } from '@earendil-works/pi-coding-agent'; -import { - promptResourceAgentDir, - promptResourceLocation, - type PromptResourceFamily, -} from '../../../../agents/registry.js'; +import { promptResourceAgentDir, promptResourceLocation, type PromptResourceFamily } from '../registry.js'; export interface PromptResourceManifestEntry { name: string; diff --git a/src/.pi/extensions/agent-runtime/runtime/state.ts b/src/agents/runtime/state.ts similarity index 92% rename from src/.pi/extensions/agent-runtime/runtime/state.ts rename to src/agents/runtime/state.ts index 32112fdb3..f0f67ea5b 100644 --- a/src/.pi/extensions/agent-runtime/runtime/state.ts +++ b/src/agents/runtime/state.ts @@ -1,27 +1,27 @@ -import { bundledAgentBodyLocation } from '../../../../agents/registry.js'; -import type { ElicitationGap } from '../../../../graph/schema/elicitation-gaps.js'; -import type { CapabilityId } from '../../../../projections/session/capability-readiness.js'; +import type { ElicitationGap } from '../../graph/schema/elicitation-gaps.js'; +import type { CapabilityId } from '../../projections/session/capability-readiness.js'; import { AUTO_EXCLUDED_STRATEGIES, axisOptionsForRuntimeState, isCapabilityLegalForGaps, toolPolicyForRuntimeState, type ResolvedBrunchAgentState, -} from '../../../../projections/session/runtime-policy.js'; +} from '../../projections/session/runtime-policy.js'; import { AGENT_LENS_IDS, AGENT_METHOD_IDS, AGENT_STRATEGY_IDS, type AgentMethodId, type AgentRoleId, -} from '../../../../session/schema/kinds.js'; +} from '../../session/schema/kinds.js'; +import { bundledAgentBodyLocation } from '../registry.js'; import { loadPromptResourceManifestEntries, type PromptManifests, type PromptResourceManifestEntry, -} from '../system-prompts/prompt-skills.js'; +} from './prompt-skills.js'; -export type { PromptManifests, PromptResourceManifestEntry } from '../system-prompts/prompt-skills.js'; +export type { PromptManifests, PromptResourceManifestEntry } from './prompt-skills.js'; export type MethodId = AgentMethodId; diff --git a/src/agents/skills/README.md b/src/agents/skills/README.md index c38942226..017b381d0 100644 --- a/src/agents/skills/README.md +++ b/src/agents/skills/README.md @@ -4,7 +4,7 @@ SPEC decisions: D25-L, D39-L, D52-L, D58-L, D59-L, D85-L ## Owns -Agent Skills-standard prompt resources the Brunch Pi session agent reads on demand after `.pi/extensions/agent-runtime/runtime/state.ts` advertises them in a runtime-filtered `` manifest. +Agent Skills-standard prompt resources the Brunch Pi session agent reads on demand after `agents/runtime/state.ts` advertises them in a runtime-filtered `` manifest. These are Brunch-authored model-facing prompt resources, not product data models and not ambient filesystem discovery inputs. @@ -20,19 +20,19 @@ skills/ └── references/*.md optional disclosed reference payloads ``` -Each live resource is a directory whose `SKILL.md` has YAML frontmatter (`name`, `description`) plus the instruction body. `name` must equal the parent directory and the code-owned id in `.pi/extensions/agent-runtime/runtime/state.ts`. +Each live resource is a directory whose `SKILL.md` has YAML frontmatter (`name`, `description`) plus the instruction body. `name` must equal the parent directory and the code-owned id in `agents/runtime/state.ts`. ## Boundary rules ```pseudo rules: - .pi/extensions/agent-runtime/runtime/state.ts -> agents/skills/*/*/SKILL.md [explicit code-owned path list via agents/registry.ts] - .pi/extensions/agent-runtime/runtime/state.ts -> pi loadSkills(includeDefaults:false, skillPaths=[...]) + agents/runtime/state.ts -> agents/skills/*/*/SKILL.md [explicit code-owned path list via agents/registry.ts] + agents/runtime/state.ts -> pi loadSkills(includeDefaults:false, skillPaths=[...]) agents/skills/**/SKILL.md x> TypeScript imports [read-only prompt resources] agents/skills/ x> graph mutation [guidance only] ``` -The legal set is sealed by the code-owned path list in `.pi/extensions/agent-runtime/runtime/state.ts`; adding a `SKILL.md` does not make it available until that table enumerates it. `src/agents/registry.ts` owns file locations. Frontmatter owns `name` and `description`; code owns axis family, legality, and location enumeration. The former `goals/` family is retired by D85-L; the elicitor objective postures are inline in `src/agents/prompts/elicitor/SYSTEM.md`. +The legal set is sealed by the code-owned path list in `agents/runtime/state.ts`; adding a `SKILL.md` does not make it available until that table enumerates it. `src/agents/registry.ts` owns file locations. Frontmatter owns `name` and `description`; code owns axis family, legality, and location enumeration. The former `goals/` family is retired by D85-L; the elicitor objective postures are inline in `src/agents/prompts/elicitor/SYSTEM.md`. ## Prompt-resource sub-shapes diff --git a/src/projections/README.md b/src/projections/README.md index 65fecc96d..350382e77 100644 --- a/src/projections/README.md +++ b/src/projections/README.md @@ -28,7 +28,7 @@ Disposition: `✓` resolved (direct lock or accepted transitive proof) · `●` | `session/transcript-context` | 2 | ✓ | `transcript-context.test.ts` — no non-empty markdown-bearing message disappears across the Pi `buildSessionContext()` + `convertToLlm()` seam; non-renderable entries drop at the projection boundary. | | `session/runtime-state` | 13 | ✓ | `runtime-state.test.ts` — direct flattened-shape invariant for defaults, last-writer-wins runtime posture, mentions/world/lifecycle slots, and non-linear transcript rejection. | | `session/affordances` | 1 | ✓ | `affordances.test.ts` — gap-driven legality + default-on-switch derivation tested directly. Legal options are a menu projection over capability-readiness; omitted options are not capability refusals (I31-L). | -| `session/capability-readiness` | 1 | ✓ | D74-L/D75-L tracer gate, not a reusable DTO. `capability-readiness.test.ts` locks the explicit capability→node-kind map, proceed / low-epistemic / negotiate outcomes, no-refusal invariant, loud failure when the gap register lacks a required kind, same-kind discrimination through `question`, and live presence-coverage flip. `session/affordances` now consumes it for axis-option legality. **D86-L: capability-readiness gates AUTO axis menus (`strategy`/`lens`) and the non-graph-write `review-for-gaps` method only — it never withholds a graph-write tool. `mutate_graph` and the review-set tools (`present_review_set`/`request_response`) are floor in elicit mode (their `commit-graph`/`generate-proposal` methods are absent from `METHOD_CAPABILITY` in `.pi/extensions/agent-runtime/runtime/state.ts`); `negotiate` is advisory (establishment offer + epistemic scaling), proven by `state.test.ts` + the tier-2 live-boot legality test.** | +| `session/capability-readiness` | 1 | ✓ | D74-L/D75-L tracer gate, not a reusable DTO. `capability-readiness.test.ts` locks the explicit capability→node-kind map, proceed / low-epistemic / negotiate outcomes, no-refusal invariant, loud failure when the gap register lacks a required kind, same-kind discrimination through `question`, and live presence-coverage flip. `session/affordances` now consumes it for axis-option legality. **D86-L: capability-readiness gates AUTO axis menus (`strategy`/`lens`) and the non-graph-write `review-for-gaps` method only — it never withholds a graph-write tool. `mutate_graph` and the review-set tools (`present_review_set`/`request_response`) are floor in elicit mode (their `commit-graph`/`generate-proposal` methods are absent from `METHOD_CAPABILITY` in `agents/runtime/state.ts`); `negotiate` is advisory (establishment offer + epistemic scaling), proven by `state.test.ts` + the tier-2 live-boot legality test.** | | `session/readiness-estimate` | — | ✓ | D45-L soft per-band coverage rollup over `ElicitationGap[]`; UI-only and gates nothing. `readiness-estimate.test.ts` locks every-band shape, empty-band zero, importance-weighted mean, honest regression, no grade imports, and no legality-path imports. | | `session/runtime-policy` | 4 | ○ | Policy/definitions data, not a DTO transform. Gap-driven legality is guarded via `affordances.test.ts`; no runtime grade table remains. | | `session/assistant-visible-watermark` | 2 | ✓ | Carrier projection over the authoritative `continuity-entry-classifier` watermark set. Unit tests guard seed/overview/own-mutation/`worldUpdate` carriers, narrow-read exclusion, and cross-spec failure. | diff --git a/src/projections/session/__tests__/readiness-estimate.test.ts b/src/projections/session/__tests__/readiness-estimate.test.ts index 52ff0cf5a..5d4e6579c 100644 --- a/src/projections/session/__tests__/readiness-estimate.test.ts +++ b/src/projections/session/__tests__/readiness-estimate.test.ts @@ -71,7 +71,7 @@ describe('readiness estimate projection', () => { for (const relativePath of [ '../runtime-policy.ts', '../affordances.ts', - '../../../.pi/extensions/agent-runtime/runtime/state.ts', + '../../../agents/runtime/state.ts', ]) { const source = readFileSync(fileURLToPath(new URL(relativePath, import.meta.url)), 'utf8'); expect(source).not.toMatch(/readiness-estimate|readinessEstimate/); diff --git a/src/session/README.md b/src/session/README.md index 160730102..345db1252 100644 --- a/src/session/README.md +++ b/src/session/README.md @@ -106,7 +106,7 @@ directly instead of growing a wrapper. | `cwd_inventory` | `workspace/cwd-inventory.ts` (`inspectWorkspaceCwdInventory`) | `read_workspace_context`, `renderers/workspace/workspace-context.ts` | Workspace-owned direct PULL read. The typed inventory already matches the tool/renderer seam, so no `projections/workspace/workspace-context` wrapper survives. | | `workspace_overview` | `workspace-overview-context.ts` (`inspectWorkspaceOverview`) | `read_workspace_context`, origination seed context, `renderers/workspace/workspace-context.ts` | Session-side composition over graph specs and canonical session files. Same no-wrapper rationale as `cwd_inventory`: the source shape is already the consumer shape. | | `workspace_session_state` | `WorkspaceSessionCoordinator` (`WorkspaceSessionState`) | `projections/workspace/workspace-state.ts`, `chromeStateForWorkspace`, app/rpc/web workspace flows | Source union owned by the coordinator. Downstream code may flatten it, but the coordinator remains the authority for the narrow chrome snapshot and status-variant field set. | -| `agent_runtime_vocab` | `schema/kinds.ts`, `schema/tool-names.ts` | `runtime-state.ts`, `projections/session/runtime-policy.ts`, `projections/session/affordances.ts`, `.pi/extensions/agent-runtime/runtime/state.ts`, `.pi/extensions/agent-runtime/orchestrator-stub/` | Pure vocabulary leaf for runtime axes, agent-role ids, and shared Brunch tool-name constants; imports nothing and mirrors D73-L's graph taxonomy direction on the session side. | +| `agent_runtime_vocab` | `schema/kinds.ts`, `schema/tool-names.ts` | `runtime-state.ts`, `projections/session/runtime-policy.ts`, `projections/session/affordances.ts`, `agents/runtime/state.ts`, `.pi/extensions/agent-runtime/orchestrator-stub/` | Pure vocabulary leaf for runtime axes, agent-role ids, and shared Brunch tool-name constants; imports nothing and mirrors D73-L's graph taxonomy direction on the session side. | | `agent_runtime_state` | `latestValidBrunchAgentStateEntryData` and transcript-backed runtime-state facts in `session/runtime-state.ts` | `projections/session/runtime-state.ts`, `projections/session/affordances.ts`, `.pi/extensions/agent-runtime/runtime/` | Transcript-backed source read. Projection/policy layers derive from these facts rather than storing parallel hidden runtime memory. | ## Runtime affordance coverage ledger diff --git a/src/session/agent-context-seed.ts b/src/session/agent-context-seed.ts index 6a944eeb0..f2b8ba326 100644 --- a/src/session/agent-context-seed.ts +++ b/src/session/agent-context-seed.ts @@ -5,7 +5,7 @@ * Owns the per-turn pushed context blocks the agent receives each turn: the * selected-workspace seed and the selected-spec graph seed. This is session/ * world state rendered for the agent, distinct from system-prompt assembly - * (`.pi/extensions/agent-runtime/system-prompts/compose.ts`), which only splices these blocks + * (`agents/runtime/compose.ts`), which only splices these blocks * into the prompt frame. Keeping composition here means cycling operational * modes — which swaps the agent role and therefore the system prompt — does not * re-own context derivation: the prompt layer consumes a bundle it does not From b9f225485b2aca720b1a3c644dc357e97eec83ac Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Thu, 25 Jun 2026 18:42:08 +0200 Subject: [PATCH 14/54] Move agent context seeds under agents --- memory/REFACTOR.md | 2 +- memory/SPEC.md | 10 +++---- .../agent-runtime/system-prompts/index.ts | 4 +-- .../extensions/subagents/prompt-assembly.ts | 10 +++---- src/README.md | 9 ++++--- src/agents/README.md | 11 +++++--- src/agents/contexts/README.md | 26 +++++++++++++++++++ src/agents/contexts/seeds/README.md | 22 ++++++++++++++++ .../seeds/__tests__/origination.test.ts} | 4 +-- .../seeds/__tests__/turn-context.test.ts} | 6 ++--- .../contexts/seeds/origination.ts} | 8 +++--- .../contexts/seeds/turn-context.ts} | 18 ++++++------- src/agents/runtime/compose.ts | 5 +--- src/app/pi-subagents.ts | 2 +- src/renderers/README.md | 4 +-- src/session/README.md | 12 ++++----- src/session/originate-assistant-turn.ts | 2 +- 17 files changed, 102 insertions(+), 53 deletions(-) create mode 100644 src/agents/contexts/README.md create mode 100644 src/agents/contexts/seeds/README.md rename src/{session/__tests__/context-seed.test.ts => agents/contexts/seeds/__tests__/origination.test.ts} (96%) rename src/{session/__tests__/agent-context-seed.test.ts => agents/contexts/seeds/__tests__/turn-context.test.ts} (96%) rename src/{session/context-seed.ts => agents/contexts/seeds/origination.ts} (89%) rename src/{session/agent-context-seed.ts => agents/contexts/seeds/turn-context.ts} (91%) diff --git a/memory/REFACTOR.md b/memory/REFACTOR.md index bcefe1340..0b4d410d1 100644 --- a/memory/REFACTOR.md +++ b/memory/REFACTOR.md @@ -67,7 +67,7 @@ callers after refactor 2. ✓ Move agent prompt bodies into the new prompt home and update foreground/body loading, background subagent loading, build asset copying, and prompt-body tests without changing prompt bytes. 3. ✓ Move Brunch prompt-resource skills into the new skills home and update manifest loading, build asset copying, resource-location snapshots, and skill topology docs without changing skill bytes. 4. ✓ Move foreground prompt composition and prompt-resource legality code into the new runtime home; leave Pi extension code as a thin hook adapter that imports the central composer. -5. Move per-turn pushed context composition and origination/session-entry seed composition into the new context seed home; update session and app callers to import from the central agent context layer. +5. ✓ Move per-turn pushed context composition and origination/session-entry seed composition into the new context seed home; update session and app callers to import from the central agent context layer. 6. Move reusable LLM-facing context renderers into the new context home, keeping product-only/human-only renderers outside unless they are deliberately agent-visible. 7. Promote adapter-local LLM text formatting into the new context home: graph mutation result text, related-node text, elicitation agenda/update text, and reconciliation agenda/update text. 8. Consolidate golden/preview tests for prompt composition and agent context renderers under the new agent tree, preserving existing semantic invariants while making “what enters the model” reviewable in one place. diff --git a/memory/SPEC.md b/memory/SPEC.md index 7fdf5fd10..0233d1fd6 100644 --- a/memory/SPEC.md +++ b/memory/SPEC.md @@ -133,7 +133,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c - **D40-L — Runtime state is transcript-backed Brunch session-agent state, not hidden extension memory.** The architectural commitment is that Brunch session-agent posture remains transcript-backed Pi JSONL state rather than hidden extension memory: posture switches are user/system authority, the foreground session agent is derived from `op_mode`, child axes default back to `AUTO` when invalidated, and tool authority remains `op_mode`-gated rather than prompt-composition-owned. Runtime-state entries are Pi JSONL state-change facts, not assistant/user chat content: init and switch entries should render, when visible, as dim non-chat state rows analogous to Pi thinking/model-change rows, and must not enter LLM context as ordinary conversation. Current materialized state lives in [`src/session/README.md`](src/session/README.md), [`src/projections/README.md`](src/projections/README.md), [`src/.pi/README.md`](src/.pi/README.md), [`src/agents/README.md`](src/agents/README.md), [`src/agents/prompts/README.md`](src/agents/prompts/README.md), and [`src/projections/session/runtime-policy.ts`](src/projections/session/runtime-policy.ts). Depends on: D17-L, D23-L, D25-L, D39-L, D58-L, D59-L. Refined by: D85-L (the AUTO-able objective axes reduce to `strategy` + `lens`; `goal` is dropped from the runtime-state axis set and inlined into the agent role prompt). Supersedes: mode-only vocabulary, extension-local mutable state as authority, storing the foreground role as independent session state, the "runtime bundle / role preset" as one knob deriving model/thinking/resources, and binding prompt-resource location to `src/.pi/context/`. - **D34-L — Command containment separates visibility suppression from effect blocking.** Current Pi extension seams can hide unsupported slash suggestions with autocomplete wrapping and can cancel branch/session effects through lifecycle hooks, but they cannot strictly suppress exact interactive built-in commands before `InteractiveMode` dispatches them. Brunch-owned commands must use product-specific names and route writes through Brunch handlers/`CommandExecutor`; extension command collisions are not an override mechanism. Strict built-in command/keybinding policy is a Pi upstream/API ask, while POC safety relies on hiding generic affordances, blocking dangerous effects (`/fork`, `/clone`, raw session replacement), allowing native `/tree` as inspection/navigation, and failing fast on branched transcripts. Brunch's command-policy code should live in `src/.pi/extensions/commands/policy.ts`, merging branch/session-effect blocking with any product command allow/deny behavior instead of preserving a branch-only module. Depends on: D2-L, D24-L, A18-L. Supersedes: treating extension `input` handlers or command-name collisions as built-in command allowlisting. - **D35-L — Dynamic TUI chrome is a Brunch projection wrapper over Pi UI primitives.** The architectural commitment is that downstream TUI affordances call one Brunch-owned renderer (`renderBrunchChrome` or its successor) with a single activated product-state value rather than scattering raw `ctx.ui.setHeader` / `setFooter` / `setWidget` / title / working-indicator calls; the wrapper is stateless projection over canonical workspace/session/graph facts, never its own mutable state. Chrome is a project-first shell surface with selected-spec context — project name labels the cwd container, spec title labels the selected graph, session label distinguishes transcript instances — and a session label must never replace spec identity or graph truth. Chrome must not consume the status-key namespace for its own summary (`ctx.ui.setStatus` stays a lateral channel for other extensions), must not advertise unwired affordances, and RPC clients must rely only on surfaces Pi actually emits (header/footer/working-indicator are TUI-only in current Pi RPC mode). Current chrome state shape, render surfaces, telemetry/refresh, startup-header behavior, and status-key filtering live in [`src/.pi/extensions/chrome/README.md`](src/.pi/extensions/chrome/README.md); launch/activation wiring lives in [`src/.pi/extensions/README.md`](src/.pi/extensions/README.md). Depends on: D2-L, D21-L, D34-L, A18-L. Supersedes: treating Pi UI methods as direct downstream affordance APIs, rendering placeholder session state such as `unbound` after a session is activated, consuming the status-key namespace for chrome's own static summary, using spec title as the default session label, or allowing two unchanged Brunch-created default names to collide in one cwd, and the earlier resume/open-launches-stay-quiet clause (superseded 2026-06-11: the shipped, test-locked behavior headers every non-cancel activation). -- **D52-L — Source topology targets `src/{app, workspace, scripts, agents, .pi, db, graph, session, projections, renderers, rpc, web}` with directed layer dependencies.** Reusable projection and rendering modules live in top-level `src/projections/` and `src/renderers/` rather than whichever domain or adapter first needed them; `src/agents/` is the Pi-independent owner for Brunch-authored LLM context ingress (currently bundled agent prompt bodies, prompt-resource skills, prompt composition, prompt-resource/tool legality, and the central registry for their file paths, with later context-rendering moves tracked by the LLM-context refactor; the `projections/session/runtime-policy.ts` registry import is a temporary migration edge until the foreground roster moves under `agents/runtime/`); domain layers (`graph/`, `session/`) and the reusable `projections` / `renderers` / `agents` layers must not import adapters, transports, app entrypoints, or web code; `graph/` is the only layer that imports `db/`, plus the single sanctioned `db/`→`graph/schema/kinds.ts` taxonomy edge (D73-L). The concrete per-directory ownership, layout sketch, and full import matrix are owned by [`src/README.md`](src/README.md). Depends on: D2-L, D4-L, D39-L, D40-L. Refined by: D73-L. Supersedes: scattering session domain files at `src/` root; treating Pi-only agents as a host-independent top-level `src/.pi/` layer; nesting prompt composition under `src/.pi/context/`; treating reusable `project` / `format` helpers as owned by whichever adapter first needed them; treating retired `.pi/agents` / `.pi/skills` locations as the long-term conceptual owner for Brunch-authored model-facing content. +- **D52-L — Source topology targets `src/{app, workspace, scripts, agents, .pi, db, graph, session, projections, renderers, rpc, web}` with directed layer dependencies.** Reusable projection and rendering modules live in top-level `src/projections/` and `src/renderers/` rather than whichever domain or adapter first needed them; `src/agents/` is the Pi-independent owner for Brunch-authored LLM context ingress (currently bundled agent prompt bodies, prompt-resource skills, prompt composition, prompt-resource/tool legality, seed context composition, and the central registry for prompt/skill file paths, with later context-rendering moves tracked by the LLM-context refactor; the `projections/session/runtime-policy.ts` registry import is a temporary migration edge until the foreground roster moves under `agents/runtime/`); domain layers (`graph/`, `session/`) and the reusable `projections` / `renderers` / `agents` layers must not import adapters, transports, app entrypoints, or web code; `graph/` is the only layer that imports `db/`, plus the single sanctioned `db/`→`graph/schema/kinds.ts` taxonomy edge (D73-L). The concrete per-directory ownership, layout sketch, and full import matrix are owned by [`src/README.md`](src/README.md). Depends on: D2-L, D4-L, D39-L, D40-L. Refined by: D73-L. Supersedes: scattering session domain files at `src/` root; treating Pi-only agents as a host-independent top-level `src/.pi/` layer; nesting prompt composition under `src/.pi/context/`; treating reusable `project` / `format` helpers as owned by whichever adapter first needed them; treating retired `.pi/agents` / `.pi/skills` locations as the long-term conceptual owner for Brunch-authored model-facing content. - **D73-L — Domain enum taxonomy is owned by drizzle-free schema leaves; persistence and adapters are consumers, not the source.** The closed enum `const` arrays that define graph vocabulary — node kinds (`INTENT_KINDS`, `ORACLE_KINDS`, `DESIGN_KINDS`, `PLAN_KINDS`), `NODE_PLANES` (`intent`/`oracle`/`design`/`plan`), `NODE_BASES`, `EDGE_CATEGORIES`, `EDGE_STANCES`, `READINESS_BANDS`, `LENS_AFFINITIES`, `GAP_DISPOSITIONS`, and `GAP_PREDICATE_KINDS` — live in `graph/schema/kinds.ts`, a pure constants leaf that imports nothing (no drizzle, no `graph/atoms`). Both `db/schema.ts` (for `text({ enum })` column constraints, including the previously-inlined `plane` columns) and `graph/` domain modules import the arrays from this leaf; `graph/index.ts` re-exports them from the leaf so non-graph layers still avoid importing `db/` directly (I26-L). Session runtime axis vocabulary mirrors the same ownership direction in `session/schema/kinds.ts`: that leaf imports nothing and owns the `op_mode`, agent-role, `strategy`, `lens`, `auto`, and display-only planned mode choices consumed by `session/runtime-state.ts`, `projections/session/*`, and `agents/runtime/state.ts`; it deliberately contains no `goal` axis and no retired `READINESS_GRADES`. Derivations stay where they are read: `NODE_KIND_METADATA`, `formatGraphNodeCode`, `parseGraphNodeCode`, and `intentKindCategory` remain in `graph/schema/nodes.ts` (D62-L). The motivating defect: because `db/schema.ts` eagerly evaluates `sqliteTable(...)` and `verbatimModuleSyntax` emits even type-only imports at runtime, any value-import path from `web/` into the old taxonomy location pulled Drizzle into the browser bundle. Locating taxonomy in a drizzle-free leaf makes the `web/` build target structurally Drizzle-free (I44-L) and corrects the ownership direction so the domain, not the persistence layer, owns its vocabulary. Vocabulary migration status: `READINESS_GRADES` is retired (readiness is no longer a stored grade, D45-L), `ELICITATION_BACKLOG_STATUSES` is replaced by the `elicitation_gaps` disposition + predicate-shape enums (D65-L), and `READINESS_BANDS` stays. Depends on: D16-L, D52-L, D54-L, D62-L, D63-L, D64-L; I26-L. Supersedes: `db/schema.ts` owning the shared enum `const` arrays and the "enum literals flow outward from `db/schema.ts`" posture; the triplicated inline `['intent','oracle','design','plan']` plane literals. #### Data model & vocabulary @@ -272,18 +272,18 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c - **Projectors/reviewers** — **projector** (no tools) emits one variant of a candidate proposal from a grounding bundle and lens frame; **reviewer** (no tools) checks supplied candidate material before main-agent presentation or commitment. The main agent achieves diversity by issuing parallel `tasks: []` invocations of `projector` with intentionally distinct framings — the subagent realization of the "design it twice" pattern from `ln-design` and the parallel fan-out anticipated by `ln-oracles`. Each `projector` invocation runs in its own isolated context so variants don't cross-contaminate; the main agent collects outputs and owns any product write. This division mirrors the batch-proposal flow in D26-L. Worker-style write-capable subagents and nesting remain deferred beyond the initial `execute`/`orchestrator` standup. Cross-extension agent registration (Amos's `globalThis.__pi_subagents` bridge), raw `pi` subprocesses, and ambient `~/.pi` discovery are rejected for the POC because they conflict with profile sealing. Subagents remain an optional enhancement to candidate-proposal diversity and future delegated acquisition, not a load-bearing M0–M9 substrate. Depends on: D2-L, D26-L, D27-L, D30-L, D31-L, D39-L, D40-L, D41-L. Distinct from: D15-L Side task (non-blocking, status-via-custom-message), the deferred Side chat (user-invoked overlay; see Future Direction Register). Supersedes: subprocess/argv-shaped subagents and the `globalThis.__pi_subagents` bridge. Refined by: D90-L (shared foreground/background manifest + code-owned background discovery), D91-L (semi-permeable seal + assembled prompt + injected world), D92-L (sovereign tool grants + op_mode delegatable-set gate). - **D90-L — Foreground and background agents share one manifest model; background discovery is code-owned (frontmatter is authoring DX, not a second agent model).** Agent definitions project into one `AgentManifest` (`id`, `kind`, `description`, `model`, `thinking`, body at the canonical `src/agents/prompts//SYSTEM.md` convention, a skills grant, a tools grant, and a `canDelegate` set naming the background agents it may spawn — D92-L/D93-L) discriminated by `kind: "foreground" | "background"` — the execution **lifecycle/host**, not a noun: a foreground agent is a live op_mode-derived Pi session; a background agent is a spawned-to-completion sealed child. The kinds keep **distinct authority sources**: a foreground agent's identity is derived from `op_mode` (D40-L) and its tool/skill legality is dynamic (op_mode policy + live gaps); a background agent's identity is caller-chosen (`{agent, task}`) and its skills/tools come from its authored manifest. DX-vs-strictness is reconciled by keeping **frontmatter as the authoring surface** for background agents while making **discovery code-owned**: the `readdir` scan over `agents/*.md` is retired for an explicit registry id list (mirroring how `state.ts` loads foreground bodies/skills through `loadSkills({ skillPaths, includeDefaults: false })`), so D39-L "no filesystem discovery" holds and frontmatter authoring survives. "subagent" stays the tool/UX noun (the main-agent tool call), not the kind name. Depends on: D39-L, D40-L, D44-L, D58-L. Refines: D44-L (the parallel frontmatter-discovered format collapses into the shared manifest; background agent bodies migrated from `src/.pi/extensions/subagents/agents/*.md` onto the canonical `src/agents/prompts//SYSTEM.md` convention, so SPEC carries one agent-body layout — D44-L and the `src/.pi/extensions/subagents/README.md` topology notes reconcile to that path). Establishing frontier: `subagent-reconciliation`. Supersedes: `readdir` filesystem discovery of subagent definitions; the standalone subagent frontmatter format as a second, separate agent model. -- **D91-L — Background subagents run a semi-permeable seal: explicitly-injected parent world handles plus an assembled (not verbatim) prompt; ambient leakage stays closed.** This deliberately reopens the D44-L/I29-L "no graph access, no Brunch RPC, no inherited context" clause. The seal stays closed against **ambient** leakage (in-memory auth/settings/session, no `~/.pi` discovery — D39-L intact) but opens to **explicitly injected** parent world handles the app root (`src/app/pi-subagents.ts`) supplies at spawn: the same `GraphReaders` the foreground uses scoped to the parent's `specId`, the spec/workspace context seed, and a bounded **session digest** (the parent branch flattened via `sessionManager.getBranch()`, the pattern in pi's `summarize.ts` example). The child's system prompt becomes **assembled, not verbatim**: body + a background control header (sealed child, delegated task, snapshot view) + world snapshot + a `` manifest built from the manifest's skills grant + router rules — reusing the foreground composer's extracted prompt-skill core (`renderBrunchSkills`, the skill-manifest loader) plus the selected workspace/spec seed renderer from `src/session/agent-context-seed.ts`, minus the foreground-only elicitation-recommendation block. World binding is **snapshot-at-spawn** (the child runs to completion against a fixed view) where the foreground is live-per-turn. Read access is asymmetric **by design**: the **session digest** is a snapshot block baked into the prompt (expensive, rarely re-pulled), while the **graph** is exposed as Brunch read tools (`read_graph` now; `read_session_context`, `read_elicitation_gaps`, … remain future grants) the child calls on demand (a recon agent iterates on graph). Return to the main agent is the ordinary tool-call result: findings re-enter main-agent context as the tool-result `content`; the structured `details` payload (`{ agent, status, text, … }`) is render-only via custom `renderCall`/`renderResult`, never model context. Write-capable children stay deferred (gated by D92-L); when they land, a `mutate_graph` against the parent's `specId` is a real side effect crossing back *outside* the tool result, and is named here so the write slice does not surprise. Depends on: D39-L, D43-L, D44-L, D58-L, D60-L, D82-L. Establishing frontier: `subagent-reconciliation`. Supersedes: the D44-L/I29-L clause that subagents have no graph access, no Brunch RPC/graph reads, no inherited world context, and a verbatim-body system prompt. +- **D91-L — Background subagents run a semi-permeable seal: explicitly-injected parent world handles plus an assembled (not verbatim) prompt; ambient leakage stays closed.** This deliberately reopens the D44-L/I29-L "no graph access, no Brunch RPC, no inherited context" clause. The seal stays closed against **ambient** leakage (in-memory auth/settings/session, no `~/.pi` discovery — D39-L intact) but opens to **explicitly injected** parent world handles the app root (`src/app/pi-subagents.ts`) supplies at spawn: the same `GraphReaders` the foreground uses scoped to the parent's `specId`, the spec/workspace context seed, and a bounded **session digest** (the parent branch flattened via `sessionManager.getBranch()`, the pattern in pi's `summarize.ts` example). The child's system prompt becomes **assembled, not verbatim**: body + a background control header (sealed child, delegated task, snapshot view) + world snapshot + a `` manifest built from the manifest's skills grant + router rules — reusing the foreground composer's extracted prompt-skill core (`renderBrunchSkills`, the skill-manifest loader) plus the selected workspace/spec seed renderer from `src/agents/contexts/seeds/turn-context.ts`, minus the foreground-only elicitation-recommendation block. World binding is **snapshot-at-spawn** (the child runs to completion against a fixed view) where the foreground is live-per-turn. Read access is asymmetric **by design**: the **session digest** is a snapshot block baked into the prompt (expensive, rarely re-pulled), while the **graph** is exposed as Brunch read tools (`read_graph` now; `read_session_context`, `read_elicitation_gaps`, … remain future grants) the child calls on demand (a recon agent iterates on graph). Return to the main agent is the ordinary tool-call result: findings re-enter main-agent context as the tool-result `content`; the structured `details` payload (`{ agent, status, text, … }`) is render-only via custom `renderCall`/`renderResult`, never model context. Write-capable children stay deferred (gated by D92-L); when they land, a `mutate_graph` against the parent's `specId` is a real side effect crossing back *outside* the tool result, and is named here so the write slice does not surprise. Depends on: D39-L, D43-L, D44-L, D58-L, D60-L, D82-L. Establishing frontier: `subagent-reconciliation`. Supersedes: the D44-L/I29-L clause that subagents have no graph access, no Brunch RPC/graph reads, no inherited world context, and a verbatim-body system prompt. - **D92-L — Background tool grants are sovereign per-agent ceilings gated by a code-owned, op_mode-keyed delegatable-set allowlist — not parent-subset containment.** The earlier containment invariant (child tools ⊆ the parent's current legal set) is rejected: delegation is **capability-inverting on purpose** — a low-privilege orchestrator (the `execute`-mode foreground agent holding only data-layer tools) may spawn a narrow high-privilege child (e.g. a file-writing worker) so writes are quarantined in a child that does one job and exits. Each background agent's tool grant is therefore **sovereign** (authored in its manifest; may exceed the parent's). The surviving safety boundary is not a tool subset but **which background agents an op_mode may spawn**: a **code-owned, op_mode-keyed delegatable-set allowlist** living beside `AGENT_PROMPT_DEFINITIONS` / the op_mode policy, *not* authored in frontmatter (otherwise a manifest could self-advertise into a read-only mode). This lifts D40-L's registration ≠ advertisement from tools to agents: every background agent is registered; op_mode decides which are advertised as spawnable. A read-only `elicit` session is write-safe because elicit's delegatable set **excludes** write-capable agents, not because children are subset-bounded. Enabling write tools later = author the write-capable worker manifest + add it to the relevant op_mode's delegatable set (an advertisement change), not a re-derivation of parent authority. Depends on: D39-L, D40-L, D44-L. Establishing frontier: `subagent-reconciliation`. Refined by: D93-L (the delegatable-set allowlist becomes a per-agent `AgentManifest` `canDelegate` field; for a foreground mode it is that mode's code-owned delegatable set, and it generalizes to background→background nesting). Supersedes: the parent-subset tool-containment model for subagents; D44-L's "read-only/no-tool allowlist" as the only background tool posture; the framing that write-capable subagents wait on an execute mode raising both parent and child ceilings together. - **D93-L — Operational mode and foreground agent collapse to one op-mode-keyed source of truth; the foreground roster is `elicit` / `execute` / `code`.** A foreground agent and its operational mode are 1:1 (D40-L: the foreground agent is derived from `op_mode`), so the prior **three-record fragmentation** — id enums in `src/session/schema/kinds.ts`, `OPERATIONAL_MODE_DEFINITIONS` + `AGENT_ROLE_DEFINITIONS` + `TOOL_POLICY_DEFINITIONS` in `src/projections/session/runtime-policy.ts`, and `AGENT_PROMPT_DEFINITIONS` in `src/agents/runtime/state.ts` (which duplicated `model`/`thinking`/`allowedStrategies`/`allowedLenses` across two of them) — collapses to a **single op-mode-keyed record**. An operational mode IS `{ foreground AgentManifest (D90-L), tool policy, canDelegate set }`; background agents live in a sibling `AgentManifest` registry, and the per-agent **`canDelegate`** field (D92-L generalized from op_mode-keyed to a manifest field) links a foreground mode to the background agents it may spawn — **code-owned for foreground modes** so the write-safety boundary (I49-L) holds; it also generalizes to background→background nesting. The foreground roster is three modes: **`elicit` → `elicitor`** (current: read-only + graph-writes-via-tools; delegates to read-only gatherers); **`execute` → `orchestrator`** (live initial standup: direct shell/file writes blocked, no delegated workers yet, and one code-owned `orchestrator_stub` grant proves the non-dev runnable path; future worker delegation quarantines writes in spawned high-privilege workers per D92-L); and **`code` → `pi-coder`** (planned direct-coding assistant with direct `read`/`edit`/`write`/`bash`, **augmented to be Brunch-tooling-aware** — it also sees the graph read/capture tools). `execute` and `code` contrast deliberately: the orchestrator does not write directly, while the pi-coder will write directly (and does not delegate by default). `pi-coder` **instantiates** the augment case D58-L anticipated — its `SYSTEM.md` augments Pi's base coding-agent prompt rather than replacing it; whether other foreground roles should instead suppress/replace the base stays open per D58-L. (`code` is provisional naming for the pi-coder mode; rename is a one-token change. `code` remains in `PLANNED_OPERATIONAL_MODE_IDS` until built; `execute` is live.) Depends on: D23-L, D40-L, D58-L, D90-L, D92-L; I49-L. Establishing frontier: `subagent-reconciliation` (model + collapse land here; `execute` stood up here with the stub grant, while `code` remains declarative build-out — see PLAN). Supersedes: the three-record foreground-agent fragmentation as separate sources of truth; `defaultRole`/`allowedRoles` as a flexible many-roles-per-mode model (it is 1:1); the `executor` name for the execute-mode agent (now `orchestrator`). - **D36-L — Spec/session selection is a reusable hierarchical decision model with transport-specific presentations.** Brunch owns a pure spec/session selection model that renders cwd-scoped inventory under the discovered project name without calling the user-created object a “workspace”. In TUI mode, the model may present a fast “continue last session” affordance when `.brunch/workspace.json` points to a valid spec+session; otherwise, or after “other spec/session”, the durable tree is: `create new spec → provide spec name → session created automatically`; `resume existing spec → choose existing spec → create a new session OR resume existing session → choose existing session`. The UI should not list every spec as a top-level action label; “resume existing spec” is the top-level intent, and the spec list is the next screen/scrollable selector. The model returns a product decision (`new spec`, `new session for spec`, `open session`, `continue selected session`, `cancel/quit`) without opening Pi sessions or mutating `.brunch/workspace.json` itself. The `WorkspaceSessionCoordinator` activates that decision and owns all persistence/session-binding effects. TUI startup and in-session paths share branded `pi-tui` components and colocated logo assets under `src/.pi/components/workspace-dialog`; adapters differ only in terminal lifecycle and Pi session-replacement mechanics (`ProcessTerminal`/`TUI.showOverlay` before Pi starts, `ctx.ui.custom(..., { overlay: true })` inside Pi), not in product semantics. RPC/headless transports must not invoke the TUI picker; they expose the same initial-selection requirement and activation decisions as JSON-RPC/product results so CLI JSON-RPC clients can select or create spec/session correctly. Depends on: D11-L, D21-L, D24-L, D33-L. Supersedes: implicit resume of `.brunch/workspace.json` on TUI launch, Pi `/resume`/`/new` as Brunch's product session chooser, one-off startup-only picker implementations, a flat action list that says “workspace” for specs, top-level `resume spec X` labels, and a separate intermediate action chooser for switching. - **D42-L — Session naming is Pi `session_info` presentation metadata, not spec identity.** Brunch-created sessions should be named at creation with neutral workspace-global defaults (`Untitled Session 1`, `Untitled Session 2`, …) so pickers/chrome never show an unnamed Brunch session and unchanged defaults do not collide across specs in the same cwd. These defaults are immediate lifecycle metadata, not LLM-generated summaries and not derived from the selected spec title. Brunch may later use Pi session lifecycle hooks to opportunistically replace a default with a short human-readable name that characterizes what happened in the transcript. The preferred generation trigger is `session_shutdown` for `quit`, `new`, and `resume` replacements because it sees the just-finished transcript and can name it before later picker lists need to distinguish sessions; `session_before_compact` or post-compaction (`session_compact`) may be used to refresh names after major summarization, and a manual/user rename command can force or override naming. The generation call should mirror the model-selection pattern in the local `summarize.ts` extension example: choose a cheap/fast authorized model, extract user/assistant text plus salient tool calls from the current branch, ask for a concise title, and append a Pi `session_info` entry through `SessionManager.appendSessionInfo`. Naming must be best-effort and non-blocking with a tight budget: failures, missing auth, empty transcripts, or shutdown aborts preserve the existing default/user label rather than blocking session replacement or exit. Session display names label sessions in pickers and chrome, but do not affect spec ids, session bindings, graph truth, or replay semantics. Depends on: D6-L, D17-L, D21-L, D35-L. Supersedes: using spec title or session UUID alone as the only durable display label once transcripts have meaningful content, leaving Brunch-created sessions unnamed, spec-local default numbering, or treating generated session names as canonical spec identity. -- **D58-L — Brunch prompt composition is a thin runtime header plus a gated prompt-resource manifest, not eager selection of every objective pack.** The architectural commitment is: composition stays a projection layer, not a behavioral state machine; detailed strategy/lens/method guidance lives in read-on-demand prompt resources rather than eager prompt-pack concatenation; runtime-state-gated availability is Brunch's sealed manifest, not ambient Pi discovery; pinned axes remain visible when role/mode-legal while readiness constrains AUTO menus and gated methods/tools; and the pushed-context slice stays compact, with deeper access governed by D60-L. Current prompt-resource topology, manifest emission, file-owned skill metadata, and ownership split across `agents/prompts/`, `agents/skills/`, `agents/runtime/`, and `.pi/extensions/agent-runtime/` live in [`src/agents/README.md`](src/agents/README.md), [`src/agents/prompts/README.md`](src/agents/prompts/README.md), [`src/agents/skills/README.md`](src/agents/skills/README.md), [`src/agents/runtime/README.md`](src/agents/runtime/README.md), [`src/.pi/README.md`](src/.pi/README.md), [`src/.pi/extensions/README.md`](src/.pi/extensions/README.md), [`src/agents/runtime/compose.ts`](src/agents/runtime/compose.ts), and [`src/agents/runtime/state.ts`](src/agents/runtime/state.ts). **Base-prompt relationship (validated 2026-06-18, slice 1):** the `before_agent_start` handler **appends** Brunch's composed block (now led by the agent `SYSTEM.md` body, then runtime header + manifests) to Pi's base system prompt (`${basePrompt}\n\n${composed}`), so a foreground agent currently *augments* Pi's base coding-agent prompt rather than replacing it. Whether a foreground role's `SYSTEM.md` body should suppress or replace that base is **open** and tied to the future `pi-coder` op-mode (which deliberately augments Pi's coding agent); the `elicitor` augmenting a coding base is a known follow-on question, not a settled choice. Refined by: D93-L (the `code`→`pi-coder` foreground mode instantiates the augment case; the replace option for other roles stays open). Composition is projection, not a behavioral state machine. Depends on: D23-L, D25-L, D39-L, D40-L, D52-L, D59-L, D60-L. Refined by: D85-L (implemented 2026-06-18/19: the manifest drops `` — two axes `strategy` + `lens` — and the `goal` body inlines into the `elicitor` role prompt) and by the 2026-06-22 prompt-skill-topology slice (all prompt resources adopt Agent Skills `SKILL.md` topology; `description` becomes file-owned frontmatter; the emitted wrapper becomes `` with per-skill ``). Supersedes: the flat "base + mode + role + strategy + lens + grade + …" layering; the fixed all-packs concatenation in `compose-brunch-prompt.ts`; "role preset / runtime bundle" as the composition unit; direct Layer-2 eager prompt-pack injection as the default mechanism; treating top-level `src/agents/` as Pi-only rather than Brunch LLM-context ingress; and `capability` as a parallel name for `method`. +- **D58-L — Brunch prompt composition is a thin runtime header plus a gated prompt-resource manifest, not eager selection of every objective pack.** The architectural commitment is: composition stays a projection layer, not a behavioral state machine; detailed strategy/lens/method guidance lives in read-on-demand prompt resources rather than eager prompt-pack concatenation; runtime-state-gated availability is Brunch's sealed manifest, not ambient Pi discovery; pinned axes remain visible when role/mode-legal while readiness constrains AUTO menus and gated methods/tools; and the pushed-context slice stays compact, with deeper access governed by D60-L. Current prompt-resource topology, manifest emission, file-owned skill metadata, seed context composition, and ownership split across `agents/prompts/`, `agents/skills/`, `agents/runtime/`, `agents/contexts/`, and `.pi/extensions/agent-runtime/` live in [`src/agents/README.md`](src/agents/README.md), [`src/agents/prompts/README.md`](src/agents/prompts/README.md), [`src/agents/skills/README.md`](src/agents/skills/README.md), [`src/agents/runtime/README.md`](src/agents/runtime/README.md), [`src/agents/contexts/README.md`](src/agents/contexts/README.md), [`src/.pi/README.md`](src/.pi/README.md), [`src/.pi/extensions/README.md`](src/.pi/extensions/README.md), [`src/agents/runtime/compose.ts`](src/agents/runtime/compose.ts), [`src/agents/runtime/state.ts`](src/agents/runtime/state.ts), and [`src/agents/contexts/seeds/turn-context.ts`](src/agents/contexts/seeds/turn-context.ts). **Base-prompt relationship (validated 2026-06-18, slice 1):** the `before_agent_start` handler **appends** Brunch's composed block (now led by the agent `SYSTEM.md` body, then runtime header + manifests) to Pi's base system prompt (`${basePrompt}\n\n${composed}`), so a foreground agent currently *augments* Pi's base coding-agent prompt rather than replacing it. Whether a foreground role's `SYSTEM.md` body should suppress or replace that base is **open** and tied to the future `pi-coder` op-mode (which deliberately augments Pi's coding agent); the `elicitor` augmenting a coding base is a known follow-on question, not a settled choice. Refined by: D93-L (the `code`→`pi-coder` foreground mode instantiates the augment case; the replace option for other roles stays open). Composition is projection, not a behavioral state machine. Depends on: D23-L, D25-L, D39-L, D40-L, D52-L, D59-L, D60-L. Refined by: D85-L (implemented 2026-06-18/19: the manifest drops `` — two axes `strategy` + `lens` — and the `goal` body inlines into the `elicitor` role prompt) and by the 2026-06-22 prompt-skill-topology slice (all prompt resources adopt Agent Skills `SKILL.md` topology; `description` becomes file-owned frontmatter; the emitted wrapper becomes `` with per-skill ``). Supersedes: the flat "base + mode + role + strategy + lens + grade + …" layering; the fixed all-packs concatenation in `compose-brunch-prompt.ts`; "role preset / runtime bundle" as the composition unit; direct Layer-2 eager prompt-pack injection as the default mechanism; treating top-level `src/agents/` as Pi-only rather than Brunch LLM-context ingress; and `capability` as a parallel name for `method`. #### Continuity & origination (turn-boundary choreography) -- **D76-L — Session continuity state is a projected assistant-visible watermark carried by transcript custom entries, never stored mutable state.** The architectural commitment is that session staleness is defined only by what the assistant has actually been shown, as a `{specId, lsn}` watermark projected from transcript-native continuity carriers; bare LSNs are invalid across sibling specs. `worldUpdate` remains a strict `current_lsn > watermark` reconciliation surface, own assistant-visible mutations must not be re-announced through `worldUpdate`, narrow reads must not advance the global watermark, continuity must persist through Brunch custom transcript entries rather than synthetic `toolCall`s or prompt-only injection, and any process-local cache is optimization only, never product state. Current carrier taxonomy and turn-boundary choreography live in [`src/session/README.md`](src/session/README.md), [`src/projections/README.md`](src/projections/README.md), [`src/projections/session/assistant-visible-watermark.ts`](src/projections/session/assistant-visible-watermark.ts), [`src/projections/session/continuity-entry-classifier.ts`](src/projections/session/continuity-entry-classifier.ts), [`src/session/prepare-next-turn.ts`](src/session/prepare-next-turn.ts), [`src/session/context-seed.ts`](src/session/context-seed.ts), and [`src/.pi/README.md`](src/.pi/README.md). Depends on: D14-L, D17-L, D37-L, D43-L, I1-L, I4-L. Supersedes: a stored/mutable `agent_visible_lsn` / `lastSeenLsn` field; defining the watermark as "the app sampled the graph clock" (which would let staleness vanish before the agent is shown the change); carrying continuity via synthetic tool calls or prompt-only injection. +- **D76-L — Session continuity state is a projected assistant-visible watermark carried by transcript custom entries, never stored mutable state.** The architectural commitment is that session staleness is defined only by what the assistant has actually been shown, as a `{specId, lsn}` watermark projected from transcript-native continuity carriers; bare LSNs are invalid across sibling specs. `worldUpdate` remains a strict `current_lsn > watermark` reconciliation surface, own assistant-visible mutations must not be re-announced through `worldUpdate`, narrow reads must not advance the global watermark, continuity must persist through Brunch custom transcript entries rather than synthetic `toolCall`s or prompt-only injection, and any process-local cache is optimization only, never product state. Current carrier taxonomy and turn-boundary choreography live in [`src/session/README.md`](src/session/README.md), [`src/projections/README.md`](src/projections/README.md), [`src/projections/session/assistant-visible-watermark.ts`](src/projections/session/assistant-visible-watermark.ts), [`src/projections/session/continuity-entry-classifier.ts`](src/projections/session/continuity-entry-classifier.ts), [`src/session/prepare-next-turn.ts`](src/session/prepare-next-turn.ts), [`src/agents/contexts/seeds/origination.ts`](src/agents/contexts/seeds/origination.ts), and [`src/.pi/README.md`](src/.pi/README.md). Depends on: D14-L, D17-L, D37-L, D43-L, I1-L, I4-L. Supersedes: a stored/mutable `agent_visible_lsn` / `lastSeenLsn` field; defining the watermark as "the app sampled the graph clock" (which would let staleness vanish before the agent is shown the change); carrying continuity via synthetic tool calls or prompt-only injection. - **D77-L — Turn-boundary reconciliation is one writer seam plus two auxiliary seams and a guard, not four co-equal insertion points.** The write-side of continuity is owned by a single **pre-assistant-turn reconciler** (canonically `prepareNextTurn`; `before_agent_start` is the temporary adapter until that seam is wired): it computes the projected watermark, samples `current_lsn`, and inserts `worldUpdate` (naming only items with LSN strictly greater than the pre-update watermark, I4-L), mention-staleness hints, side-task/reviewer drains (D15-L), and any boot/resume seed or kick decision (D78-L). Two auxiliary seams write continuity outside that reconciler: **submit-time mention resolution** at user-message ingestion (`session.submitMessage`, D49-L) resolves `#` handles to stable graph ids and appends `brunch.mention` ledger facts — independent of autocomplete freshness, which is advisory UI only; and **tool-result watermark stamping** at the graph read/mutation adapters records the LSN at which a graph fact became assistant-visible — but only the session's own mutations and **whole-spec snapshot reads** (full graph overview) advance the **global** assistant-visible watermark (D76-L), while narrow `getNodes` / `queryNodes` reads update **per-entity read ledgers** (the D14-L mention ledger now; an optional direct-read ledger if later built) and must not touch the global watermark, so a narrow read cannot mask unrelated staleness. `before_provider_request` is a **guard only** (assert no stale unresolved continuity remains), never the normal writer, because writing there risks double-writes against the reconciler; on detecting post-prepare drift (a write landed between `prepareNextTurn` and the provider call) it **re-runs turn preparation once** (abort/retry) rather than patching the transcript itself. The reconciler must run **before prompt composition** so its inserted continuity is visible to the same turn. Depends on: D14-L, D15-L, D17-L, D49-L, D76-L. Supersedes: four co-equal insertion points each owning overlapping continuity writes; tying mention resolution to autocomplete-time state; using `before_provider_request` as a primary continuity writer. -- **D78-L — Session origination ("kick" + context seeding) is honest assistant-origination behind `session.triggerExchange`, gated by transcript-tail policy, never a fabricated user turn.** The architectural commitment is the guardrail: origination is a product seam that seeds context and decides whether the assistant owes a turn, but it must never fabricate a user entry or a deterministic product-authored `present_*` offer. New-session seeding, full-seed payload composition, kick triggering, resume-debt classification, continuity-only-tail ignoring, and the public kick surface live in [`src/session/README.md`](src/session/README.md), [`src/session/context-seed.ts`](src/session/context-seed.ts), [`src/session/originate-assistant-turn.ts`](src/session/originate-assistant-turn.ts), [`src/session/start-assistant-turn.ts`](src/session/start-assistant-turn.ts), [`src/projections/session/continuity-entry-classifier.ts`](src/projections/session/continuity-entry-classifier.ts), [`src/rpc/README.md`](src/rpc/README.md), and [`src/rpc/methods/session.ts`](src/rpc/methods/session.ts). **(Revised 2026-06-12, `origination-native-elicitation`):** the product never fabricates a deterministic `present_*` exchange at origination — the canned offer predates the `elicitation_gaps` mechanism and is superseded by it; its pragmatic ground (new-from-scratch / existing-codebase / relates-to-prior-spec situating) migrates into elicitor prompt guidance. The deterministic exchange generator survives only as probe/dev machinery for the R24 permutation evidence, outside product origination. The "kick unless freestyle" gate maps onto D66-L: because AUTO never selects `freestyle`, AUTO always originates offer-first, and only an explicit `freestyle` pin yields a wait-for-user idle. This is **product behavior on the non-D39-L-seal side**, not a `BRUNCH_DEV` affordance. Context seeding for new specs may draw on the `elicitation_gaps` grounding floor (D75-L) to shape the opening offer, but the seeded overview itself is read context, not graph truth. Depends on: D12-L, D37-L, D49-L, D66-L, D75-L, D76-L; R16. Supersedes: faking a user message to start the agent; treating "originate the first turn" as a dev-harness concern; an unconditional resume-kick that re-asks when the tail already awaits the user; **the product-fabricated deterministic `present_*` offer at origination and its LSN-only seed stamp (2026-06-12: superseded by the elicitation-gaps-grounded assistant-authored opening over a content-rich seed)**. +- **D78-L — Session origination ("kick" + context seeding) is honest assistant-origination behind `session.triggerExchange`, gated by transcript-tail policy, never a fabricated user turn.** The architectural commitment is the guardrail: origination is a product seam that seeds context and decides whether the assistant owes a turn, but it must never fabricate a user entry or a deterministic product-authored `present_*` offer. New-session seeding, full-seed payload composition, kick triggering, resume-debt classification, continuity-only-tail ignoring, and the public kick surface live in [`src/session/README.md`](src/session/README.md), [`src/agents/contexts/seeds/origination.ts`](src/agents/contexts/seeds/origination.ts), [`src/session/originate-assistant-turn.ts`](src/session/originate-assistant-turn.ts), [`src/session/start-assistant-turn.ts`](src/session/start-assistant-turn.ts), [`src/projections/session/continuity-entry-classifier.ts`](src/projections/session/continuity-entry-classifier.ts), [`src/rpc/README.md`](src/rpc/README.md), and [`src/rpc/methods/session.ts`](src/rpc/methods/session.ts). **(Revised 2026-06-12, `origination-native-elicitation`):** the product never fabricates a deterministic `present_*` exchange at origination — the canned offer predates the `elicitation_gaps` mechanism and is superseded by it; its pragmatic ground (new-from-scratch / existing-codebase / relates-to-prior-spec situating) migrates into elicitor prompt guidance. The deterministic exchange generator survives only as probe/dev machinery for the R24 permutation evidence, outside product origination. The "kick unless freestyle" gate maps onto D66-L: because AUTO never selects `freestyle`, AUTO always originates offer-first, and only an explicit `freestyle` pin yields a wait-for-user idle. This is **product behavior on the non-D39-L-seal side**, not a `BRUNCH_DEV` affordance. Context seeding for new specs may draw on the `elicitation_gaps` grounding floor (D75-L) to shape the opening offer, but the seeded overview itself is read context, not graph truth. Depends on: D12-L, D37-L, D49-L, D66-L, D75-L, D76-L; R16. Supersedes: faking a user message to start the agent; treating "originate the first turn" as a dev-harness concern; an unconditional resume-kick that re-asks when the tail already awaits the user; **the product-fabricated deterministic `present_*` offer at origination and its LSN-only seed stamp (2026-06-12: superseded by the elicitation-gaps-grounded assistant-authored opening over a content-rich seed)**. #### Development experience (DX) diff --git a/src/.pi/extensions/agent-runtime/system-prompts/index.ts b/src/.pi/extensions/agent-runtime/system-prompts/index.ts index 3af1ba981..76ef929d5 100644 --- a/src/.pi/extensions/agent-runtime/system-prompts/index.ts +++ b/src/.pi/extensions/agent-runtime/system-prompts/index.ts @@ -2,13 +2,13 @@ import { readFile } from 'node:fs/promises'; import type { ExtensionAPI } from '@earendil-works/pi-coding-agent'; -import { composeAgentPrompt, type AgentPromptContextBundle } from '../../../../agents/runtime/compose.js'; import { composeAgentContextSeed, type AgentPromptSessionContext, type AgentPromptSpecContext, type AgentPromptWorkspaceContext, -} from '../../../../session/agent-context-seed.js'; +} from '../../../../agents/contexts/seeds/turn-context.js'; +import { composeAgentPrompt, type AgentPromptContextBundle } from '../../../../agents/runtime/compose.js'; import type { GraphReaders } from '../../brunch-data/graph/index.js'; import { activeToolNamesForBrunchAgentState, diff --git a/src/.pi/extensions/subagents/prompt-assembly.ts b/src/.pi/extensions/subagents/prompt-assembly.ts index 13f5e06ea..1696ac041 100644 --- a/src/.pi/extensions/subagents/prompt-assembly.ts +++ b/src/.pi/extensions/subagents/prompt-assembly.ts @@ -1,12 +1,12 @@ -import { renderBrunchSkills, type PromptManifests } from '../../../agents/runtime/prompt-skills.js'; -import { LENS_RESOURCES, METHOD_RESOURCES, STRATEGY_RESOURCES } from '../../../agents/runtime/state.js'; -import type { ElicitationGap } from '../../../graph/schema/elicitation-gaps.js'; import type { AgentPromptSpecContext, AgentPromptWorkspaceContext, AgentPromptSessionContext, -} from '../../../session/agent-context-seed.js'; -import { renderWorkspaceSeed } from '../../../session/agent-context-seed.js'; +} from '../../../agents/contexts/seeds/turn-context.js'; +import { renderWorkspaceSeed } from '../../../agents/contexts/seeds/turn-context.js'; +import { renderBrunchSkills, type PromptManifests } from '../../../agents/runtime/prompt-skills.js'; +import { LENS_RESOURCES, METHOD_RESOURCES, STRATEGY_RESOURCES } from '../../../agents/runtime/state.js'; +import type { ElicitationGap } from '../../../graph/schema/elicitation-gaps.js'; import type { SubagentDefinition } from './agents.js'; export interface BackgroundWorldSnapshot { diff --git a/src/README.md b/src/README.md index 823ec77d9..e367c84bc 100644 --- a/src/README.md +++ b/src/README.md @@ -1,6 +1,6 @@ # src/ — Brunch source topology -Decision D52-L in `memory/SPEC.md` locks the target layout. The current LLM-context ingress refactor introduces `agents/` as the Pi-independent owner for Brunch-authored agent context; agent prompt bodies and prompt-resource skills now live there. +Decision D52-L in `memory/SPEC.md` locks the target layout. The current LLM-context ingress refactor introduces `agents/` as the Pi-independent owner for Brunch-authored agent context; agent prompt bodies, prompt-resource skills, prompt runtime policy, and context seed composition now live there. ```text src/ @@ -11,7 +11,8 @@ src/ ├── agents/ Pi-independent owner for Brunch-authored LLM context ingress │ ├── prompts/ agent role body markdown resources │ ├── skills/ prompt-resource markdown resources -│ └── runtime/ prompt composition and prompt-resource/tool legality +│ ├── runtime/ prompt composition and prompt-resource/tool legality +│ └── contexts/ agent-visible seed/context text │ ├── .pi/ Sealed Pi-harness runtime surface │ ├── components/ reusable Pi TUI/message components @@ -46,7 +47,7 @@ rules: workspace/ -> constants/ or workspace-local files only projections/* -> agents/, graph/, session/, workspace/ [read/domain imports allowed; agents/ is temporary registry edge] renderers/* -> projections/, graph/, session/, workspace/ as needed for input types - agents/ -> constants/ [prompt/skill markdown + registry only] + agents/ -> graph/, session/, renderers/ [agent-visible text over already-read facts] .pi/ -> agents/, graph/, session/, projections/, renderers/ [Pi runtime adapters/resources] rpc/ -> graph/, session/, projections/, renderers/ app/ -> graph/, session/, projections/, renderers/ @@ -60,7 +61,7 @@ Rules: - `workspace/` owns cwd-scoped identity, inventory, and workspace default-state persistence. It must not import Pi, session, graph, DB, projection, renderer, adapter, transport, app, or web modules. - `graph/` imports from `db/`. No other layer imports `db/` directly. -- `agents/` owns the Brunch-authored LLM-context ingress seam. Today it hosts agent prompt bodies, prompt-resource skills, prompt composition, prompt-resource/tool legality, and their central file registry; later slices move seeds and agent-visible rendering under this seam. The current `projections/session/runtime-policy.ts` import of this registry is a migration edge only: once the foreground roster moves under `agents/runtime/`, projections should stop depending on `agents/`. +- `agents/` owns the Brunch-authored LLM-context ingress seam. Today it hosts agent prompt bodies, prompt-resource skills, prompt composition, prompt-resource/tool legality, context seed composition, and the central file registry; later slices move reusable agent-visible rendering under this seam. The current `projections/session/runtime-policy.ts` import of this registry is a migration edge only: once the foreground roster moves under `agents/runtime/`, projections should stop depending on `agents/`. - `.pi/` owns Pi-harness extensions/components and no longer hosts Brunch-authored prompt bodies or prompt-resource skills. - `.pi/extensions/` registers Pi tools/hooks/UI affordances and delegates product semantics outward. - `projections/` owns reusable structured output; `renderers/` owns reusable lossy text output. diff --git a/src/agents/README.md b/src/agents/README.md index 9c6a3b140..b935cde6c 100644 --- a/src/agents/README.md +++ b/src/agents/README.md @@ -4,7 +4,7 @@ SPEC decisions: D39-L, D40-L, D52-L, D60-L, D85-L, D90-L, D91-L, D93-L ## Owns -`src/agents/` is the Pi-independent home for Brunch-authored model-facing context. It now owns bundled agent prompt bodies, Brunch prompt-resource skills, and the central registry for their paths. +`src/agents/` is the Pi-independent home for Brunch-authored model-facing context. It now owns bundled agent prompt bodies, Brunch prompt-resource skills, prompt composition/runtime legality, seed context composition, and the central registry for prompt/skill paths. ```text agents/ @@ -12,6 +12,7 @@ agents/ ├── prompts/ bundled foreground/background agent body markdown ├── skills/ strategy/lens/method prompt-resource markdown ├── runtime/ prompt composition and prompt-resource/tool legality +├── contexts/ agent-visible context text, currently seed composition ├── registry.ts path registry for bundled agent bodies and prompt-resource skills └── __tests__/ registry/topology tests ``` @@ -22,11 +23,13 @@ agents/ rules: agents/registry.ts -> agents/prompts/*/SYSTEM.md [body file locations] agents/registry.ts -> agents/skills/*/*/SKILL.md [prompt-resource locations] - .pi/extensions/* -> agents/registry.ts [adapters ask for Brunch-authored context locations] + agents/contexts/ -> graph/, session/, renderers/ [agent-visible text over already-read facts] + .pi/extensions/* -> agents/ [adapters ask for Brunch-authored context] + session/ -> agents/contexts/seeds/ [origination asks for seed payload text] projections/session/runtime-policy.ts -> agents/registry.ts [temporary roster-location edge] - agents/ x> Pi extension hooks [no registration side effects] + agents/ x> Pi extension hooks [no registration side effects] ``` ## Migration note -This directory is intentionally mid-migration. Agent prompt bodies, prompt-resource skills, prompt composition, and prompt-resource/tool legality have moved here byte-stably. Later slices move seed context and agent-visible renderers here; Pi extensions remain runtime adapters that register hooks/tools and call this layer. +This directory is intentionally mid-migration. Agent prompt bodies, prompt-resource skills, prompt composition, prompt-resource/tool legality, and context seed composition have moved here byte-stably. Later slices move reusable agent-visible renderers here; Pi extensions remain runtime adapters that register hooks/tools and call this layer. diff --git a/src/agents/contexts/README.md b/src/agents/contexts/README.md new file mode 100644 index 000000000..02216c0c5 --- /dev/null +++ b/src/agents/contexts/README.md @@ -0,0 +1,26 @@ +# agents/contexts/ — agent-visible context text + +SPEC decisions: D52-L, D58-L, D60-L, D76-L, D78-L, D91-L + +## Owns + +`src/agents/contexts/` owns Brunch-authored text that is placed into an agent's model context outside the static prompt body/resource system. + +```text +contexts/ +└── seeds/ per-turn pushed context blocks and origination seed payloads +``` + +## Boundary rules + +```pseudo +rules: + agents/contexts/ -> graph/, session/, renderers/ [render already-read facts as agent-visible text] + .pi/extensions/* -> agents/contexts/ [adapters gather data, then ask for text] + session/ -> agents/contexts/ [origination choreography asks for seed payload text] + agents/contexts/ x> .pi/ [no Pi hook/tool registration] +``` + +## Migration note + +Only seed context composition has moved here so far. Later refactor items move reusable agent-visible renderers and adapter-local tool-result text into this subtree. diff --git a/src/agents/contexts/seeds/README.md b/src/agents/contexts/seeds/README.md new file mode 100644 index 000000000..32161ad93 --- /dev/null +++ b/src/agents/contexts/seeds/README.md @@ -0,0 +1,22 @@ +# agents/contexts/seeds/ — context seeds + +SPEC decisions: D58-L, D76-L, D78-L, D91-L + +## Owns + +Seed text that Brunch deliberately inserts into model context: + +- `turn-context.ts` composes compact per-turn pushed context blocks for prompt assembly and background subagent world snapshots. +- `origination.ts` composes the provider-visible `brunch.context_seed` payload used when a session is kicked or resumed. + +Both modules are pure over already-read data. Callers own PULL: graph reads, gap reads, workspace inspection, transcript-tail classification, and Pi/session side effects. + +## Boundary rules + +```pseudo +rules: + seeds/* -> graph/, renderers/, session/schema [format already-read facts] + .pi/extensions/ -> seeds/ [foreground/background prompt adapters] + session/ -> seeds/origination.ts [append choreography uses seed text] + seeds/ x> .pi/, app/, rpc/ [no host, adapter, or transport effects] +``` diff --git a/src/session/__tests__/context-seed.test.ts b/src/agents/contexts/seeds/__tests__/origination.test.ts similarity index 96% rename from src/session/__tests__/context-seed.test.ts rename to src/agents/contexts/seeds/__tests__/origination.test.ts index b0dd07f57..e83d9cfc9 100644 --- a/src/session/__tests__/context-seed.test.ts +++ b/src/agents/contexts/seeds/__tests__/origination.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest'; -import type { ElicitationGap } from '../../graph/schema/elicitation-gaps.js'; -import { composeContextSeedContent } from '../context-seed.js'; +import type { ElicitationGap } from '../../../../graph/schema/elicitation-gaps.js'; +import { composeContextSeedContent } from '../origination.js'; const specId = 7; diff --git a/src/session/__tests__/agent-context-seed.test.ts b/src/agents/contexts/seeds/__tests__/turn-context.test.ts similarity index 96% rename from src/session/__tests__/agent-context-seed.test.ts rename to src/agents/contexts/seeds/__tests__/turn-context.test.ts index b066739b3..255a51f7c 100644 --- a/src/session/__tests__/agent-context-seed.test.ts +++ b/src/agents/contexts/seeds/__tests__/turn-context.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it } from 'vitest'; -import type { GraphSlice } from '../../graph/queries.js'; -import { presenceGap } from '../../graph/schema/elicitation-gap-fixtures.js'; -import { composeAgentContextSeed, renderGraphSeed, renderWorkspaceSeed } from '../agent-context-seed.js'; +import type { GraphSlice } from '../../../../graph/queries.js'; +import { presenceGap } from '../../../../graph/schema/elicitation-gap-fixtures.js'; +import { composeAgentContextSeed, renderGraphSeed, renderWorkspaceSeed } from '../turn-context.js'; describe('renderWorkspaceSeed', () => { it('renders selected-spec/session/posture facts without ambient resource discovery', () => { diff --git a/src/session/context-seed.ts b/src/agents/contexts/seeds/origination.ts similarity index 89% rename from src/session/context-seed.ts rename to src/agents/contexts/seeds/origination.ts index 0375391c0..23f86273f 100644 --- a/src/session/context-seed.ts +++ b/src/agents/contexts/seeds/origination.ts @@ -16,10 +16,10 @@ * Used by: brunch-tui boot seeding, session.triggerExchange RPC origination */ -import { sortElicitationGapsForAsking } from '../graph/elicitation-driver.js'; -import type { GraphSlice } from '../graph/index.js'; -import type { ElicitationGap } from '../graph/schema/elicitation-gaps.js'; -import { formatGraphOverview } from '../renderers/graph/graph-slice.js'; +import { sortElicitationGapsForAsking } from '../../../graph/elicitation-driver.js'; +import type { GraphSlice } from '../../../graph/index.js'; +import type { ElicitationGap } from '../../../graph/schema/elicitation-gaps.js'; +import { formatGraphOverview } from '../../../renderers/graph/graph-slice.js'; const TOP_GAP_COUNT = 5; diff --git a/src/session/agent-context-seed.ts b/src/agents/contexts/seeds/turn-context.ts similarity index 91% rename from src/session/agent-context-seed.ts rename to src/agents/contexts/seeds/turn-context.ts index f2b8ba326..d90d355bd 100644 --- a/src/session/agent-context-seed.ts +++ b/src/agents/contexts/seeds/turn-context.ts @@ -1,6 +1,6 @@ /** - * Agent context-seed composition — a session-context concern, not a - * system-prompt concern. + * Agent context-seed composition — an agent-context concern, not a + * system-prompt or Pi-adapter concern. * * Owns the per-turn pushed context blocks the agent receives each turn: the * selected-workspace seed and the selected-spec graph seed. This is session/ @@ -9,7 +9,7 @@ * into the prompt frame. Keeping composition here means cycling operational * modes — which swaps the agent role and therefore the system prompt — does not * re-own context derivation: the prompt layer consumes a bundle it does not - * compose. Mirrors `context-seed.ts` (origination continuity entry); this is + * compose. Mirrors `origination.ts` (continuity seed entry); this is * its ephemeral per-turn sibling. * * Input: selected spec/workspace/session + gaps + already-read graph slice + lens @@ -17,12 +17,12 @@ * Used by: `.pi/extensions/agent-runtime/system-prompts` (before_agent_start) via composeAgentContextSeed */ -import type { GraphSlice } from '../graph/queries.js'; -import type { ElicitationGap } from '../graph/schema/elicitation-gaps.js'; -import { formatGraphNodeCode, type GraphNode } from '../graph/schema/nodes.js'; -import { renderSoftReadinessEstimate } from '../renderers/session/readiness-estimate.js'; -import type { AgentLensSelection } from './schema/kinds.js'; -import type { WorkspacePostureState } from './workspace-session-coordinator.js'; +import type { GraphSlice } from '../../../graph/queries.js'; +import type { ElicitationGap } from '../../../graph/schema/elicitation-gaps.js'; +import { formatGraphNodeCode, type GraphNode } from '../../../graph/schema/nodes.js'; +import { renderSoftReadinessEstimate } from '../../../renderers/session/readiness-estimate.js'; +import type { AgentLensSelection } from '../../../session/schema/kinds.js'; +import type { WorkspacePostureState } from '../../../session/workspace-session-coordinator.js'; export interface AgentPromptSpecContext { id: number; diff --git a/src/agents/runtime/compose.ts b/src/agents/runtime/compose.ts index e674a0e8c..0382c4151 100644 --- a/src/agents/runtime/compose.ts +++ b/src/agents/runtime/compose.ts @@ -2,10 +2,7 @@ import { selectElicitationGap } from '../../graph/elicitation-driver.js'; import type { ElicitationGap } from '../../graph/schema/elicitation-gaps.js'; import type { ResolvedBrunchAgentState } from '../../projections/session/runtime-state.js'; import { renderSoftReadinessEstimate } from '../../renderers/session/readiness-estimate.js'; -import type { - AgentPromptSpecContext, - AgentPromptWorkspaceContext, -} from '../../session/agent-context-seed.js'; +import type { AgentPromptSpecContext, AgentPromptWorkspaceContext } from '../contexts/seeds/turn-context.js'; import { renderBrunchSkills, type PromptManifests } from './prompt-skills.js'; import { manifestsForState } from './state.js'; diff --git a/src/app/pi-subagents.ts b/src/app/pi-subagents.ts index 0bc392eb9..29147d364 100644 --- a/src/app/pi-subagents.ts +++ b/src/app/pi-subagents.ts @@ -16,7 +16,7 @@ import type { AgentPromptSessionContext, AgentPromptSpecContext, AgentPromptWorkspaceContext, -} from '../session/agent-context-seed.js'; +} from '../agents/contexts/seeds/turn-context.js'; import { brunchResourceLoaderOptions, createBrunchSettingsManager } from './pi-settings.js'; export interface LoadBrunchSubagentsOptions { diff --git a/src/renderers/README.md b/src/renderers/README.md index 80f193024..3c242f878 100644 --- a/src/renderers/README.md +++ b/src/renderers/README.md @@ -54,7 +54,7 @@ Ledger statuses: | `workspace/workspace-state.ts` | print-mode `workspace.state` | ◐ partial | n/a | Print-mode state text | Remaining: decide the `brunch print` house-style/status fork, then add preview/golden if it stays durable. | | `workspace/workspace-context.ts` | `read_workspace_context`; origination context seed workspace section | ✓ locked | `read_workspace_context` text; `brunch.context_seed` workspace section | Tool result markdown | `workspace/__tests__/workspace-context.test.ts` + `workspace/__previews__/*`; invariants for `` wrapper, no sessions, table specs, fenced topology, and no ATX headings. | | `specification/specification-context.ts` | `read_specification_context` | ✓ locked | `read_specification_context` text | Tool result markdown | `specification/__tests__/specification-context.test.ts` + `specification/__previews__/specification-context.md`; embeds the shared G-D graph overview and locks `` wrapper, Overview → Graph → Gaps → Sessions order, spec-scoped sessions, TOON gaps, and no ATX headings. | -| `session/readiness-estimate.ts` | Shared soft-readiness line for seed + specification context | ✓ locked | `brunch.context_seed`; `read_specification_context` overview line | Tool result markdown where embedded | `agent-context-seed.test.ts` + `specification-context.test.ts`; parity invariant keeps seed/specification readiness over the same full gap register. | +| `session/readiness-estimate.ts` | Shared soft-readiness line for seed + specification context | ✓ locked | per-turn agent context seed; `read_specification_context` overview line | Tool result markdown where embedded | `turn-context.test.ts` + `specification-context.test.ts`; parity invariant keeps seed/specification readiness over the same full gap register. | | `session/runtime-frame.ts` | `read_session_context` | ✓ locked | `read_session_context` text | Tool result markdown | `session/__tests__/runtime-frame.test.ts` + `session/__previews__/runtime-frame-ready.md`; invariant for projected handles. | | `session/transcript.ts` | Brunch-semantic transcript rendering | ◐ partial | Probe/report transcript markdown | Transcript/report text, not Pi live display | Remaining: migrate `` transcript shape into the renderer home when the session context render is scoped. | | `exchanges/request-answer.ts` | `request_answer` | ◐ partial | Request result text | Tool result `renderResult` via exchange markdown adapter | Remaining: request-family goldens for answered/non-answered branches; invariants for cancel/unavailable copy and comments. | @@ -89,4 +89,4 @@ Provider-visible strings composed outside `src/renderers/` carry the same drift | Mention-staleness hints | `src/session/mention-ledger.ts` / turn-boundary reconciler | ○ review-only | No renderer row until copy changes or drift appears. | | Session lifecycle notices | `src/.pi/extensions/session-hooks/session/lifecycle.ts` | ○ review-only | Keep owner-local unless promoted by a wording bug. | | Compaction copy / anchor rationale | `src/.pi/extensions/compaction/index.ts` | ○ review-only | Contract prose, not a renderer row. | -| Seed framing | `src/session/context-seed.ts` | ◐ partial | Covered indirectly through origination/context tests today; promote only if wording churn continues. | +| Seed framing | `src/agents/contexts/seeds/origination.ts` | ◐ partial | Covered through colocated origination seed tests today; promote only if wording churn continues. | diff --git a/src/session/README.md b/src/session/README.md index 345db1252..dd5e9f4db 100644 --- a/src/session/README.md +++ b/src/session/README.md @@ -68,10 +68,10 @@ plus the coordination logic for workspace/spec/session lifecycle. `prepare-next-turn.ts` owns the single pre-turn continuity writer; Pi lifecycle hooks adapt it through `.pi/extensions/session-hooks/session/lifecycle.ts`, and `before_provider_request` is a guard-only check. `start-assistant-turn.ts` - owns the origination decision and context seed entries; `context-seed.ts` - composes the seed's provider-visible payload (spec overview + top-ranked - open gaps) from spec-scoped reads; `originate-assistant-turn.ts` is the one - seed choreography every entry point (TUI boot, `session.triggerExchange`) + owns the origination decision and context seed entries; + `agents/contexts/seeds/origination.ts` composes the seed's provider-visible + payload (spec overview + top-ranked open gaps) from spec-scoped reads; + `originate-assistant-turn.ts` is the one seed choreography every entry point (TUI boot, `session.triggerExchange`) delegates to — origin derives from conversational-message presence in the projected transcript, never entry counts (I46-L). Origination only *decides and seeds* — it fabricates **no** `present_*` exchange (D78-L revised @@ -139,12 +139,12 @@ schema, and the product-state-gated rows must stay explicit deferred tripwires. - Cwd project identity, pure cwd inventory, and `.brunch/workspace.json` persistence — those live in `workspace/`. - Graph state, CommandExecutor, graph queries — those live in `graph/`. -- Prompt composition, pushed seed context building — those live in `.pi/extensions/agent-runtime/system-prompts/` (manifest/legality policy in `.pi/extensions/agent-runtime/runtime/`). +- Prompt composition and pushed seed context building — those live in `agents/runtime/` and `agents/contexts/seeds/`, adapted by `.pi/extensions/agent-runtime/system-prompts/`. - Pi extension registration — those live in `.pi/extensions/`. ## Imported by -- `.pi/extensions/agent-runtime/system-prompts/seed/` — for workspace/graph pushed-context reads. +- `agents/contexts/seeds/` — for agent-visible per-turn and origination seed text. - `.pi/extensions/brunch-data/context/` — for direct workspace overview reads; pure cwd inventory comes from `workspace/`. - `projections/session/` — for reusable transcript-context DTO projection. - `projections/workspace/` — for reusable workspace-state DTO projection. diff --git a/src/session/originate-assistant-turn.ts b/src/session/originate-assistant-turn.ts index 396df9e30..ea44fcc55 100644 --- a/src/session/originate-assistant-turn.ts +++ b/src/session/originate-assistant-turn.ts @@ -17,10 +17,10 @@ * seed. */ +import { composeContextSeedContent } from '../agents/contexts/seeds/origination.js'; import type { GraphSlice } from '../graph/index.js'; import type { ElicitationGap } from '../graph/schema/elicitation-gaps.js'; import type { TranscriptEntryLike } from '../projections/session/continuity-entry-classifier.js'; -import { composeContextSeedContent } from './context-seed.js'; import { appendPreparedContinuityEntry, type ContinuityEntryAppender } from './prepare-next-turn.js'; import { startAssistantTurn, type StartAssistantTurnDecision } from './start-assistant-turn.js'; From 43d929a294eeffce75154984e4ed658ff0dc1e98 Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Thu, 25 Jun 2026 18:49:21 +0200 Subject: [PATCH 15/54] Move agent-visible renderers under agents --- memory/PLAN.md | 4 +- memory/REFACTOR.md | 2 +- memory/SPEC.md | 18 ++-- package.json | 6 +- src/.pi/__tests__/graph-tools.test.ts | 2 +- .../extensions/brunch-data/context/get-cwd.ts | 2 +- .../brunch-data/context/get-specification.ts | 2 +- .../extensions/brunch-data/context/index.ts | 8 +- src/.pi/extensions/brunch-data/graph/index.ts | 4 +- .../exchanges/present-candidates.ts | 2 +- .../extensions/exchanges/present-question.ts | 2 +- .../exchanges/present-review-set.ts | 2 +- .../exchanges/shared/answer-source.ts | 2 +- .../exchanges/shared/choice-source.ts | 2 +- .../exchanges/shared/choices-editor.ts | 2 +- .../exchanges/shared/review-source.ts | 2 +- src/README.md | 10 +- src/agents/README.md | 6 +- src/agents/contexts/README.md | 27 +++-- src/agents/contexts/exchanges/README.md | 5 + .../__tests__/present-candidates.test.ts | 2 +- .../contexts}/exchanges/present-candidates.ts | 2 +- .../contexts}/exchanges/present-question.ts | 2 +- .../contexts}/exchanges/present-review-set.ts | 4 +- .../contexts}/exchanges/request-answer.ts | 2 +- .../contexts}/exchanges/request-choice.ts | 2 +- .../contexts}/exchanges/request-choices.ts | 2 +- .../contexts}/exchanges/request-review.ts | 2 +- src/agents/contexts/graph/README.md | 5 + .../graph-overview-kind-band-spread.md | 0 .../neighborhood-brunch-self-MOD1-hops2.md | 0 .../neighborhood-brunch-self-REQ1.md | 0 .../neighborhood-code-health-REQ1.md | 0 .../neighborhood-hub-REQ1-compact.md | 0 .../neighborhood-hub-REQ1-hops2.md | 0 .../__snapshots__}/neighborhood-hub-REQ1.md | 0 .../graph/__tests__/graph-slice.test.ts | 6 +- .../graph/__tests__/node-neighborhood.test.ts | 16 +-- .../contexts}/graph/commit-result.ts | 0 .../contexts}/graph/graph-slice.ts | 14 +-- .../contexts}/graph/node-neighborhood.ts | 10 +- .../contexts}/graph/reconciliation-needs.ts | 0 src/agents/contexts/primitives/README.md | 3 + .../primitives}/__tests__/markdown.test.ts | 0 .../primitives}/__tests__/section.test.ts | 0 .../primitives}/__tests__/toon.test.ts | 0 .../primitives}/__tests__/tree.test.ts | 0 .../contexts/primitives}/markdown.ts | 0 .../contexts/primitives}/section.ts | 0 .../contexts/primitives}/toon.ts | 0 .../contexts/primitives}/tree.ts | 0 src/agents/contexts/seeds/origination.ts | 2 +- src/agents/contexts/seeds/turn-context.ts | 2 +- src/agents/contexts/session/README.md | 5 + .../__snapshots__}/runtime-frame-ready.md | 0 .../session/__tests__/runtime-frame.test.ts | 4 +- .../contexts}/session/readiness-estimate.ts | 6 +- .../contexts}/session/runtime-frame.ts | 2 +- src/agents/contexts/specification/README.md | 5 + .../__snapshots__}/specification-context.md | 0 .../__tests__/specification-context.test.ts | 14 +-- .../specification/specification-context.ts | 10 +- src/agents/contexts/workspace/README.md | 5 + .../__snapshots__}/workspace-cwd-context.md | 0 .../workspace-overview-context.md | 0 .../__tests__/workspace-context.test.ts | 8 +- .../contexts}/workspace/workspace-context.ts | 12 +-- src/agents/runtime/compose.ts | 2 +- src/graph/README.md | 2 +- src/graph/__tests__/command-executor.test.ts | 2 +- src/probes/deterministic-exchange-script.ts | 2 +- src/projections/README.md | 2 +- src/projections/graph/commit-result.ts | 2 +- src/projections/graph/overview.ts | 2 +- src/projections/graph/reconciliation-needs.ts | 2 +- src/renderers/README.md | 99 ++++--------------- src/session/README.md | 6 +- src/session/specification-overview-context.ts | 2 +- src/session/workspace-overview-context.ts | 2 +- 79 files changed, 181 insertions(+), 201 deletions(-) create mode 100644 src/agents/contexts/exchanges/README.md rename src/{renderers => agents/contexts}/exchanges/__tests__/present-candidates.test.ts (96%) rename src/{renderers => agents/contexts}/exchanges/present-candidates.ts (89%) rename src/{renderers => agents/contexts}/exchanges/present-question.ts (84%) rename src/{renderers => agents/contexts}/exchanges/present-review-set.ts (86%) rename src/{renderers => agents/contexts}/exchanges/request-answer.ts (77%) rename src/{renderers => agents/contexts}/exchanges/request-choice.ts (83%) rename src/{renderers => agents/contexts}/exchanges/request-choices.ts (87%) rename src/{renderers => agents/contexts}/exchanges/request-review.ts (87%) create mode 100644 src/agents/contexts/graph/README.md rename src/{renderers/graph/__previews__ => agents/contexts/graph/__snapshots__}/graph-overview-kind-band-spread.md (100%) rename src/{renderers/graph/__previews__ => agents/contexts/graph/__snapshots__}/neighborhood-brunch-self-MOD1-hops2.md (100%) rename src/{renderers/graph/__previews__ => agents/contexts/graph/__snapshots__}/neighborhood-brunch-self-REQ1.md (100%) rename src/{renderers/graph/__previews__ => agents/contexts/graph/__snapshots__}/neighborhood-code-health-REQ1.md (100%) rename src/{renderers/graph/__previews__ => agents/contexts/graph/__snapshots__}/neighborhood-hub-REQ1-compact.md (100%) rename src/{renderers/graph/__previews__ => agents/contexts/graph/__snapshots__}/neighborhood-hub-REQ1-hops2.md (100%) rename src/{renderers/graph/__previews__ => agents/contexts/graph/__snapshots__}/neighborhood-hub-REQ1.md (100%) rename src/{renderers => agents/contexts}/graph/__tests__/graph-slice.test.ts (94%) rename src/{renderers => agents/contexts}/graph/__tests__/node-neighborhood.test.ts (81%) rename src/{renderers => agents/contexts}/graph/commit-result.ts (100%) rename src/{renderers => agents/contexts}/graph/graph-slice.ts (93%) rename src/{renderers => agents/contexts}/graph/node-neighborhood.ts (93%) rename src/{renderers => agents/contexts}/graph/reconciliation-needs.ts (100%) create mode 100644 src/agents/contexts/primitives/README.md rename src/{renderers => agents/contexts/primitives}/__tests__/markdown.test.ts (100%) rename src/{renderers => agents/contexts/primitives}/__tests__/section.test.ts (100%) rename src/{renderers => agents/contexts/primitives}/__tests__/toon.test.ts (100%) rename src/{renderers => agents/contexts/primitives}/__tests__/tree.test.ts (100%) rename src/{renderers => agents/contexts/primitives}/markdown.ts (100%) rename src/{renderers => agents/contexts/primitives}/section.ts (100%) rename src/{renderers => agents/contexts/primitives}/toon.ts (100%) rename src/{renderers => agents/contexts/primitives}/tree.ts (100%) create mode 100644 src/agents/contexts/session/README.md rename src/{renderers/session/__previews__ => agents/contexts/session/__snapshots__}/runtime-frame-ready.md (100%) rename src/{renderers => agents/contexts}/session/__tests__/runtime-frame.test.ts (88%) rename src/{renderers => agents/contexts}/session/readiness-estimate.ts (56%) rename src/{renderers => agents/contexts}/session/runtime-frame.ts (96%) create mode 100644 src/agents/contexts/specification/README.md rename src/{renderers/specification/__previews__ => agents/contexts/specification/__snapshots__}/specification-context.md (100%) rename src/{renderers => agents/contexts}/specification/__tests__/specification-context.test.ts (86%) rename src/{renderers => agents/contexts}/specification/specification-context.ts (84%) create mode 100644 src/agents/contexts/workspace/README.md rename src/{renderers/workspace/__previews__ => agents/contexts/workspace/__snapshots__}/workspace-cwd-context.md (100%) rename src/{renderers/workspace/__previews__ => agents/contexts/workspace/__snapshots__}/workspace-overview-context.md (100%) rename src/{renderers => agents/contexts}/workspace/__tests__/workspace-context.test.ts (86%) rename src/{renderers => agents/contexts}/workspace/workspace-context.ts (80%) diff --git a/memory/PLAN.md b/memory/PLAN.md index 23f352c44..b9a19d752 100644 --- a/memory/PLAN.md +++ b/memory/PLAN.md @@ -175,8 +175,8 @@ context-pipeline/ - **Branch:** tbd - **Kind:** refactor / earned cleanup - **Status:** next candidate, not capability-blocking. -- **Objective:** Confirm each retained `projections/exchanges` and `renderers/exchanges` file earns its place; delete symmetry regrowth where single-owner reads were mirrored into shared layers only for shape symmetry. -- **Acceptance:** Retained files have named multi-consumer/shared-semantics justification; unjustified mirrors are deleted; TUI presenters stay local and renderers stay durable markdown/text/TOON only. +- **Objective:** Confirm each retained `projections/exchanges` and `agents/contexts/exchanges` file earns its place; delete symmetry regrowth where single-owner reads were mirrored into shared layers only for shape symmetry. +- **Acceptance:** Retained files have named multi-consumer/shared-semantics justification; unjustified mirrors are deleted; TUI presenters stay local and exchange context renderers stay durable markdown/text/TOON only. - **Traceability:** D27-L, D65-L, D66-L. ## Dependencies diff --git a/memory/REFACTOR.md b/memory/REFACTOR.md index 0b4d410d1..3e73ba13e 100644 --- a/memory/REFACTOR.md +++ b/memory/REFACTOR.md @@ -68,7 +68,7 @@ callers after refactor 3. ✓ Move Brunch prompt-resource skills into the new skills home and update manifest loading, build asset copying, resource-location snapshots, and skill topology docs without changing skill bytes. 4. ✓ Move foreground prompt composition and prompt-resource legality code into the new runtime home; leave Pi extension code as a thin hook adapter that imports the central composer. 5. ✓ Move per-turn pushed context composition and origination/session-entry seed composition into the new context seed home; update session and app callers to import from the central agent context layer. -6. Move reusable LLM-facing context renderers into the new context home, keeping product-only/human-only renderers outside unless they are deliberately agent-visible. +6. ✓ Move reusable LLM-facing context renderers into the new context home, keeping product-only/human-only renderers outside unless they are deliberately agent-visible. 7. Promote adapter-local LLM text formatting into the new context home: graph mutation result text, related-node text, elicitation agenda/update text, and reconciliation agenda/update text. 8. Consolidate golden/preview tests for prompt composition and agent context renderers under the new agent tree, preserving existing semantic invariants while making “what enters the model” reviewable in one place. 9. Add a boundary guard that prevents Pi extension adapters from owning Brunch-authored tool/session/prompt content text, while explicitly allowing tool schemas, labels, descriptions, and prompt snippets to remain adapter-owned. diff --git a/memory/SPEC.md b/memory/SPEC.md index 0233d1fd6..9b0d840eb 100644 --- a/memory/SPEC.md +++ b/memory/SPEC.md @@ -298,18 +298,18 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c - **D80-L — Generalized capture is the elicitor's banded capture sweep: in-turn, synchronous, over the un-swept transcript tail.** Capture is conduct of the foreground elicitor, not product wiring: there is no observer/auditor queue on the primary path (D18-L, reaffirmed — the v1 observer failed on structure-dependence and context starvation), no product-side LLM extraction pass on the submit paths, no gateway/translation/judgment layer between the agent and graph truth, and no capture subagent in the current block. The **banded capture sweep** is one band-ordered pass that walks intent-kind groups (the same typology the `elicitation_gaps` register references via `refersTo: NodeKind`, D65-L/D75-L), committing through the real role-named `mutateGraph` grammar (D53-L/A14-L) and moving gap dispositions through `update_elicitation_gaps`. Its input window is the **un-swept transcript tail** — all conversational and digest content since the last sweep, tracked by a **sweep watermark** (prior art: the I45-L assistant-visible watermark and the own-mutation stamp) — so capture is robust to RPC-submitted messages, interruptions, and multi-message bursts, and probes get a crisp invariant: after any elicitor turn, nothing conversational remains behind the watermark. Default is a single pass; bulk material (pastes, document reads, exploration digests) may engage an **escalation valve** — chunked/iterated sweeping within the same turn — without changing window or watermark semantics. Choreography is **capture-then-ask**: the sweep commits facts and moves gaps *before* the elicitor composes its next question, so the question provably benefits from what was just captured. Consequence: the deterministic labeled-prefix capture core (`graph/capture/structured-response.ts`), its `session.submitMessage`/`session.submitExchangeResponse` wiring, and the `capture-response-to-graph` proof are retired fossils — capture becomes turn-coupled (same agent for RPC transport clients, different moment; no coverage loss). Depends on: A14-L, A22-L, D18-L, D49-L, D53-L, D63-L, D65-L, D66-L; I45-L. Supersedes: submit-time product-side capture (the D66-L "exactly as the structured-response capture tracer does" wiring), the labeled-prefix extraction core, and the capture-quality spike's product-side extraction-pass shape. - **D81-L — Capture commitment gradient: confidence, not directness; low-confidence noticings spawn elicitation gaps.** What the sweep commits is governed by confidence in grounding, not by whether the user uttered the exact words: directly-stated facts commit with `basis: explicit`; confidently-materialized items — including implied edges and structure soundly inferred from stated content — commit with `basis: implicit`, which D63-L already licenses (agent-materialized-from-user-input); low-confidence **noticings** are never committed — the sweep's prompt directs the elicitor to spawn an `elicitation_gap` instead (`basis: implicit`, rationale citing the noticing), so the false-commit guard's positive output *is* the capture-reflection behavior: one prompted discipline discharges both the guard and the gap-writeback obligation, the agenda durably carries what was noticed, and the anti-shadowing line (D65-L) holds because the gap carries question/rationale, never domain content as truth. There is **no structural gate**: the guard is commitment rules in the sweep prompting plus the false-commit scenario matrix re-aimed at the low-confidence line and run at probe tier (some spike implication rows become legitimate implicit commits under the gradient; expected gap-spawns become assertable probe outcomes); CI guards structural legality only at the `CommandExecutor` boundary. Refines: D18-L (low-confidence material now spawns gaps rather than only "folding into later questions"; preface, D47-L, remains the orientation carrier). Depends on: A22-L, D18-L, D47-L, D63-L, D65-L. Supersedes: "implications never become graph truth" as a *directness* rule, and the spike matrix's `shouldCommit` expectations as written. - **D82-L — Ground-material acquisition is a skill-structured layer in front of the sweep; bulk modes interpose a digest; a seeded situating gap routes modes.** Questions and answers are not the only way the graph gains ground material: the elicitor must also accept arbitrary pasted content, read user-referenced workspace documents, and explore-and-characterize a brownfield codebase. These are **acquisition modes** — elicit-by-question, ingest-paste, read-referenced-documents, explore-and-characterize — structured as Brunch prompt-resource skills (D58-L manifest world), each a distinct competence the elicitor reaches for; `read-referenced-documents` and `explore-and-characterize` may use Brunch-owned static `web_fetch`/`web_search` tools registered in the sealed Pi profile (D39-L/D40-L). Acquisition varies, capture stays uniform (`acquire → digest → sweep`): everything acquired lands in the transcript behind the sweep watermark. Bulk modes (exploration, research, large document reads) interpose a **digest** — an assistant-authored characterization of what was read/found (prior art: the v1 preface-of-exchange-tuple, which proved capture should run over the summary, not the raw bulk; D47-L) — and the sweep captures from digests plus conversational content while raw tool results pass behind the watermark as background. A **situating gap** is seeded at spec creation (orientation anchors: new-from-scratch / brownfield codebase / continuation of a prior thread — the grounding-advance anchors promoted from skill prose to agenda), so the opening elicitation itself routes the session into the right acquisition mode; the gap's discharge is what licenses, e.g., explore-and-characterize. Near-future direction (not current block): exploration/research acquisition delegated to **subagents** with the digest as the handback artifact — clean main-elicitor context without observer starvation, because the subagent owns its exploration context and returns only the digest. Depends on: D47-L, D57-L, D58-L, D65-L, D80-L. Supersedes: treating conversational answers as the only capture source. -- **D60-L — Agent context splits into pull / projection / render / surface, distinguishes graph-truth from active-context reads, and keeps `workspace.state` separate.** Agent context (what the agent reasons over) spans `cwd` (filesystem kickoff heuristic — `.brunch?`, session count/length, README/markdown sizes, file counts), `graph` (overview/list/query), and `node` (variable-hop neighborhood). The architectural commitment is that agent context is a staged pipeline (pull / projection / render / surface), graph reads must make visibility/projection explicit instead of silently mixing graph-truth with active-context views, the read family must stay a named-shape surface rather than a generic records API, and `workspace.state` remains a separate product-state subject rather than an agent-context alias. Current materialized state lives in [`src/graph/README.md`](src/graph/README.md), [`src/projections/README.md`](src/projections/README.md), [`src/renderers/README.md`](src/renderers/README.md), [`src/session/README.md`](src/session/README.md), [`src/graph/queries.ts`](src/graph/queries.ts), [`src/workspace/cwd-inventory.ts`](src/workspace/cwd-inventory.ts), and [`src/session/workspace-overview-context.ts`](src/session/workspace-overview-context.ts). Depends on: D35-L, D52-L, D53-L, D62-L, D64-L. Supersedes: pre-rendering context strings in the pull layer, scattering context-build logic across `graph/`, `.pi/agents/contexts/`, and tool adapters, or silently mixing graph-truth and active-context reads. +- **D60-L — Agent context splits into pull / projection / render / surface, distinguishes graph-truth from active-context reads, and keeps `workspace.state` separate.** Agent context (what the agent reasons over) spans `cwd` (filesystem kickoff heuristic — `.brunch?`, session count/length, README/markdown sizes, file counts), `graph` (overview/list/query), and `node` (variable-hop neighborhood). The architectural commitment is that agent context is a staged pipeline (pull / projection / render / surface), graph reads must make visibility/projection explicit instead of silently mixing graph-truth with active-context views, the read family must stay a named-shape surface rather than a generic records API, and `workspace.state` remains a separate product-state subject rather than an agent-context alias. Current materialized state lives in [`src/graph/README.md`](src/graph/README.md), [`src/projections/README.md`](src/projections/README.md), [`src/agents/contexts/README.md`](src/agents/contexts/README.md), [`src/renderers/README.md`](src/renderers/README.md), [`src/session/README.md`](src/session/README.md), [`src/graph/queries.ts`](src/graph/queries.ts), [`src/workspace/cwd-inventory.ts`](src/workspace/cwd-inventory.ts), and [`src/session/workspace-overview-context.ts`](src/session/workspace-overview-context.ts). Depends on: D35-L, D52-L, D53-L, D62-L, D64-L. Supersedes: pre-rendering context strings in the pull layer, scattering context-build logic across `graph/`, `.pi/agents/contexts/`, and tool adapters, or silently mixing graph-truth and active-context reads. - **D83-L — Context-render house style: a markdown frame (md-pen) with TOON for uniform data and a fenced ASCII tree for hierarchy, wrapped in `
` tags; agent context clusters into `` / `` / `` scopes.** Refines D60-L's RENDER stage. LLM-facing agent-context renders adopt one consistent dialect instead of ad-hoc `[bracket]` + bullet lists: - - **Audience scope.** `renderers/` serves several reader audiences — LLM agent context, human CLI/print, human evidence (probe/report transcript), and tool-result display. This house style is the **agent-context dialect** (the `
` scope-clustering plus TOON data blocks and the documents tree), scoped to LLM agent context. The **md-pen markdown substrate is shared across all audiences** (consistent formatting + escaping); human-facing renders such as the print-mode `workspace.state` dump (`renderWorkspaceState`, the sole `app/brunch.ts` `mode === 'print'` caller) use that substrate but are **not** part of the agent-context `
` clustering, because they are not agent context. A golden lock is audience-agnostic — it pins any stable text-output contract, human or LLM. (Open: whether `brunch print` should eventually render the house-style human views rather than a separate terse status dump — an `ln-plan` call, not assumed here.) - - **Markdown frame** — built with **md-pen** (zero-dependency, GFM, CommonMark-audited), the wrapper seam already reserved at `src/renderers/markdown.ts`: headings, sections, prose, blockquote notes, inline code, fenced blocks. Retires hand-rolled markdown string concatenation. - - **Uniform record sets — format by *size and legibility*, not uniformity alone.** *Large or unbounded* sets (ranked elicitation gaps, long session lists, mentions) render as **TOON** (`@toon-format/toon`, the `src/renderers/toon.ts` wrapper seam): token-efficient, lossless, with `[N]`-length + `{fields}`-header guardrails that improve model parse-reliability. *Small bounded* rosters (e.g. the workspace spec roster), positional/relational sets where table columns carry the reading contract (e.g. graph overview node and edge tables), and any human-facing render use **markdown tables** for stronger read-comprehension. The TOON token advantage concentrates on large arrays, so a short table gains little from it (per the TOON “keep examples small” guidance); for very large graph overviews, TOON is a future compact variant rather than the default overview contract. + - **Audience scope.** `src/agents/contexts/` owns the LLM agent-context dialect (the `
` scope-clustering plus TOON data blocks and the documents tree). `src/renderers/` now remains for human/product-only text such as print-mode `workspace.state` and debug transcript output. A golden lock is audience-agnostic — it pins any stable text-output contract, human or LLM. (Open: whether `brunch print` should eventually render the house-style human views rather than a separate terse status dump — an `ln-plan` call, not assumed here.) + - **Markdown frame** — built with **md-pen** (zero-dependency, GFM, CommonMark-audited), through the wrapper seam at `src/agents/contexts/primitives/markdown.ts`: headings, sections, prose, blockquote notes, inline code, fenced blocks. Retires hand-rolled markdown string concatenation. + - **Uniform record sets — format by *size and legibility*, not uniformity alone.** *Large or unbounded* sets (ranked elicitation gaps, long session lists, mentions) render as **TOON** (`@toon-format/toon`, the `src/agents/contexts/primitives/toon.ts` wrapper seam): token-efficient, lossless, with `[N]`-length + `{fields}`-header guardrails that improve model parse-reliability. *Small bounded* rosters (e.g. the workspace spec roster), positional/relational sets where table columns carry the reading contract (e.g. graph overview node and edge tables), and any human-facing render use **markdown tables** for stronger read-comprehension. The TOON token advantage concentrates on large arrays, so a short table gains little from it (per the TOON “keep examples small” guidance); for very large graph overviews, TOON is a future compact variant rather than the default overview contract. - **Hierarchy / file trees** (the documents surface) — a pure-JS ASCII-tree renderer (**stringify-tree** chosen; archy is the equivalent alternative) fed by Brunch's existing gitignore-aware filesystem walk (`workspace/cwd-inventory.ts`) and embedded in a fenced ` ```tree ` block. **Not** the system `tree` binary: Brunch already owns the walk, and a system executable is undistributable for an `npm`-installed CLI and non-deterministic/untestable inside a context path; the binary's only added value (the walk) is already ours, so we depend on a renderer, not a binary. - **Block delimiters** — each top-level context block is wrapped in an XML-style `
` tag (matching Pi's own markdown+XML system-prompt convention), e.g. ``, ``, ``. - **Legibility-over-structure rule** — format is chosen by reader legibility, not by mirroring internal data shape: prose where raw structure would mislead (the anchored neighborhood projection deliberately renders relations as prose and forbids arrow/role-token vocabulary — already guarded by the no-structural-leak invariant); pseudo/graph notation is reserved for human/debug surfaces, never default LLM context. - **Scope clustering** — the D60-L “agent context” subjects are regrouped along the `workspace → spec → session` hierarchy (D19-L): **``** (cwd scope) carries project identity, the documents tree, and the spec roster, and **carries no sessions**; **``** (selected-spec scope) carries the spec header/readiness, graph overview, anchored neighborhood, ranked elicitation gaps, the spec's **sessions** (a session binds to exactly one spec, D19-L), and future reconciliation needs; **``** (live-session scope) carries the runtime-posture frame, mentions, the world-update watermark, lifecycle, and recent transcript. The soft readiness line is computed over the selected spec's full elicitation-gap register, while the `Gaps` block renders only the ask-eligible ranked agenda; seed and `` context share the same renderer so their readiness numbers cannot diverge by caller-chosen population. - **Lexicon** — these LLM-facing context renders are distinct from the `workspace.state` product-state projection, which D60-L keeps for print/RPC/UI status; the `` context render is not `workspace.state`, and `renderWorkspaceState` stays the product-state renderer. (The earlier `` tag sketch is renamed `` to avoid the collision.) - **Dependencies** — three small leaf libraries (md-pen, `@toon-format/toon`, stringify-tree), each *retiring owned format-generation code* (the md-pen/TOON wrapper seams are already stubbed; the tree library replaces a hand-built formatter), so net owned surface decreases — the trade that justifies them under a dependencies-resist posture. - - **Rollout** — incremental: `` and `` context first, then migrate the `graph`, `session`/runtime-frame, and transcript renders onto the house style; the `renderer-golden-coverage` frontier re-scopes around the new dialect. + - **Rollout** — incremental: ``, ``, graph, session runtime-frame, and structured-exchange result renders now live under `src/agents/contexts/`; transcript debug/report rendering remains in `src/renderers/session/transcript.ts` until it becomes deliberate agent context. - **Closed audit** — per-session `turnCount` is derived once while inspecting canonical session files and counts only current Pi v3 JSONL message entries (`type: "message"` with `message.role: "user" | "assistant"`); tool/custom entries are excluded, and downstream workspace/specification overview renders reuse that inspected count rather than reparsing the file. - **D85-L — Brunch prompt-resource axis model: two AUTO axes (`strategy`, `lens`); `goal` inlined into the agent role; graph-write mechanism is method-routed, not a strategy.** A 2026-06-18 grill consolidation of the `agents/skills/` topology and the D58-L manifest axes, implemented across FE-893, FE-861, and FE-898. Four locked moves: 1. **Two AUTO objective axes, not three.** The runtime manifest advertises only `strategy` and `lens`; **`goal` is dropped as a manifest/runtime axis**. The four goal postures (`grounding-advance`, `elicit-expand`, `commit-converge`, always-on `capture-posture`) **inline into the `elicitor` agent role prompt** (`src/agents/prompts/elicitor/SYSTEM.md`), selected inline by the agent from the pushed readiness-band/posture context. Rationale: `goal` was already internal/readiness-derived and not user-mutable (D59-L), so advertising it as an AUTO-selectable axis was indirection over what is agent-directed-by-band anyway. Consequences for the build: `compose.ts` drops the `` family, `manifestsForState` drops `goals`, `runtime-state.ts` / `runtime-policy.ts` / `affordances.ts` drop the `goal` axis slot, and the runtime header drops the goal line. Capability-readiness (D74-L) is unaffected — it keys on gaps, not goal. @@ -321,7 +321,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c Depends on: D19-L, D52-L, D60-L, D62-L, D65-L, D75-L. Refines: D60-L (RENDER stage). Supersedes: the ad-hoc `[bracket]`-header + bullet-list render style as the house convention; hand-rolled markdown and tree string generation in renderers; carrying sessions in the `` cwd render. - **D95-L — Elicitor capability spine: `capture` / `generate` / `project` are the three elicitor capabilities, named over the existing skill axes — not a fourth axis or a re-axis.** The elicitor's work decomposes into three capabilities by what each does to the graph: **capture** commits ground material already present in the transcript tail into graph truth (the D80-L banded sweep + D81-L commitment gradient + D82-L acquisition layer, already specced); **generate** proposes new typed graph expressions on a requested plane from grounding plus a lens frame, fanning candidates out and committing the chosen one through review (D96-L); **project** derives nodes on one plane from a subset/plane of the existing graph with connecting cross-plane edges (e.g. requirements→design, design→oracles, A33-L). This is a capability vocabulary layered over D85-L's `strategy` / `lens` / `method` axes (held frozen, A35-L), not a replacement topology: capture is always-on conduct of every elicitor turn (D85-L move 3), while generate and project are method-routed capabilities requested just-in-time and gated only advisorily by capability-readiness (D74-L/D86-L). Background acquisition subagents (D82-L near-future, A34-L) are the `acquire` arm feeding capture, not a fourth capability. Depends on: D74-L, D80-L, D81-L, D82-L, D85-L, D86-L. Supersedes: the proposed `grounding` / `elicitation` / `projection` lifecycle directories as a replacement skill topology. -- **D96-L — `generate` is one deep plane-parameterized skill; fan-in is a three-value mode carried by `present_candidates` + the review-set path, not three skills.** Generative proposal across the intent, design, and oracle planes is **one** `generate` skill taking the target plane (and lens frame) as a parameter, not per-plane `propose-scenarios` / `propose-design-shapes` / `propose-oracle-ensembles` skills (the earlier per-plane sketch in [`docs/design/ELICITATION_LENSES.md`](docs/design/ELICITATION_LENSES.md)). The fan-out/fan-in shape is shared: the skill fans candidate expressions out, then **fan-in is a three-value mode** — `pick` (choose one), `synthesize` (merge candidates into one), `compose` (accept several) — expressed as plane-keyed method conduct over `present_candidates` plus the review-set path rather than branched per plane. Plane-specific judgment (the "design it twice" pattern for design, oracle-family selection for oracles, the kernel/lens heuristics in [`docs/design/BEHAVIORAL_KERNELS.md`](docs/design/BEHAVIORAL_KERNELS.md) / [`docs/design/ELICITATION_LENSES.md`](docs/design/ELICITATION_LENSES.md) for intent) lives in plane-keyed skill content read on demand (D58-L manifest world), not in separate skills. This **entailed un-stubbing the `present_candidates` topology** — the tool [`src/.pi/extensions/exchanges/present-candidates.ts`](src/.pi/extensions/exchanges/present-candidates.ts), the projection [`src/projections/exchanges/present-candidates.ts`](src/projections/exchanges/present-candidates.ts), and the renderer [`src/renderers/exchanges/present-candidates.ts`](src/renderers/exchanges/present-candidates.ts) — which D85-L move 4 confirmed as a live anticipated stub, not a fossil: candidate presentation gets its product owner here. The materialized tool remains **pick-only at the UI boundary**: intent uses the pick as recognition/provenance, while design-plane synthesize is performed by the method after the pick and then reviewed/committed through `present_review_set → request_response → acceptReviewSet`, so no `fan_in_mode` field is needed unless a later plane proves the UI itself must carry that mode. Commitment still flows through the review-set path (D27-L); `present_candidates` recognizes fan-out presentation and never commits graph truth itself (I51-L). Depends on: D26-L, D27-L, D30-L, D31-L, D58-L, D74-L, D85-L, D95-L; A31-L, A32-L. Supersedes: per-plane generative skills as the topology; treating `present_candidates` as a permanent stub without a product owner; prebuilding a fan-in schema field before a plane proves it necessary. +- **D96-L — `generate` is one deep plane-parameterized skill; fan-in is a three-value mode carried by `present_candidates` + the review-set path, not three skills.** Generative proposal across the intent, design, and oracle planes is **one** `generate` skill taking the target plane (and lens frame) as a parameter, not per-plane `propose-scenarios` / `propose-design-shapes` / `propose-oracle-ensembles` skills (the earlier per-plane sketch in [`docs/design/ELICITATION_LENSES.md`](docs/design/ELICITATION_LENSES.md)). The fan-out/fan-in shape is shared: the skill fans candidate expressions out, then **fan-in is a three-value mode** — `pick` (choose one), `synthesize` (merge candidates into one), `compose` (accept several) — expressed as plane-keyed method conduct over `present_candidates` plus the review-set path rather than branched per plane. Plane-specific judgment (the "design it twice" pattern for design, oracle-family selection for oracles, the kernel/lens heuristics in [`docs/design/BEHAVIORAL_KERNELS.md`](docs/design/BEHAVIORAL_KERNELS.md) / [`docs/design/ELICITATION_LENSES.md`](docs/design/ELICITATION_LENSES.md) for intent) lives in plane-keyed skill content read on demand (D58-L manifest world), not in separate skills. This **entailed un-stubbing the `present_candidates` topology** — the tool [`src/.pi/extensions/exchanges/present-candidates.ts`](src/.pi/extensions/exchanges/present-candidates.ts), the projection [`src/projections/exchanges/present-candidates.ts`](src/projections/exchanges/present-candidates.ts), and the renderer [`src/agents/contexts/exchanges/present-candidates.ts`](src/agents/contexts/exchanges/present-candidates.ts) — which D85-L move 4 confirmed as a live anticipated stub, not a fossil: candidate presentation gets its product owner here. The materialized tool remains **pick-only at the UI boundary**: intent uses the pick as recognition/provenance, while design-plane synthesize is performed by the method after the pick and then reviewed/committed through `present_review_set → request_response → acceptReviewSet`, so no `fan_in_mode` field is needed unless a later plane proves the UI itself must carry that mode. Commitment still flows through the review-set path (D27-L); `present_candidates` recognizes fan-out presentation and never commits graph truth itself (I51-L). Depends on: D26-L, D27-L, D30-L, D31-L, D58-L, D74-L, D85-L, D95-L; A31-L, A32-L. Supersedes: per-plane generative skills as the topology; treating `present_candidates` as a permanent stub without a product owner; prebuilding a fan-in schema field before a plane proves it necessary. - **D97-L — Skill ontology-heuristic provenance: three sources — consumed renderers, generated typed-vocab, hand-authored judgment — kept distinct.** Skill bodies that teach the agent how to think about the graph model draw ontology/heuristic content from three provenance classes that must not blur: (1) **dynamic instance context** rendered into the prompt by the context-render house style (D83-L, FE-870) — graph overviews, gap agendas, neighborhoods — consumed, never restated in skill prose; (2) **generated typed-vocab references** (`_generated/`, D85-L prompt-shape closure (d)) projected from the closed `kinds.ts` enums (D73-L) and drift-checked, for any skill that must enumerate node kinds / edge categories / bands / planes; and (3) **hand-authored judgment** — the irreducible "how to reason" content (kernels, lenses, oracle-family selection) that is neither instance data nor mechanical vocabulary. The rule: a skill cites the renderer or the generated reference rather than copying its content, so ontology drift (D73-L renames, D94-L band changes) propagates through one canonical source. Depends on: D58-L, D73-L, D83-L, D85-L, D94-L; FE-870. Supersedes: hand-restating node-kind / band / edge-category / plane vocabulary inside skill bodies. ### Critical Invariants @@ -358,14 +358,14 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c | I28-L | Auto-compaction output preserves the configured anchor set byte-stable: every entry kind listed in [src/.pi/extensions/compaction/index.ts](src/.pi/extensions/compaction/index.ts) is reconstructable post-compaction according to its `select` rule (`first | latest | active-leaves | all-unresolved`); LLM-generated narrative summary never replaces or rephrases preserved-anchor content; extension failure falls through to Pi default compaction rather than dropping anchors silently. | planned (compaction round-trip property tests at M9 plus inner-loop anchor-rendering unit tests and TypeBox schema validation of the anchor contract) | D43-L; R15, R13; I3-L, I4-L, I8-L, I12-L | | I29-L | Subagent SDK child sessions inherit Brunch Pi Profile sealing while allowing explicitly injected parent-world reads: every `subagent` tool invocation builds an in-process `AgentSession` from explicit sealed services (in-memory auth/settings/session managers, no ambient resources, assembled background system prompt, parent model registry, explicit tool allowlist); subagents never load ambient user/project `.pi/` skills, prompts, themes, extensions, context files, or behavior-shaping settings; subagents never gain direct access to the parent's `CommandExecutor`, Brunch RPC handlers, or graph persistence; parent world access is injected by the app root as a snapshot prompt block plus selected-spec read tools such as `read_graph`; parent aborts prevent prompt execution before/during setup and abort live child sessions; subagent results return to the main agent only as tool result content (no side-effect transcript writes). | covered for the implemented SDK seam by `src/.pi/extensions/subagents/subagents.test.ts`: frontmatter/config validation (including duplicate keys), explicit registry loading from `src/.pi/agents//SYSTEM.md` while ignoring unlisted planted bodies, tool allowlist conformance for `explorer`/`projector`/`researcher`, sealed faux-provider child sessions with no inherited base prompt or conversation, assembled prompt snapshot coverage (selected spec/workspace/session digest, no foreground elicitation recommendation), unknown-tool failure, `read_graph` availability only with injected parent graph readers, parent-spec-only graph read content with sibling-spec negative assertion, bounded concurrency including waiter/new-arrival race, invalid invocation shape rejection before runner call, and parent-abort setup/live-session behavior. Startup advertisement remains dev-gated by whether a launch path supplies subagent deps to `createBrunchPiExtensions(...)`. | D2-L, D39-L, D40-L, D44-L, D91-L; I1-L, I2-L, I11-L, I24-L | | I30-L | Elicitor capture commits only high-confidence graph truth; under the D81-L gradient, directly-stated facts commit `explicit`, confidently-materialized facts/edges commit `implicit`, low-confidence noticings never become graph truth — they map to existing-or-new `elicitation_gaps` as agenda — and contradictions with existing graph truth route to `reconciliation_need` rather than gap or overwrite. | covered for deterministic routing (`src/graph/__tests__/capture-commitment-gradient-gate.test.ts` proves the FE-861 routing gate through the real `mutate_graph`, `update_elicitation_gaps`, and `update_reconciliation_needs` adapters: explicit→commit, implicit→commit, low→one gap, contradiction→one semantic-conflict recon need, structural answered derivation, manual gap close on the graph clock, illegal capture batches failing loud, and the closed capture-quality-spike scenario family re-aimed from binary `shouldCommit` to gradient `expectedOutcome` rows across free prose, file refs, implication-heavy, and contradiction classes. `src/probes/capture-quality-loop.ts` keeps the LLM-in-loop probe as fitness by scoring gradient-routing accuracy, not gating classification quality. `src/.pi/extensions/brunch-data/reconciliation/index.test.ts` proves the recon-need tool pair over `CommandExecutor`/`getOpenReconciliationNeeds` plus elicit-posture legality. `src/projections/session/sweep-watermark.test.ts` plus `src/.pi/__tests__/extension-registry.test.ts` prove the D80-L transcript-position sweep watermark: conversational/digest tail classification, raw background exclusion, idempotent marker advance, graph-LSN watermark separation, and live `before_agent_start` wiring. The submit-time labeled-prefix capture module, its `session.*` wiring, and the `capture-response-to-graph` / `submit-message-capture` proofs were deleted 2026-06-19 (D80-L fossil retirement); `session.submitMessage` / `session.submitExchangeResponse` results no longer carry a `capture` field. Confidence/dedup quality remains fitness.) | D8-L, D18-L, D47-L, D65-L, D80-L, D81-L; A22-L | -| I31-L | Readiness never bars graph truth or work; it is just-in-time capability-readiness over relevant gaps, not a stored grade or kind whitelist. There is no `readiness_grade` scalar; capability availability is judged on request against the relevant `elicitation_gaps` (D74-L) and may proceed, proceed at low epistemic status, or negotiate — it never refuses outright. The `CommandExecutor` must not reject a graph node solely because its kind belongs to a later readiness band (D64-L). The soft `readiness estimate` (D45-L) is UI-only and gates nothing. Capability-readiness never *withholds a graph-write tool*: `mutate_graph` and the review-set tools stay in the active tool set regardless of readiness; `negotiate` is advisory (establishment offer + epistemic scaling), never a tool gate (D86-L). | partially covered (`src/projections/session/capability-readiness.test.ts` covers the D74-L tracer gate, including proceed / proceed_low_epistemic / negotiate, no-refusal, no grade-symbol import, and a live `presence` coverage flip; `src/projections/session/affordances.test.ts` covers the first consumer rewire: menu legality omits gated options while relevant gaps negotiate and includes them when coverage rises, with no grade symbols in `runtime-policy.ts` / `affordances.ts`, and a required `NodeKind` absent from the gap register fails loud (config bug ≠ uncovered — readiness omission never masks a seeding error); `src/projections/session/readiness-estimate.test.ts` covers the soft D45-L estimate shape, empty-band zero, importance-weighted per-band coverage, honest regression, and no legality-path imports; `src/.pi/extensions/agent-runtime/runtime/state.test.ts`, `src/.pi/extensions/agent-runtime/system-prompts/__tests__/compose.test.ts`, `src/.pi/extensions/agent-runtime/system-prompts/seed/workspace.test.ts`, and `src/.pi/__tests__/prompting.test.ts` cover the prompt consumer path: selected-spec gaps render as the soft per-band estimate, readiness-thin pinned axes remain visible, gated methods stay withheld, `readiness_grade=` is absent from prompt display, and the turn boundary threads the same gaps into cwd context without prompt-assembly failure; `src/session/workspace-session-coordinator.test.ts`, `src/renderers/workspace/workspace-state.test.ts`, `src/session/workspace-overview-context.test.ts`, `src/.pi/__tests__/context-tools.test.ts`, `src/rpc/handlers.test.ts`, and `src/web/app.test.tsx` cover the workspace/chrome display retirement: `chrome.phase` / `chrome.chatMode` no longer project through coordinator/RPC/web/chrome fixtures, and workspace overview session inventory no longer carries or renders `readinessGrade`; `createSpec` / `getSpec` persistence, seed/export fixture contracts, probes, and selected-spec prompt carriers no longer persist or transport a readiness grade; the D86-L graph-write-tool-floor sub-claim is covered — `state.test.ts` proves `mutate_graph` + review-set tools stay floor while `propose-graph`/`project-graph` readiness `negotiate`s and only the non-graph-write `review-for-gaps` is withheld, and `dev/__tests__/tier-2-harness.test.ts` proves the same through a real `runBrunchTui` boot at thin vs covered grounding) | D20-L, D45-L, D64-L, D74-L, D86-L | +| I31-L | Readiness never bars graph truth or work; it is just-in-time capability-readiness over relevant gaps, not a stored grade or kind whitelist. There is no `readiness_grade` scalar; capability availability is judged on request against the relevant `elicitation_gaps` (D74-L) and may proceed, proceed at low epistemic status, or negotiate — it never refuses outright. The `CommandExecutor` must not reject a graph node solely because its kind belongs to a later readiness band (D64-L). The soft `readiness estimate` (D45-L) is UI-only and gates nothing. Capability-readiness never *withholds a graph-write tool*: `mutate_graph` and the review-set tools stay in the active tool set regardless of readiness; `negotiate` is advisory (establishment offer + epistemic scaling), never a tool gate (D86-L). | partially covered (`src/projections/session/capability-readiness.test.ts` covers the D74-L tracer gate, including proceed / proceed_low_epistemic / negotiate, no-refusal, no grade-symbol import, and a live `presence` coverage flip; `src/projections/session/affordances.test.ts` covers the first consumer rewire: menu legality omits gated options while relevant gaps negotiate and includes them when coverage rises, with no grade symbols in `runtime-policy.ts` / `affordances.ts`, and a required `NodeKind` absent from the gap register fails loud (config bug ≠ uncovered — readiness omission never masks a seeding error); `src/projections/session/readiness-estimate.test.ts` covers the soft D45-L estimate shape, empty-band zero, importance-weighted per-band coverage, honest regression, and no legality-path imports; `src/.pi/extensions/agent-runtime/runtime/state.test.ts`, `src/agents/runtime/__tests__/compose.test.ts`, `src/agents/contexts/seeds/__tests__/turn-context.test.ts`, and `src/.pi/__tests__/prompting.test.ts` cover the prompt consumer path: selected-spec gaps render as the soft per-band estimate, readiness-thin pinned axes remain visible, gated methods stay withheld, `readiness_grade=` is absent from prompt display, and the turn boundary threads the same gaps into cwd context without prompt-assembly failure; `src/session/workspace-session-coordinator.test.ts`, `src/renderers/workspace/__tests__/workspace-state.test.ts`, `src/session/workspace-overview-context.test.ts`, `src/.pi/__tests__/context-tools.test.ts`, `src/rpc/handlers.test.ts`, and `src/web/app.test.tsx` cover the workspace/chrome display retirement: `chrome.phase` / `chrome.chatMode` no longer project through coordinator/RPC/web/chrome fixtures, and workspace overview session inventory no longer carries or renders `readinessGrade`; `createSpec` / `getSpec` persistence, seed/export fixture contracts, probes, and selected-spec prompt carriers no longer persist or transport a readiness grade; the D86-L graph-write-tool-floor sub-claim is covered — `state.test.ts` proves `mutate_graph` + review-set tools stay floor while `propose-graph`/`project-graph` readiness `negotiate`s and only the non-graph-write `review-for-gaps` is withheld, and `dev/__tests__/tier-2-harness.test.ts` proves the same through a real `runBrunchTui` boot at thin vs covered grounding) | D20-L, D45-L, D64-L, D74-L, D86-L | | I32-L | Public RPC structured-exchange driving never requires a client to speak raw Pi RPC: after Brunch method discovery and workspace/spec/session activation, each pending assistant-originated exchange is answered exactly once through `session.submitExchangeResponse`, and the deterministic permutation run produces linear Pi JSONL whose structured exchange projection preserves the same prompt/answer/status/comment artifacts as the equivalent TUI structured-exchange path. | covered for deterministic FE-744 parity under canonical session method names (`session.triggerExchange`, `session.pendingExchange`, `session.submitExchangeResponse`, `session.exchanges`): `rpc.discover` contract tests, pending/respond lifecycle tests, current public-RPC structured-exchange permutations, terminal non-answered status handling, option content/rationale parity, no repeated deterministic prompts, and transcript/exchange parity assertions. | D5-L, D48-L, D49-L; I10-L, I13-L, I21-L, I23-L | | I33-L | `capture_*` analysis entries are transcript evidence only: they persist as Brunch structured-exchange `toolResult` rows, are included by Brunch-semantic transcript renderers, are hidden or collapsed in TUI display, and never mutate graph truth or bypass `CommandExecutor`. | partially covered (minimum capture details schemas parse/export and reject graph payload fields; future runtime capture-analysis schema/rendering tests plus transcript renderer fixtures still need to prove persisted result rendering and TUI hide/collapse behavior; later graph-capture fixtures compare analysis candidates against committed graph mutations) | D17-L, D18-L, D37-L, D47-L, D50-L; I2-L, I11-L, I23-L, I30-L | | I34-L | `mutateGraph` batch validation is all-or-nothing: if any node or edge in the batch is structurally illegal, the entire batch is rejected and no partial state is persisted; the agent receives diagnostics sufficient for bounded self-correction retry. | covered (`command-executor/commit-graph-batch.test.ts` and graph-tool adapter tests cover dry-run/commit diagnostic parity for invalid basis, missing refs/codes, invalid category/stance, self-loop, invalid node kind/detail shape, rollback of nodes/edges/change_log/counters, transaction-local planning before LSN allocation/writes, and structured adapter diagnostics without thrown projected-code errors or fake endpoint refs) | D53-L; I1-L, I11-L | | I35-L | Graph context reads support multiple detail levels: a cursory/compact full-graph overview for orientation, detailed node-neighborhood context with configurable hop depth for focused work, bounded list slices by kind/readiness band, and related-node traversal by anchor and edge category. The `read_graph` parameter boundary teaches the mode-specific companion contract: `neighborhood` requires non-empty `nodeCode`, `related` requires non-empty `anchorCodes` plus `edgeCategory`, and list modes intentionally treat omitted/empty filters as unfiltered slices while unknown filters produce an empty slice. Context builders in `.pi/extensions/agent-runtime/system-prompts/seed/` (pushed seed contexts) and `.pi/extensions/brunch-data/context/` (pull tools) orchestrate which level to inject or advertise based on mode/goal/strategy/lens/readiness. | covered for current POC push and pull paths (`getGraphOverview` + `getNodeNeighborhood` in `queries.ts` with 10 tests; `src/.pi/extensions/agent-runtime/system-prompts/seed/{graph,workspace}.test.ts` covers lens-shaped overview rendering and selected-spec workspace/session/posture seed context, and `src/.pi/__tests__/context-tools.test.ts` covers the pulled context tools including bounded node-neighborhood rendering; `src/.pi/__tests__/prompting.test.ts` proves the explicit shell/product prompt path supplies selected-spec-bound graph context to `composeAgentPrompt()`). `src/graph/__tests__/observed-shapes-coverage.test.ts` guards the read mode ledger, and `src/.pi/__tests__/graph-tools.test.ts` covers `read_graph` mode-companion schema enforcement plus loud adapter diagnostics for malformed companion calls. Pulled context tools are part of the live read surface. | D52-L, D53-L, D58-L | | I36-L | Node `kind` is drawn from a per-plane closed enum structurally validated by the `CommandExecutor`. | covered (CommandExecutor rejects invalid kind-for-plane; tests in `command-executor.test.ts`) | D54-L, D56-L | | I37-L | `detail` is per-kind validated and boundary-advertised from the graph schema owner: `decision` and `term` nodes REQUIRE `detail` with their respective sub-schemas; the claim kinds `requirement`/`criterion`/`invariant` accept an OPTIONAL `form`-discriminated union (`plain`/`gherkin`/`formal`) and `context` accepts an OPTIONAL `form:"given"` payload (D88-L); all other kinds must omit `detail`; the `form` discriminant and any unknown per-form fields are rejected. `kind` drives behavior (band/edge-legality/source-question); `form` is inert payload plus a renderer hook. The agent/tool and dev-RPC mutation schemas expose the same per-kind companion shapes — including the claim/context form union — instead of accepting opaque `Unknown`. | covered (detail-required/prohibited/form-shape tests in `command-executor.test.ts`; boundary schema companion tests in `mutate-graph-edge-schema.test.ts`) | D54-L, D88-L | -| I38-L | Every Brunch prompt-resource manifest injected for an agent turn is generated from projected runtime state and spec/workspace gates: listed resources are Brunch-owned, readable under the active tool policy, legal for the current `(op_mode × strategy × lens)` tuple / capability-readiness / agent allow-list, and off-list resources are not advertised as available. AUTO axes never list readiness-illegal choices; pinned axes point to the pinned resource whenever the tuple is role/mode-legal, even if readiness negotiates. The shared affordance derivation and prompt manifest filtering use the same capability-readiness/AUTO legality source. | covered for current P0 manifest families (`src/.pi/extensions/agent-runtime/system-prompts/__tests__/compose.test.ts` covers default header/context/manifest output, two-AUTO-axis manifest output with no ``, AUTO capability-readiness filtering, pinned singleton resources, readiness-thin pin retention, role/mode-illegal pin rejection, and readable `src/.pi/` locations; `src/.pi/__tests__/prompting.test.ts` covers the explicit shell `before_agent_start` product path appending `composeAgentPrompt()` output from transcript-projected runtime state and no legacy composer import/resource discovery; `src/.pi/extensions/agent-runtime/runtime/state.test.ts` plus `src/projections/session/affordances.test.ts` cover shared legality/default behavior, including AUTO excluding `freestyle` and gated methods staying withheld during negotiation). FE-825 added a dev-gated introspection loop (`src/.pi/extensions/dev-mode/introspection/` + `src/dev/introspection-launcher.ts`) that records final provider payloads and pairs them with subjective model answers under `.fixtures/scratch/introspection//`; `brunch_introspect_query` now makes the captured provider payload/tool schemas/base options queryable in-chat for the same diagnostic plane. Probe fitness may still track whether the agent reads selected resources before use. | D39-L, D40-L, D58-L, D59-L, D66-L, D69-L, D85-L | +| I38-L | Every Brunch prompt-resource manifest injected for an agent turn is generated from projected runtime state and spec/workspace gates: listed resources are Brunch-owned, readable under the active tool policy, legal for the current `(op_mode × strategy × lens)` tuple / capability-readiness / agent allow-list, and off-list resources are not advertised as available. AUTO axes never list readiness-illegal choices; pinned axes point to the pinned resource whenever the tuple is role/mode-legal, even if readiness negotiates. The shared affordance derivation and prompt manifest filtering use the same capability-readiness/AUTO legality source. | covered for current P0 manifest families (`src/agents/runtime/__tests__/compose.test.ts` covers default header/context/manifest output, two-AUTO-axis manifest output with no ``, AUTO capability-readiness filtering, pinned singleton resources, readiness-thin pin retention, role/mode-illegal pin rejection, and readable `src/.pi/` locations; `src/.pi/__tests__/prompting.test.ts` covers the explicit shell `before_agent_start` product path appending `composeAgentPrompt()` output from transcript-projected runtime state and no legacy composer import/resource discovery; `src/.pi/extensions/agent-runtime/runtime/state.test.ts` plus `src/projections/session/affordances.test.ts` cover shared legality/default behavior, including AUTO excluding `freestyle` and gated methods staying withheld during negotiation). FE-825 added a dev-gated introspection loop (`src/.pi/extensions/dev-mode/introspection/` + `src/dev/introspection-launcher.ts`) that records final provider payloads and pairs them with subjective model answers under `.fixtures/scratch/introspection//`; `brunch_introspect_query` now makes the captured provider payload/tool schemas/base options queryable in-chat for the same diagnostic plane. Probe fitness may still track whether the agent reads selected resources before use. | D39-L, D40-L, D58-L, D59-L, D66-L, D69-L, D85-L | | I39-L | Every graph node in a spec has exactly one stable projected human reference code derived from `kind` + `kind_ordinal`; `(spec_id, plane, kind, kind_ordinal)` is unique; ordinals are monotonic per `(spec_id, plane, kind)` and are not reused after deletion or supersession. | partially covered (`graph-tool-resilience` added `nodes.kind_ordinal`, `node_kind_counters`, DB uniqueness, CommandExecutor allocation for single-node/batch writes, rollback protection, `GraphNode.kindOrdinal` row mapping, globally unique 1–3 letter labels with readiness-band metadata, projected-code parsing, selected-spec adapter resolution before `CommandExecutor`, code-only `mutate_graph` / `read_graph` schemas, and code-primary prompt/tool rendering; `queries.test.ts` now pins the merged `NODE_KIND_METADATA` labels + D64-L readiness bands so schema/code drift fails loudly; remaining slice still needs deletion/supersession no-reuse coverage) | D54-L, D62-L; I1-L, I11-L | | I40-L | Accepted graph nodes and edges use only `basis ∈ explicit | implicit`; review-set approval and direct user statements produce `explicit`, `propose-graph` concept-level materialization produces `implicit`, and the mutation path is recoverable from `change_log` rather than from a persisted basis enum value such as `accepted_review_set`. | covered (`graph-tool-resilience` replaced the persisted basis enum with `explicit | implicit`, made `mutateGraph` apply one batch create-basis to all created nodes/edges, made single-node `createNode` reject retired basis values before LSN/counter/node/change-log allocation, made `propose-graph` adapter commits implicit, made review-set translation explicit, rejected retired `accepted_review_set`, and records `change_log.operation` independently; `capture-response-to-graph` proves direct structured text responses commit explicit-basis graph nodes through `CommandExecutor`; `.fixtures/runs/project-graph-review-cycle/2026-06-06-project-graph-review-cycle/` proves full review-cycle approval creates explicit-basis graph truth) | D26-L, D27-L, D53-L, D63-L | | I41-L | Same-spec `supersession` edges form an acyclic directed graph; every edge-creation path validates proposed supersession edges together with existing supersession edges before committing. | covered (`command-executor/commit-graph-batch.test.ts` rejects existing-cycle closure, intra-batch cycles, and mixed existing+batch cycles through the shared dry-run/commit planner before batch writes; rejected cycles roll back or avoid batch nodes/edges/change_log; acyclic supersession commits remain covered by query/CommandExecutor success paths) | D51-L, D53-L; I34-L | @@ -377,7 +377,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c | I47-L | Continuity facts (seed/refresh, `worldUpdate`, `brunch.mention*`, `brunch.session_lifecycle`) persist only as Brunch custom transcript entries — never synthetic `toolCall`s, never prompt-only injection — so the D43-L projection can reconstruct them. Model-intent facts (`worldUpdate`, drains, staleness hints, context seed) ride pi's `CustomMessageEntry` (provider-visible `content` + structured `details`); ledger-only facts (`own_mutation`, `mention`, runtime state, binding, lifecycle) ride `CustomEntry` — both are custom transcript entries under this invariant (FE-857 carrier migration); boot/resume seeding is idempotent, deriving dedupe from projected transcript state (a seed/world-update already present is not re-emitted) rather than from hidden flags, and survives real restart/resume. The watermark must also survive compaction: the preserved-anchor set retains the latest watermark-carrier entry per spec so the projected global watermark never regresses after compaction+resume (which would otherwise spuriously re-emit `worldUpdate`). | covered (2026-06-11: boot/resume dedupe proven across an actual restart via `rebootTier2Runtime` — seed, kick, and `worldUpdate` non-duplicated, derived purely from transcript projection; compaction-anchor carrier preservation asserted at projection level; the Tier-2 scaffold has zero skipped/todo rows) | D17-L, D37-L, D43-L, D76-L, D78-L | | I48-L | Dev seeding never mutates an unintended workspace and never loads unrelated reusable seeds by ambient default: the seed path is target-workspace-scoped, selected by seed set/slug unless an all-seeds batch is explicitly requested, routes through `CommandExecutor`, and reports the destination `.brunch/data.db`; dev launch (`npm run dev`, with or without `--cwd`) observes existing workspace DB state but does not imply seeding. | partially validated — seed CLI now requires unambiguous `--workspace` + safe `--seed /` input, rejects malformed/unknown/duplicate flags before opening a workspace DB, writes only the named workspace DB through `seedFixture`/`CommandExecutor`, reports destination + selected seed ref mapping, and product RPC `workspace.selectionState` through `--cwd` proves seeded-vs-sibling workspace isolation; explicit all-seeds opt-in and full seed disposition catalog remain `dev-seed-fixtures` follow-up. | D70-L, D71-L, D79-L; I1-L, I11-L | | I49-L | The op_mode delegatable-set allowlist is the subagent write-safety boundary. A background agent's tool grant may exceed its spawning parent's, but an op_mode may spawn only the background agents named in its **code-owned** delegatable-set allowlist; a frontmatter-authored manifest can never self-advertise into an op_mode's delegatable set. Protected property: a read-only `elicit` session cannot spawn a write-capable child unless elicit's delegatable set explicitly names it. Ambient seal preserved alongside (D39-L): an injected-world background child still constructs in-memory auth/settings/session and performs no `~/.pi` discovery — world access is **injected, never discovered**. | covered (2026-06-24: `FOREGROUND_AGENT_ROSTER.elicit.foregroundAgent.canDelegate` is populated with the read-only background roster; `delegatableAgentsForRuntimeState` feeds `loadBrunchSubagents`; `registerBrunchSubagents` advertises and executes only `definitions ∩ delegatableAgents`; `subagents.test.ts` plants a test-only write-capable `worker` and proves `elicit` refuses it, while background frontmatter cannot author `canDelegate`) | D39-L, D40-L; D91-L, D92-L | -| I50-L | The readiness-band axis has two carriers that must not re-couple: the elicitor's asking agenda reads `gap.band` (`elicitation_gaps`, elicitation super-type only — `grounding`/`elicitation`), and node-level filter/render/threshold reads node band membership derived from `plane` (D94-L). The gap-driver sort and the readiness-estimate rollup never read the node-kind table; node band membership is never stored per-kind where `plane` determines it. No reader gates graph truth or work on band (I31-L). | covered (`src/projections/session/__tests__/readiness-estimate.test.ts` source-guards both `readiness-estimate.ts` and `elicitation-driver.ts` against `NODE_KIND_METADATA` / `bandsForKind` / `schema/nodes` imports; `src/graph/__tests__/read-api.test.ts` proves `queryGraph(..., { bands })` uses derived projection/commitment/dual-band membership; `src/renderers/graph/__tests__/graph-slice.test.ts` proves projection and band-less render handling; `src/graph/__tests__/command-executor.test.ts` proves `projection` is legal and unknown bands reject at the command boundary; `src/.pi/__tests__/graph-tools.test.ts` proves `read_graph` advertises the closed four-band enum) | D64-L, D94-L; I31-L, I35-L, I39-L | +| I50-L | The readiness-band axis has two carriers that must not re-couple: the elicitor's asking agenda reads `gap.band` (`elicitation_gaps`, elicitation super-type only — `grounding`/`elicitation`), and node-level filter/render/threshold reads node band membership derived from `plane` (D94-L). The gap-driver sort and the readiness-estimate rollup never read the node-kind table; node band membership is never stored per-kind where `plane` determines it. No reader gates graph truth or work on band (I31-L). | covered (`src/projections/session/__tests__/readiness-estimate.test.ts` source-guards both `readiness-estimate.ts` and `elicitation-driver.ts` against `NODE_KIND_METADATA` / `bandsForKind` / `schema/nodes` imports; `src/graph/__tests__/read-api.test.ts` proves `queryGraph(..., { bands })` uses derived projection/commitment/dual-band membership; `src/agents/contexts/graph/__tests__/graph-slice.test.ts` proves projection and band-less render handling; `src/graph/__tests__/command-executor.test.ts` proves `projection` is legal and unknown bands reject at the command boundary; `src/.pi/__tests__/graph-tools.test.ts` proves `read_graph` advertises the closed four-band enum) | D64-L, D94-L; I31-L, I35-L, I39-L | | I51-L | `present_candidates` is fan-out recognition only: it presents candidate graph expressions and records the chosen fan-in mode (`pick` / `synthesize` / `compose`, D96-L), but never commits graph truth itself. Generative commitment crosses into the graph only through the review-set path (`acceptReviewSet`, D27-L) or, for concept-accept direct commit, the `mutateGraph` grammar (D53-L); no candidate-presentation tool writes nodes/edges. | partially covered (2026-06-24 FE-1059 pick-only un-stub: `present_candidates` tool/projection/renderer carry no `CommandExecutor`/graph dependency, and `structured-exchange-present-request.test.ts` proves a pending `present_candidates` resolves to a `request_choice`/`capture_candidate` pick with no graph write; promoted run `.fixtures/runs/generate-fan-out/2026-06-24T16-51-13-704Z/` proves the live oracle fan-out turn emitted `present_candidates` while graph LSN/node/edge counts stayed unchanged and no `mutate_graph` or approved review result appeared; the `capture_candidate`→review-set/`mutateGraph` commit leg remains for later slices) | D26-L, D27-L, D53-L, D96-L | ## Future Direction Register diff --git a/package.json b/package.json index 1d5ca9466..459fbd354 100644 --- a/package.json +++ b/package.json @@ -43,9 +43,9 @@ "db:studio": "drizzle-kit studio", "test": "vitest --run", "test:watch": "vitest", - "test:renderers": "vitest --run src/renderers", - "test:renderers:watch": "vitest src/renderers", - "test:renderers:update": "vitest --run src/renderers --update", + "test:renderers": "vitest --run src/agents/contexts src/renderers", + "test:renderers:watch": "vitest src/agents/contexts src/renderers", + "test:renderers:update": "vitest --run src/agents/contexts src/renderers --update", "test:prompts": "vitest --run src/.pi/extensions/system-prompts", "test:prompts:watch": "vitest src/.pi/extensions/system-prompts", "test:prompts:update": "vitest --run src/.pi/extensions/system-prompts --update", diff --git a/src/.pi/__tests__/graph-tools.test.ts b/src/.pi/__tests__/graph-tools.test.ts index 032292666..565ec8ceb 100644 --- a/src/.pi/__tests__/graph-tools.test.ts +++ b/src/.pi/__tests__/graph-tools.test.ts @@ -1,6 +1,7 @@ import { Value } from 'typebox/value'; import { describe, expect, it } from 'vitest'; +import { formatGraphOverview } from '../../agents/contexts/graph/graph-slice.js'; import { createDb, type BrunchDb } from '../../db/connection.js'; import { CommandExecutor } from '../../graph/command-executor.js'; import { @@ -14,7 +15,6 @@ import { type GraphVisibility, } from '../../graph/queries.js'; import { READINESS_BANDS } from '../../graph/schema/kinds.js'; -import { formatGraphOverview } from '../../renderers/graph/graph-slice.js'; import { translateMutateGraph, formatMutateGraphResult, diff --git a/src/.pi/extensions/brunch-data/context/get-cwd.ts b/src/.pi/extensions/brunch-data/context/get-cwd.ts index 97277781f..23dc53407 100644 --- a/src/.pi/extensions/brunch-data/context/get-cwd.ts +++ b/src/.pi/extensions/brunch-data/context/get-cwd.ts @@ -1,6 +1,6 @@ import { resolve } from 'node:path'; -import { renderWorkspaceContext } from '../../../../renderers/workspace/workspace-context.js'; +import { renderWorkspaceContext } from '../../../../agents/contexts/workspace/workspace-context.js'; import { inspectWorkspaceOverview, type WorkspaceOverview, diff --git a/src/.pi/extensions/brunch-data/context/get-specification.ts b/src/.pi/extensions/brunch-data/context/get-specification.ts index d35e1e527..5196f8e87 100644 --- a/src/.pi/extensions/brunch-data/context/get-specification.ts +++ b/src/.pi/extensions/brunch-data/context/get-specification.ts @@ -1,4 +1,4 @@ -import { renderSpecificationContext } from '../../../../renderers/specification/specification-context.js'; +import { renderSpecificationContext } from '../../../../agents/contexts/specification/specification-context.js'; import { inspectSpecificationOverview } from '../../../../session/specification-overview-context.js'; import { resolveWorkspaceCwd } from './get-cwd.js'; import { resolveSelectedSpecBinding, type SessionManagerLike } from './session-binding.js'; diff --git a/src/.pi/extensions/brunch-data/context/index.ts b/src/.pi/extensions/brunch-data/context/index.ts index ed6681ce8..e665539ab 100644 --- a/src/.pi/extensions/brunch-data/context/index.ts +++ b/src/.pi/extensions/brunch-data/context/index.ts @@ -1,13 +1,13 @@ import type { ExtensionAPI } from '@earendil-works/pi-coding-agent'; +import { + renderRuntimeFrame, + type SessionRuntimeFrameRenderInput, +} from '../../../../agents/contexts/session/runtime-frame.js'; import { projectSessionRuntimeState, type RuntimeStateProjection, } from '../../../../projections/session/runtime-state.js'; -import { - renderRuntimeFrame, - type SessionRuntimeFrameRenderInput, -} from '../../../../renderers/session/runtime-frame.js'; import { NonLinearTranscriptError } from '../../../../session/brunch-session-envelope.js'; import { readWorkspaceContext } from './get-cwd.js'; import { readSpecificationContext } from './get-specification.js'; diff --git a/src/.pi/extensions/brunch-data/graph/index.ts b/src/.pi/extensions/brunch-data/graph/index.ts index 5b8749fe9..1f29e1d24 100644 --- a/src/.pi/extensions/brunch-data/graph/index.ts +++ b/src/.pi/extensions/brunch-data/graph/index.ts @@ -2,6 +2,8 @@ import type { ExtensionAPI, ToolDefinition } from '@earendil-works/pi-coding-agent'; +import { formatGraphOverview } from '../../../../agents/contexts/graph/graph-slice.js'; +import { formatNeighborhood } from '../../../../agents/contexts/graph/node-neighborhood.js'; import type { CommandExecutor } from '../../../../graph/command-executor.js'; import type { EdgeCategory, @@ -16,8 +18,6 @@ import type { NodeSelector, ReadinessBand, } from '../../../../graph/index.js'; -import { formatGraphOverview } from '../../../../renderers/graph/graph-slice.js'; -import { formatNeighborhood } from '../../../../renderers/graph/node-neighborhood.js'; import { graphMutationProductUpdates, type ProductUpdatePublisher } from '../../../../rpc/product-updates.js'; import { stampOwnMutationWatermark } from '../../../../session/prepare-next-turn.js'; import { diff --git a/src/.pi/extensions/exchanges/present-candidates.ts b/src/.pi/extensions/exchanges/present-candidates.ts index 299811e81..e39207441 100644 --- a/src/.pi/extensions/exchanges/present-candidates.ts +++ b/src/.pi/extensions/exchanges/present-candidates.ts @@ -1,7 +1,7 @@ import { defineTool } from '@earendil-works/pi-coding-agent'; +import { formatPresentCandidates } from '../../../agents/contexts/exchanges/present-candidates.js'; import { projectPresentCandidates } from '../../../projections/exchanges/present-candidates.js'; -import { formatPresentCandidates } from '../../../renderers/exchanges/present-candidates.js'; import { piSchema } from './pi-schema.js'; import { zPresentCandidatesParams, type PresentCandidatesParams } from './schemas/index.js'; import { renderMarkdownResult } from './shared/markdown.js'; diff --git a/src/.pi/extensions/exchanges/present-question.ts b/src/.pi/extensions/exchanges/present-question.ts index 5b754b66a..858e6429d 100644 --- a/src/.pi/extensions/exchanges/present-question.ts +++ b/src/.pi/extensions/exchanges/present-question.ts @@ -1,7 +1,7 @@ import { defineTool } from '@earendil-works/pi-coding-agent'; +import { formatPresentQuestion } from '../../../agents/contexts/exchanges/present-question.js'; import { projectPresentQuestion } from '../../../projections/exchanges/present-question.js'; -import { formatPresentQuestion } from '../../../renderers/exchanges/present-question.js'; import { piSchema } from './pi-schema.js'; import { zPresentQuestionParams, type PresentQuestionParams } from './schemas/index.js'; import { renderMarkdownResult } from './shared/markdown.js'; diff --git a/src/.pi/extensions/exchanges/present-review-set.ts b/src/.pi/extensions/exchanges/present-review-set.ts index 612fba932..693c9053c 100644 --- a/src/.pi/extensions/exchanges/present-review-set.ts +++ b/src/.pi/extensions/exchanges/present-review-set.ts @@ -1,9 +1,9 @@ import { defineTool } from '@earendil-works/pi-coding-agent'; +import { formatPresentReviewSet } from '../../../agents/contexts/exchanges/present-review-set.js'; import type { CommandExecutor, StructuralIllegal } from '../../../graph/command-executor.js'; import type { ReviewSetProposalPayload } from '../../../graph/review-set.js'; import { projectPresentReviewSet } from '../../../projections/exchanges/present-review-set.js'; -import { formatPresentReviewSet } from '../../../renderers/exchanges/present-review-set.js'; import { piSchema } from './pi-schema.js'; import { zPresentReviewSetParams, diff --git a/src/.pi/extensions/exchanges/shared/answer-source.ts b/src/.pi/extensions/exchanges/shared/answer-source.ts index 15e9826e7..5e11f3d8d 100644 --- a/src/.pi/extensions/exchanges/shared/answer-source.ts +++ b/src/.pi/extensions/exchanges/shared/answer-source.ts @@ -1,5 +1,5 @@ +import { formatRequestAnswer } from '../../../../agents/contexts/exchanges/request-answer.js'; import { projectRequestAnswer } from '../../../../projections/exchanges/request-answer.js'; -import { formatRequestAnswer } from '../../../../renderers/exchanges/request-answer.js'; import type { LiveExchangeAwaiter } from '../../../../session/live-exchange-broker.js'; import type { StructuredExchangeUiContext } from './ui-context.js'; diff --git a/src/.pi/extensions/exchanges/shared/choice-source.ts b/src/.pi/extensions/exchanges/shared/choice-source.ts index e5a620b13..f6dbd1b00 100644 --- a/src/.pi/extensions/exchanges/shared/choice-source.ts +++ b/src/.pi/extensions/exchanges/shared/choice-source.ts @@ -1,5 +1,5 @@ +import { formatRequestChoice } from '../../../../agents/contexts/exchanges/request-choice.js'; import { projectRequestChoice } from '../../../../projections/exchanges/request-choice.js'; -import { formatRequestChoice } from '../../../../renderers/exchanges/request-choice.js'; import type { SelectedChoice } from '../schemas/index.js'; import { normalizeOptionalText } from './markdown.js'; import type { StructuredExchangeUiContext } from './ui-context.js'; diff --git a/src/.pi/extensions/exchanges/shared/choices-editor.ts b/src/.pi/extensions/exchanges/shared/choices-editor.ts index 3e4b16223..8257f75d8 100644 --- a/src/.pi/extensions/exchanges/shared/choices-editor.ts +++ b/src/.pi/extensions/exchanges/shared/choices-editor.ts @@ -1,5 +1,5 @@ +import { formatRequestChoices } from '../../../../agents/contexts/exchanges/request-choices.js'; import { projectRequestChoices } from '../../../../projections/exchanges/request-choices.js'; -import { formatRequestChoices } from '../../../../renderers/exchanges/request-choices.js'; import { createMultiChoicePickerComponent } from '../../../components/multi-choice-picker.js'; import { STRUCTURED_EXCHANGE_REQUEST_CHOICES_EDITOR_SCHEMA, diff --git a/src/.pi/extensions/exchanges/shared/review-source.ts b/src/.pi/extensions/exchanges/shared/review-source.ts index a60c37a4e..4564eb821 100644 --- a/src/.pi/extensions/exchanges/shared/review-source.ts +++ b/src/.pi/extensions/exchanges/shared/review-source.ts @@ -1,8 +1,8 @@ +import { formatRequestReview } from '../../../../agents/contexts/exchanges/request-review.js'; import { projectRequestReview, type ReviewDecision, } from '../../../../projections/exchanges/request-review.js'; -import { formatRequestReview } from '../../../../renderers/exchanges/request-review.js'; import { normalizeOptionalText } from './markdown.js'; import type { StructuredExchangeUiContext } from './ui-context.js'; diff --git a/src/README.md b/src/README.md index e367c84bc..4caf5170d 100644 --- a/src/README.md +++ b/src/README.md @@ -12,7 +12,7 @@ src/ │ ├── prompts/ agent role body markdown resources │ ├── skills/ prompt-resource markdown resources │ ├── runtime/ prompt composition and prompt-resource/tool legality -│ └── contexts/ agent-visible seed/context text +│ └── contexts/ agent-visible seed, context-tool, graph, exchange text │ ├── .pi/ Sealed Pi-harness runtime surface │ ├── components/ reusable Pi TUI/message components @@ -30,7 +30,7 @@ src/ │ workspace coordination, session binding, LSN staleness │ ├── projections/ Structured DTOs derived from domain/session/tool facts -├── renderers/ Lossy text/markdown/toon/tool-content rendering +├── renderers/ Human/product-only lossy text rendering │ ├── rpc/ Brunch JSON-RPC handlers │ protocol, method handlers, WebSocket adapter @@ -46,7 +46,7 @@ rules: graph/ -> db/ [allowed] workspace/ -> constants/ or workspace-local files only projections/* -> agents/, graph/, session/, workspace/ [read/domain imports allowed; agents/ is temporary registry edge] - renderers/* -> projections/, graph/, session/, workspace/ as needed for input types + renderers/* -> projections/, session/, workspace/ as needed for human/product input types agents/ -> graph/, session/, renderers/ [agent-visible text over already-read facts] .pi/ -> agents/, graph/, session/, projections/, renderers/ [Pi runtime adapters/resources] rpc/ -> graph/, session/, projections/, renderers/ @@ -61,7 +61,7 @@ Rules: - `workspace/` owns cwd-scoped identity, inventory, and workspace default-state persistence. It must not import Pi, session, graph, DB, projection, renderer, adapter, transport, app, or web modules. - `graph/` imports from `db/`. No other layer imports `db/` directly. -- `agents/` owns the Brunch-authored LLM-context ingress seam. Today it hosts agent prompt bodies, prompt-resource skills, prompt composition, prompt-resource/tool legality, context seed composition, and the central file registry; later slices move reusable agent-visible rendering under this seam. The current `projections/session/runtime-policy.ts` import of this registry is a migration edge only: once the foreground roster moves under `agents/runtime/`, projections should stop depending on `agents/`. +- `agents/` owns the Brunch-authored LLM-context ingress seam. Today it hosts agent prompt bodies, prompt-resource skills, prompt composition, prompt-resource/tool legality, context seed composition, reusable agent-visible context renderers, and the central file registry. The current `projections/session/runtime-policy.ts` import of this registry is a migration edge only: once the foreground roster moves under `agents/runtime/`, projections should stop depending on `agents/`. - `.pi/` owns Pi-harness extensions/components and no longer hosts Brunch-authored prompt bodies or prompt-resource skills. - `.pi/extensions/` registers Pi tools/hooks/UI affordances and delegates product semantics outward. - `projections/` owns reusable structured output; `renderers/` owns reusable lossy text output. @@ -73,7 +73,7 @@ Product entrypoints now live in `app/`; package/project identity helpers and `.b The old domain-local `src/{graph,session,structured-exchange}/project/` folders now live under `projections/{graph,session,exchanges}/`. -The old domain-local `src/{graph,session,structured-exchange}/format/` folders and `src/render/` now live under `renderers/{graph,session,structured-exchange}/` and `renderers/`. +The old domain-local `src/{graph,session,structured-exchange}/format/` folders and `src/render/` first moved under `renderers/`; reusable model-facing renderers now live under `agents/contexts/`, while `renderers/` retains human/product-only text. Runtime-state transcript entry facts live in `session/runtime-state.ts`; reusable flattened runtime-state projection/policy now lives in `projections/session/runtime-state.ts` and `projections/session/runtime-policy.ts`. diff --git a/src/agents/README.md b/src/agents/README.md index b935cde6c..75481ba50 100644 --- a/src/agents/README.md +++ b/src/agents/README.md @@ -4,7 +4,7 @@ SPEC decisions: D39-L, D40-L, D52-L, D60-L, D85-L, D90-L, D91-L, D93-L ## Owns -`src/agents/` is the Pi-independent home for Brunch-authored model-facing context. It now owns bundled agent prompt bodies, Brunch prompt-resource skills, prompt composition/runtime legality, seed context composition, and the central registry for prompt/skill paths. +`src/agents/` is the Pi-independent home for Brunch-authored model-facing context. It now owns bundled agent prompt bodies, Brunch prompt-resource skills, prompt composition/runtime legality, seed context composition, reusable agent-visible context renderers, and the central registry for prompt/skill paths. ```text agents/ @@ -12,7 +12,7 @@ agents/ ├── prompts/ bundled foreground/background agent body markdown ├── skills/ strategy/lens/method prompt-resource markdown ├── runtime/ prompt composition and prompt-resource/tool legality -├── contexts/ agent-visible context text, currently seed composition +├── contexts/ agent-visible seed, context-tool, graph, and exchange text ├── registry.ts path registry for bundled agent bodies and prompt-resource skills └── __tests__/ registry/topology tests ``` @@ -32,4 +32,4 @@ rules: ## Migration note -This directory is intentionally mid-migration. Agent prompt bodies, prompt-resource skills, prompt composition, prompt-resource/tool legality, and context seed composition have moved here byte-stably. Later slices move reusable agent-visible renderers here; Pi extensions remain runtime adapters that register hooks/tools and call this layer. +This directory is intentionally mid-migration. Agent prompt bodies, prompt-resource skills, prompt composition, prompt-resource/tool legality, seed context composition, and reusable agent-visible context renderers have moved here byte-stably. Later slices promote remaining adapter-local model text here; Pi extensions remain runtime adapters that register hooks/tools and call this layer. diff --git a/src/agents/contexts/README.md b/src/agents/contexts/README.md index 02216c0c5..a171cce68 100644 --- a/src/agents/contexts/README.md +++ b/src/agents/contexts/README.md @@ -1,26 +1,37 @@ # agents/contexts/ — agent-visible context text -SPEC decisions: D52-L, D58-L, D60-L, D76-L, D78-L, D91-L +SPEC decisions: D52-L, D58-L, D60-L, D76-L, D78-L, D83-L, D91-L, D96-L ## Owns -`src/agents/contexts/` owns Brunch-authored text that is placed into an agent's model context outside the static prompt body/resource system. +`src/agents/contexts/` owns reusable Brunch-authored text that enters the model: pushed seed blocks, context-tool result text, graph/context markdown, and structured-exchange tool result text. ```text contexts/ -└── seeds/ per-turn pushed context blocks and origination seed payloads +├── primitives/ markdown, TOON, tree, and section formatting helpers +├── seeds/ per-turn pushed context blocks and origination seed payloads +├── graph/ graph overview/neighborhood and planned graph result text +├── workspace/ context text +├── specification/ context text +├── session/ runtime-frame and readiness text +└── exchanges/ present_* / request_* structured-exchange result text ``` ## Boundary rules ```pseudo rules: - agents/contexts/ -> graph/, session/, renderers/ [render already-read facts as agent-visible text] - .pi/extensions/* -> agents/contexts/ [adapters gather data, then ask for text] - session/ -> agents/contexts/ [origination choreography asks for seed payload text] - agents/contexts/ x> .pi/ [no Pi hook/tool registration] + agents/contexts/ -> graph/, projections/, session/, workspace/ [render already-read facts] + .pi/extensions/* -> agents/contexts/ [adapters gather data, then ask for text] + session/ -> agents/contexts/seeds/ [origination asks for seed payload text] + agents/contexts/ x> .pi/, app/, rpc/, web/ [no host, adapter, or transport effects] + renderers/ x> agents/contexts/ [human/product renderers do not own model text] ``` +## Snapshot convention + +Context golden files live beside their tests under `__snapshots__/` and use stock Vitest file snapshots. There is no separate preview writer. + ## Migration note -Only seed context composition has moved here so far. Later refactor items move reusable agent-visible renderers and adapter-local tool-result text into this subtree. +Reusable agent-visible renderers have moved here from `src/renderers/`. `src/renderers/` remains for human/product-only text such as print-mode workspace state and debug transcript output. Later refactor items promote adapter-local model text (for example related-node, mutation-result, elicitation, and reconciliation tool text) into this subtree. diff --git a/src/agents/contexts/exchanges/README.md b/src/agents/contexts/exchanges/README.md new file mode 100644 index 000000000..8fb717e48 --- /dev/null +++ b/src/agents/contexts/exchanges/README.md @@ -0,0 +1,5 @@ +# agents/contexts/exchanges/ — structured-exchange result text + +SPEC decisions: D13-L, D84-L, D96-L + +Owns model-facing text for structured-exchange tool results (`present_*` and `request_*`). Pi exchange adapters own schemas, registration, and TUI collection; this directory formats the returned text that becomes tool-result context. diff --git a/src/renderers/exchanges/__tests__/present-candidates.test.ts b/src/agents/contexts/exchanges/__tests__/present-candidates.test.ts similarity index 96% rename from src/renderers/exchanges/__tests__/present-candidates.test.ts rename to src/agents/contexts/exchanges/__tests__/present-candidates.test.ts index 91a6370c4..0950b655a 100644 --- a/src/renderers/exchanges/__tests__/present-candidates.test.ts +++ b/src/agents/contexts/exchanges/__tests__/present-candidates.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { projectPresentCandidates } from '../../../projections/exchanges/present-candidates.js'; +import { projectPresentCandidates } from '../../../../projections/exchanges/present-candidates.js'; import { formatPresentCandidates } from '../present-candidates.js'; function projection() { diff --git a/src/renderers/exchanges/present-candidates.ts b/src/agents/contexts/exchanges/present-candidates.ts similarity index 89% rename from src/renderers/exchanges/present-candidates.ts rename to src/agents/contexts/exchanges/present-candidates.ts index 3f99c5b1b..17acb82a6 100644 --- a/src/renderers/exchanges/present-candidates.ts +++ b/src/agents/contexts/exchanges/present-candidates.ts @@ -1,4 +1,4 @@ -import type { PresentCandidatesProjection } from '../../projections/exchanges/present-candidates.js'; +import type { PresentCandidatesProjection } from '../../../projections/exchanges/present-candidates.js'; const userRubricRows = [ ['Core bet', 'core_bet'], diff --git a/src/renderers/exchanges/present-question.ts b/src/agents/contexts/exchanges/present-question.ts similarity index 84% rename from src/renderers/exchanges/present-question.ts rename to src/agents/contexts/exchanges/present-question.ts index 37322e530..402867af0 100644 --- a/src/renderers/exchanges/present-question.ts +++ b/src/agents/contexts/exchanges/present-question.ts @@ -1,4 +1,4 @@ -import type { PresentQuestionProjection } from '../../projections/exchanges/present-question.js'; +import type { PresentQuestionProjection } from '../../../projections/exchanges/present-question.js'; export function formatPresentQuestion(projection: PresentQuestionProjection): string { const lines = [`# ${projection.heading.trim()}`]; diff --git a/src/renderers/exchanges/present-review-set.ts b/src/agents/contexts/exchanges/present-review-set.ts similarity index 86% rename from src/renderers/exchanges/present-review-set.ts rename to src/agents/contexts/exchanges/present-review-set.ts index a1f0b9ed8..7d3bf1066 100644 --- a/src/renderers/exchanges/present-review-set.ts +++ b/src/agents/contexts/exchanges/present-review-set.ts @@ -1,5 +1,5 @@ -import { roleNamedEdgeDraftEndpoints } from '../../graph/command-executor/role-named-edge-draft.js'; -import type { PresentReviewSetProjection } from '../../projections/exchanges/present-review-set.js'; +import { roleNamedEdgeDraftEndpoints } from '../../../graph/command-executor/role-named-edge-draft.js'; +import type { PresentReviewSetProjection } from '../../../projections/exchanges/present-review-set.js'; export function formatPresentReviewSet(projection: PresentReviewSetProjection): string { const payload = projection.payload; diff --git a/src/renderers/exchanges/request-answer.ts b/src/agents/contexts/exchanges/request-answer.ts similarity index 77% rename from src/renderers/exchanges/request-answer.ts rename to src/agents/contexts/exchanges/request-answer.ts index 910f62ad2..68cac01b2 100644 --- a/src/renderers/exchanges/request-answer.ts +++ b/src/agents/contexts/exchanges/request-answer.ts @@ -1,4 +1,4 @@ -import type { RequestAnswerDetails } from '../../projections/exchanges/request-answer.js'; +import type { RequestAnswerDetails } from '../../../projections/exchanges/request-answer.js'; export function formatRequestAnswer(details: RequestAnswerDetails): string { if ('cancelled' in details) return '# Response\n\n_User cancelled the request._'; diff --git a/src/renderers/exchanges/request-choice.ts b/src/agents/contexts/exchanges/request-choice.ts similarity index 83% rename from src/renderers/exchanges/request-choice.ts rename to src/agents/contexts/exchanges/request-choice.ts index afb3c39aa..63cc4c5fb 100644 --- a/src/renderers/exchanges/request-choice.ts +++ b/src/agents/contexts/exchanges/request-choice.ts @@ -1,4 +1,4 @@ -import type { RequestChoiceDetails } from '../../projections/exchanges/request-choice.js'; +import type { RequestChoiceDetails } from '../../../projections/exchanges/request-choice.js'; export function formatRequestChoice(details: RequestChoiceDetails): string { if ('cancelled' in details) return '# Response\n\n_User cancelled the request._'; diff --git a/src/renderers/exchanges/request-choices.ts b/src/agents/contexts/exchanges/request-choices.ts similarity index 87% rename from src/renderers/exchanges/request-choices.ts rename to src/agents/contexts/exchanges/request-choices.ts index e21ad82ef..f85ce206a 100644 --- a/src/renderers/exchanges/request-choices.ts +++ b/src/agents/contexts/exchanges/request-choices.ts @@ -1,4 +1,4 @@ -import type { RequestChoicesDetails } from '../../projections/exchanges/request-choices.js'; +import type { RequestChoicesDetails } from '../../../projections/exchanges/request-choices.js'; function markdownEscape(text: string): string { return text.replace(/([\\`*_{}[\]()#+\-.!|>])/g, '\\$1'); diff --git a/src/renderers/exchanges/request-review.ts b/src/agents/contexts/exchanges/request-review.ts similarity index 87% rename from src/renderers/exchanges/request-review.ts rename to src/agents/contexts/exchanges/request-review.ts index 49c251f95..d51778035 100644 --- a/src/renderers/exchanges/request-review.ts +++ b/src/agents/contexts/exchanges/request-review.ts @@ -1,4 +1,4 @@ -import type { RequestReviewDetails } from '../../projections/exchanges/request-review.js'; +import type { RequestReviewDetails } from '../../../projections/exchanges/request-review.js'; export function formatRequestReview(details: RequestReviewDetails): string { if ('cancelled' in details) return '# Review decision\n\n_User cancelled the review request._'; diff --git a/src/agents/contexts/graph/README.md b/src/agents/contexts/graph/README.md new file mode 100644 index 000000000..421535318 --- /dev/null +++ b/src/agents/contexts/graph/README.md @@ -0,0 +1,5 @@ +# agents/contexts/graph/ — graph context text + +SPEC decisions: D60-L, D62-L, D83-L, D97-L + +Owns reusable model-facing graph text: full selected-spec graph overviews, anchored neighborhoods, and planned graph result/context surfaces. Callers provide already-read graph/projection inputs; this directory formats them without reading storage or registering tools. diff --git a/src/renderers/graph/__previews__/graph-overview-kind-band-spread.md b/src/agents/contexts/graph/__snapshots__/graph-overview-kind-band-spread.md similarity index 100% rename from src/renderers/graph/__previews__/graph-overview-kind-band-spread.md rename to src/agents/contexts/graph/__snapshots__/graph-overview-kind-band-spread.md diff --git a/src/renderers/graph/__previews__/neighborhood-brunch-self-MOD1-hops2.md b/src/agents/contexts/graph/__snapshots__/neighborhood-brunch-self-MOD1-hops2.md similarity index 100% rename from src/renderers/graph/__previews__/neighborhood-brunch-self-MOD1-hops2.md rename to src/agents/contexts/graph/__snapshots__/neighborhood-brunch-self-MOD1-hops2.md diff --git a/src/renderers/graph/__previews__/neighborhood-brunch-self-REQ1.md b/src/agents/contexts/graph/__snapshots__/neighborhood-brunch-self-REQ1.md similarity index 100% rename from src/renderers/graph/__previews__/neighborhood-brunch-self-REQ1.md rename to src/agents/contexts/graph/__snapshots__/neighborhood-brunch-self-REQ1.md diff --git a/src/renderers/graph/__previews__/neighborhood-code-health-REQ1.md b/src/agents/contexts/graph/__snapshots__/neighborhood-code-health-REQ1.md similarity index 100% rename from src/renderers/graph/__previews__/neighborhood-code-health-REQ1.md rename to src/agents/contexts/graph/__snapshots__/neighborhood-code-health-REQ1.md diff --git a/src/renderers/graph/__previews__/neighborhood-hub-REQ1-compact.md b/src/agents/contexts/graph/__snapshots__/neighborhood-hub-REQ1-compact.md similarity index 100% rename from src/renderers/graph/__previews__/neighborhood-hub-REQ1-compact.md rename to src/agents/contexts/graph/__snapshots__/neighborhood-hub-REQ1-compact.md diff --git a/src/renderers/graph/__previews__/neighborhood-hub-REQ1-hops2.md b/src/agents/contexts/graph/__snapshots__/neighborhood-hub-REQ1-hops2.md similarity index 100% rename from src/renderers/graph/__previews__/neighborhood-hub-REQ1-hops2.md rename to src/agents/contexts/graph/__snapshots__/neighborhood-hub-REQ1-hops2.md diff --git a/src/renderers/graph/__previews__/neighborhood-hub-REQ1.md b/src/agents/contexts/graph/__snapshots__/neighborhood-hub-REQ1.md similarity index 100% rename from src/renderers/graph/__previews__/neighborhood-hub-REQ1.md rename to src/agents/contexts/graph/__snapshots__/neighborhood-hub-REQ1.md diff --git a/src/renderers/graph/__tests__/graph-slice.test.ts b/src/agents/contexts/graph/__tests__/graph-slice.test.ts similarity index 94% rename from src/renderers/graph/__tests__/graph-slice.test.ts rename to src/agents/contexts/graph/__tests__/graph-slice.test.ts index 9bccd6d46..e92743cbb 100644 --- a/src/renderers/graph/__tests__/graph-slice.test.ts +++ b/src/agents/contexts/graph/__tests__/graph-slice.test.ts @@ -1,7 +1,7 @@ import { expect, test } from 'vitest'; -import { readGraphSliceFixture } from '../../../graph/__tests__/support/fixture-reads.js'; -import type { GraphSlice } from '../../../graph/index.js'; +import { readGraphSliceFixture } from '../../../../graph/__tests__/support/fixture-reads.js'; +import type { GraphSlice } from '../../../../graph/index.js'; import { formatGraphOverview } from '../graph-slice.js'; const slice: GraphSlice = { @@ -100,7 +100,7 @@ test('overview: kind-band fixture golden stays uncapped and sectioned', async () const rendered = formatGraphOverview( readGraphSliceFixture({ set: 'kind-band-spread', fixture: 'coverage-matrix' }), ); - await expect(rendered).toMatchFileSnapshot('../__previews__/graph-overview-kind-band-spread.md'); + await expect(rendered).toMatchFileSnapshot('../__snapshots__/graph-overview-kind-band-spread.md'); expect(rendered).toContain('Graph overview (LSN 2): 24 nodes, 7 edges'); expect(rendered).toContain('| S1 | 24 | Lock one neighborhood preview |'); }); diff --git a/src/renderers/graph/__tests__/node-neighborhood.test.ts b/src/agents/contexts/graph/__tests__/node-neighborhood.test.ts similarity index 81% rename from src/renderers/graph/__tests__/node-neighborhood.test.ts rename to src/agents/contexts/graph/__tests__/node-neighborhood.test.ts index 11393bab9..0dbec391b 100644 --- a/src/renderers/graph/__tests__/node-neighborhood.test.ts +++ b/src/agents/contexts/graph/__tests__/node-neighborhood.test.ts @@ -1,7 +1,7 @@ /** * node-neighborhood render coverage. Each case renders a fixture through * `formatNeighborhood` and locks the full output as a markdown preview under the - * sibling `__previews__/`. The locked file IS the assertion: review the diff + * sibling `__snapshots__/`. The locked file IS the assertion: review the diff * when output changes, accept with `--update`. * * The one cross-cutting invariant kept inline is that anchored projections never @@ -13,7 +13,7 @@ import { expect, test } from 'vitest'; -import { readNodeNeighborhoodFixture } from '../../../graph/__tests__/support/fixture-reads.js'; +import { readNodeNeighborhoodFixture } from '../../../../graph/__tests__/support/fixture-reads.js'; import { formatNeighborhood } from '../node-neighborhood.js'; function expectNoStructuralLeak(rendered: string): void { @@ -32,7 +32,7 @@ test('neighborhood: real-port anchor (code-health REQ1)', async () => { const rendered = formatNeighborhood( readNodeNeighborhoodFixture({ set: 'bilal-port', fixture: 'code-health', anchorCode: 'REQ1' }), ); - await expect(rendered).toMatchFileSnapshot('../__previews__/neighborhood-code-health-REQ1.md'); + await expect(rendered).toMatchFileSnapshot('../__snapshots__/neighborhood-code-health-REQ1.md'); expectNoStructuralLeak(rendered); }); @@ -43,7 +43,7 @@ test('neighborhood: every edge category projected from one anchor (hub REQ1)', a // is the full label + directional-grouping matrix; the per-cell mapping is // proven by the projection unit tests. const rendered = formatNeighborhood(readNodeNeighborhoodFixture(HUB), { maxExpandedEdges: 20 }); - await expect(rendered).toMatchFileSnapshot('../__previews__/neighborhood-hub-REQ1.md'); + await expect(rendered).toMatchFileSnapshot('../__snapshots__/neighborhood-hub-REQ1.md'); expectNoStructuralLeak(rendered); }); @@ -51,13 +51,13 @@ test('neighborhood: hops=2 collapses ambient edges to a count (hub REQ1)', async const rendered = formatNeighborhood(readNodeNeighborhoodFixture({ ...HUB, hops: 2 }), { maxExpandedEdges: 30, }); - await expect(rendered).toMatchFileSnapshot('../__previews__/neighborhood-hub-REQ1-hops2.md'); + await expect(rendered).toMatchFileSnapshot('../__snapshots__/neighborhood-hub-REQ1-hops2.md'); expectNoStructuralLeak(rendered); }); test('neighborhood: maxExpandedEdges compacts oversized sections without omissions (hub REQ1)', async () => { const rendered = formatNeighborhood(readNodeNeighborhoodFixture(HUB), { maxExpandedEdges: 3 }); - await expect(rendered).toMatchFileSnapshot('../__previews__/neighborhood-hub-REQ1-compact.md'); + await expect(rendered).toMatchFileSnapshot('../__snapshots__/neighborhood-hub-REQ1-compact.md'); expect(rendered).not.toContain('omitted'); expectNoStructuralLeak(rendered); }); @@ -66,7 +66,7 @@ test('brunch-self: requirement anchor neighborhood (REQ1 one-authority)', async const rendered = formatNeighborhood(readNodeNeighborhoodFixture({ ...SELF, anchorCode: 'REQ1' }), { maxExpandedEdges: 20, }); - await expect(rendered).toMatchFileSnapshot('../__previews__/neighborhood-brunch-self-REQ1.md'); + await expect(rendered).toMatchFileSnapshot('../__snapshots__/neighborhood-brunch-self-REQ1.md'); expectNoStructuralLeak(rendered); }); @@ -74,7 +74,7 @@ test('brunch-self: module anchor neighborhood (MOD1 CommandExecutor)', async () const rendered = formatNeighborhood(readNodeNeighborhoodFixture({ ...SELF, anchorCode: 'MOD1', hops: 2 }), { maxExpandedEdges: 20, }); - await expect(rendered).toMatchFileSnapshot('../__previews__/neighborhood-brunch-self-MOD1-hops2.md'); + await expect(rendered).toMatchFileSnapshot('../__snapshots__/neighborhood-brunch-self-MOD1-hops2.md'); expectNoStructuralLeak(rendered); }); diff --git a/src/renderers/graph/commit-result.ts b/src/agents/contexts/graph/commit-result.ts similarity index 100% rename from src/renderers/graph/commit-result.ts rename to src/agents/contexts/graph/commit-result.ts diff --git a/src/renderers/graph/graph-slice.ts b/src/agents/contexts/graph/graph-slice.ts similarity index 93% rename from src/renderers/graph/graph-slice.ts rename to src/agents/contexts/graph/graph-slice.ts index 9b5aabd2d..d32dbeb53 100644 --- a/src/renderers/graph/graph-slice.ts +++ b/src/agents/contexts/graph/graph-slice.ts @@ -2,18 +2,18 @@ * Formats selected-spec GraphSlice reads into model-facing text. */ -import type { EdgeEndpoint } from '../../graph/policy/category-policy.js'; -import { edgeImpact } from '../../graph/projection/direction.js'; -import { edgeLabel } from '../../graph/projection/labels.js'; -import type { GraphSlice } from '../../graph/queries.js'; -import { NODE_KINDS, NODE_PLANES, type ReadinessBand } from '../../graph/schema/kinds.js'; +import type { EdgeEndpoint } from '../../../graph/policy/category-policy.js'; +import { edgeImpact } from '../../../graph/projection/direction.js'; +import { edgeLabel } from '../../../graph/projection/labels.js'; +import type { GraphSlice } from '../../../graph/queries.js'; +import { NODE_KINDS, NODE_PLANES, type ReadinessBand } from '../../../graph/schema/kinds.js'; import { NODE_KIND_METADATA, bandsForKind, formatGraphNodeCode, type NodeKind, -} from '../../graph/schema/nodes.js'; -import { markdownTable, joinMarkdownBlocks } from '../markdown.js'; +} from '../../../graph/schema/nodes.js'; +import { markdownTable, joinMarkdownBlocks } from '../primitives/markdown.js'; /** * The full, uncapped graph overview — node codes/planes/kinds/titles plus the diff --git a/src/renderers/graph/node-neighborhood.ts b/src/agents/contexts/graph/node-neighborhood.ts similarity index 93% rename from src/renderers/graph/node-neighborhood.ts rename to src/agents/contexts/graph/node-neighborhood.ts index 0ad4c0dae..284b8ca3a 100644 --- a/src/renderers/graph/node-neighborhood.ts +++ b/src/agents/contexts/graph/node-neighborhood.ts @@ -7,11 +7,11 @@ * never reach context. See src/graph/projection/. */ -import type { GraphEdge, NodeNeighborhood } from '../../graph/index.js'; -import type { EdgeEndpoint } from '../../graph/policy/category-policy.js'; -import { relationFromAnchor, type EdgeRelation } from '../../graph/projection/direction.js'; -import { edgeLabel } from '../../graph/projection/labels.js'; -import { formatGraphNodeCode, type GraphNode } from '../../graph/schema/nodes.js'; +import type { GraphEdge, NodeNeighborhood } from '../../../graph/index.js'; +import type { EdgeEndpoint } from '../../../graph/policy/category-policy.js'; +import { relationFromAnchor, type EdgeRelation } from '../../../graph/projection/direction.js'; +import { edgeLabel } from '../../../graph/projection/labels.js'; +import { formatGraphNodeCode, type GraphNode } from '../../../graph/schema/nodes.js'; export interface RenderNodeNeighborhoodOptions { readonly maxExpandedEdges?: number; diff --git a/src/renderers/graph/reconciliation-needs.ts b/src/agents/contexts/graph/reconciliation-needs.ts similarity index 100% rename from src/renderers/graph/reconciliation-needs.ts rename to src/agents/contexts/graph/reconciliation-needs.ts diff --git a/src/agents/contexts/primitives/README.md b/src/agents/contexts/primitives/README.md new file mode 100644 index 000000000..d24034a4c --- /dev/null +++ b/src/agents/contexts/primitives/README.md @@ -0,0 +1,3 @@ +# agents/contexts/primitives/ — context formatting helpers + +Owns small formatting helpers used by agent-visible context renders: markdown escaping/tables, XML-style sections, TOON record blocks, and fenced ASCII trees. These helpers are formatting substrate only; they do not choose what facts enter context. diff --git a/src/renderers/__tests__/markdown.test.ts b/src/agents/contexts/primitives/__tests__/markdown.test.ts similarity index 100% rename from src/renderers/__tests__/markdown.test.ts rename to src/agents/contexts/primitives/__tests__/markdown.test.ts diff --git a/src/renderers/__tests__/section.test.ts b/src/agents/contexts/primitives/__tests__/section.test.ts similarity index 100% rename from src/renderers/__tests__/section.test.ts rename to src/agents/contexts/primitives/__tests__/section.test.ts diff --git a/src/renderers/__tests__/toon.test.ts b/src/agents/contexts/primitives/__tests__/toon.test.ts similarity index 100% rename from src/renderers/__tests__/toon.test.ts rename to src/agents/contexts/primitives/__tests__/toon.test.ts diff --git a/src/renderers/__tests__/tree.test.ts b/src/agents/contexts/primitives/__tests__/tree.test.ts similarity index 100% rename from src/renderers/__tests__/tree.test.ts rename to src/agents/contexts/primitives/__tests__/tree.test.ts diff --git a/src/renderers/markdown.ts b/src/agents/contexts/primitives/markdown.ts similarity index 100% rename from src/renderers/markdown.ts rename to src/agents/contexts/primitives/markdown.ts diff --git a/src/renderers/section.ts b/src/agents/contexts/primitives/section.ts similarity index 100% rename from src/renderers/section.ts rename to src/agents/contexts/primitives/section.ts diff --git a/src/renderers/toon.ts b/src/agents/contexts/primitives/toon.ts similarity index 100% rename from src/renderers/toon.ts rename to src/agents/contexts/primitives/toon.ts diff --git a/src/renderers/tree.ts b/src/agents/contexts/primitives/tree.ts similarity index 100% rename from src/renderers/tree.ts rename to src/agents/contexts/primitives/tree.ts diff --git a/src/agents/contexts/seeds/origination.ts b/src/agents/contexts/seeds/origination.ts index 23f86273f..8ebc16ec1 100644 --- a/src/agents/contexts/seeds/origination.ts +++ b/src/agents/contexts/seeds/origination.ts @@ -16,10 +16,10 @@ * Used by: brunch-tui boot seeding, session.triggerExchange RPC origination */ +import { formatGraphOverview } from '../../../agents/contexts/graph/graph-slice.js'; import { sortElicitationGapsForAsking } from '../../../graph/elicitation-driver.js'; import type { GraphSlice } from '../../../graph/index.js'; import type { ElicitationGap } from '../../../graph/schema/elicitation-gaps.js'; -import { formatGraphOverview } from '../../../renderers/graph/graph-slice.js'; const TOP_GAP_COUNT = 5; diff --git a/src/agents/contexts/seeds/turn-context.ts b/src/agents/contexts/seeds/turn-context.ts index d90d355bd..6bca9dd0a 100644 --- a/src/agents/contexts/seeds/turn-context.ts +++ b/src/agents/contexts/seeds/turn-context.ts @@ -17,10 +17,10 @@ * Used by: `.pi/extensions/agent-runtime/system-prompts` (before_agent_start) via composeAgentContextSeed */ +import { renderSoftReadinessEstimate } from '../../../agents/contexts/session/readiness-estimate.js'; import type { GraphSlice } from '../../../graph/queries.js'; import type { ElicitationGap } from '../../../graph/schema/elicitation-gaps.js'; import { formatGraphNodeCode, type GraphNode } from '../../../graph/schema/nodes.js'; -import { renderSoftReadinessEstimate } from '../../../renderers/session/readiness-estimate.js'; import type { AgentLensSelection } from '../../../session/schema/kinds.js'; import type { WorkspacePostureState } from '../../../session/workspace-session-coordinator.js'; diff --git a/src/agents/contexts/session/README.md b/src/agents/contexts/session/README.md new file mode 100644 index 000000000..1ca7218c4 --- /dev/null +++ b/src/agents/contexts/session/README.md @@ -0,0 +1,5 @@ +# agents/contexts/session/ — live-session context text + +SPEC decisions: D40-L, D45-L, D60-L, D83-L + +Owns reusable model-facing session context fragments, currently the runtime-frame render and shared soft-readiness estimate. Transcript debug/report text remains in `src/renderers/session/transcript.ts` until it becomes deliberate agent context. diff --git a/src/renderers/session/__previews__/runtime-frame-ready.md b/src/agents/contexts/session/__snapshots__/runtime-frame-ready.md similarity index 100% rename from src/renderers/session/__previews__/runtime-frame-ready.md rename to src/agents/contexts/session/__snapshots__/runtime-frame-ready.md diff --git a/src/renderers/session/__tests__/runtime-frame.test.ts b/src/agents/contexts/session/__tests__/runtime-frame.test.ts similarity index 88% rename from src/renderers/session/__tests__/runtime-frame.test.ts rename to src/agents/contexts/session/__tests__/runtime-frame.test.ts index d873de65f..590ebcb6d 100644 --- a/src/renderers/session/__tests__/runtime-frame.test.ts +++ b/src/agents/contexts/session/__tests__/runtime-frame.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import type { RuntimeStateProjection } from '../../../projections/session/runtime-state.js'; +import type { RuntimeStateProjection } from '../../../../projections/session/runtime-state.js'; import { renderRuntimeFrame } from '../runtime-frame.js'; function readyProjection(): RuntimeStateProjection { @@ -36,7 +36,7 @@ describe('renderRuntimeFrame', () => { it('locks the ready runtime-frame preview and renders projected graph handles', async () => { const rendered = renderRuntimeFrame(readyProjection()); - await expect(rendered).toMatchFileSnapshot('../__previews__/runtime-frame-ready.md'); + await expect(rendered).toMatchFileSnapshot('../__snapshots__/runtime-frame-ready.md'); expect(rendered).toContain('#D12'); expect(rendered).not.toContain('node-1'); expect(rendered).toContain('mode=elicit; role=elicitor; strategy=step-wise-disambiguate; lens=oracle'); diff --git a/src/renderers/session/readiness-estimate.ts b/src/agents/contexts/session/readiness-estimate.ts similarity index 56% rename from src/renderers/session/readiness-estimate.ts rename to src/agents/contexts/session/readiness-estimate.ts index 5c9c3ba5a..0f454f49d 100644 --- a/src/renderers/session/readiness-estimate.ts +++ b/src/agents/contexts/session/readiness-estimate.ts @@ -1,6 +1,6 @@ -import type { ElicitationGap } from '../../graph/schema/elicitation-gaps.js'; -import { READINESS_BANDS } from '../../graph/schema/kinds.js'; -import { readinessEstimate } from '../../projections/session/readiness-estimate.js'; +import type { ElicitationGap } from '../../../graph/schema/elicitation-gaps.js'; +import { READINESS_BANDS } from '../../../graph/schema/kinds.js'; +import { readinessEstimate } from '../../../projections/session/readiness-estimate.js'; export function renderSoftReadinessEstimate(gaps: readonly ElicitationGap[]): string { const estimate = readinessEstimate(gaps); diff --git a/src/renderers/session/runtime-frame.ts b/src/agents/contexts/session/runtime-frame.ts similarity index 96% rename from src/renderers/session/runtime-frame.ts rename to src/agents/contexts/session/runtime-frame.ts index da06e53be..f0ca1b5d2 100644 --- a/src/renderers/session/runtime-frame.ts +++ b/src/agents/contexts/session/runtime-frame.ts @@ -1,4 +1,4 @@ -import type { RuntimeStateProjection } from '../../projections/session/runtime-state.js'; +import type { RuntimeStateProjection } from '../../../projections/session/runtime-state.js'; export type SessionRuntimeFrameRenderInput = | RuntimeStateProjection diff --git a/src/agents/contexts/specification/README.md b/src/agents/contexts/specification/README.md new file mode 100644 index 000000000..1b07474f9 --- /dev/null +++ b/src/agents/contexts/specification/README.md @@ -0,0 +1,5 @@ +# agents/contexts/specification/ — selected-spec context text + +SPEC decisions: D19-L, D60-L, D83-L + +Owns the `` context render: selected-spec overview, graph overview, ranked gaps, readiness line, and spec-scoped sessions. Inputs are inspected by session/graph callers; this directory only renders model-facing text. diff --git a/src/renderers/specification/__previews__/specification-context.md b/src/agents/contexts/specification/__snapshots__/specification-context.md similarity index 100% rename from src/renderers/specification/__previews__/specification-context.md rename to src/agents/contexts/specification/__snapshots__/specification-context.md diff --git a/src/renderers/specification/__tests__/specification-context.test.ts b/src/agents/contexts/specification/__tests__/specification-context.test.ts similarity index 86% rename from src/renderers/specification/__tests__/specification-context.test.ts rename to src/agents/contexts/specification/__tests__/specification-context.test.ts index affcad039..ecdd4aa37 100644 --- a/src/renderers/specification/__tests__/specification-context.test.ts +++ b/src/agents/contexts/specification/__tests__/specification-context.test.ts @@ -5,11 +5,11 @@ import { fileURLToPath } from 'node:url'; import { describe, expect, it } from 'vitest'; -import { openWorkspaceCommandExecutor } from '../../../graph/index.js'; -import { presenceGap } from '../../../graph/schema/elicitation-gap-fixtures.js'; -import { seedFixture, type SeedFixture } from '../../../graph/seed-fixtures.js'; -import { createSessionBindingData } from '../../../session/session-binding.js'; -import { inspectSpecificationOverview } from '../../../session/specification-overview-context.js'; +import { openWorkspaceCommandExecutor } from '../../../../graph/index.js'; +import { presenceGap } from '../../../../graph/schema/elicitation-gap-fixtures.js'; +import { seedFixture, type SeedFixture } from '../../../../graph/seed-fixtures.js'; +import { createSessionBindingData } from '../../../../session/session-binding.js'; +import { inspectSpecificationOverview } from '../../../../session/specification-overview-context.js'; import { renderSpecificationContext } from '../specification-context.js'; describe('renderSpecificationContext', () => { @@ -26,7 +26,7 @@ describe('renderSpecificationContext', () => { const details = await inspectSpecificationOverview(cwd, seeded.specId); const rendered = renderSpecificationContext(details); - await expect(rendered).toMatchFileSnapshot('../__previews__/specification-context.md'); + await expect(rendered).toMatchFileSnapshot('../__snapshots__/specification-context.md'); expect(rendered).toContain(''); expect(rendered).toContain('Overview:'); expect(rendered).toContain('Graph (LSN 2): 5 nodes, 3 edges'); @@ -64,7 +64,7 @@ describe('renderSpecificationContext', () => { async function loadFixture(slug: string, set = 'bilal-port'): Promise { const fixturePath = fileURLToPath( - new URL(`../../../../.fixtures/seeds/${set}/${slug}.json`, import.meta.url), + new URL(`../../../../../.fixtures/seeds/${set}/${slug}.json`, import.meta.url), ); return JSON.parse(await import('node:fs/promises').then(({ readFile }) => readFile(fixturePath, 'utf8'))); } diff --git a/src/renderers/specification/specification-context.ts b/src/agents/contexts/specification/specification-context.ts similarity index 84% rename from src/renderers/specification/specification-context.ts rename to src/agents/contexts/specification/specification-context.ts index 016c8e1a8..56362fcf9 100644 --- a/src/renderers/specification/specification-context.ts +++ b/src/agents/contexts/specification/specification-context.ts @@ -1,10 +1,10 @@ -import type { ElicitationGap, GraphSlice } from '../../graph/index.js'; -import type { WorkspaceSessionOverview } from '../../session/workspace-overview-context.js'; +import type { ElicitationGap, GraphSlice } from '../../../graph/index.js'; +import type { WorkspaceSessionOverview } from '../../../session/workspace-overview-context.js'; import { formatGraphOverview } from '../graph/graph-slice.js'; -import { joinMarkdownBlocks, markdownTable, markdownUl } from '../markdown.js'; -import { section } from '../section.js'; +import { joinMarkdownBlocks, markdownTable, markdownUl } from '../primitives/markdown.js'; +import { section } from '../primitives/section.js'; +import { renderToonBlock, type ToonRecord } from '../primitives/toon.js'; import { renderSoftReadinessEstimate } from '../session/readiness-estimate.js'; -import { renderToonBlock, type ToonRecord } from '../toon.js'; export interface SpecificationContextRenderInput { readonly spec: { diff --git a/src/agents/contexts/workspace/README.md b/src/agents/contexts/workspace/README.md new file mode 100644 index 000000000..9affdf041 --- /dev/null +++ b/src/agents/contexts/workspace/README.md @@ -0,0 +1,5 @@ +# agents/contexts/workspace/ — workspace context text + +SPEC decisions: D19-L, D60-L, D83-L + +Owns the `` context render for cwd/project/topology/spec-roster facts. It is agent context, not `workspace.state`; human print-mode workspace state stays in `src/renderers/workspace/workspace-state.ts`. diff --git a/src/renderers/workspace/__previews__/workspace-cwd-context.md b/src/agents/contexts/workspace/__snapshots__/workspace-cwd-context.md similarity index 100% rename from src/renderers/workspace/__previews__/workspace-cwd-context.md rename to src/agents/contexts/workspace/__snapshots__/workspace-cwd-context.md diff --git a/src/renderers/workspace/__previews__/workspace-overview-context.md b/src/agents/contexts/workspace/__snapshots__/workspace-overview-context.md similarity index 100% rename from src/renderers/workspace/__previews__/workspace-overview-context.md rename to src/agents/contexts/workspace/__snapshots__/workspace-overview-context.md diff --git a/src/renderers/workspace/__tests__/workspace-context.test.ts b/src/agents/contexts/workspace/__tests__/workspace-context.test.ts similarity index 86% rename from src/renderers/workspace/__tests__/workspace-context.test.ts rename to src/agents/contexts/workspace/__tests__/workspace-context.test.ts index d89f00f29..53683cc87 100644 --- a/src/renderers/workspace/__tests__/workspace-context.test.ts +++ b/src/agents/contexts/workspace/__tests__/workspace-context.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest'; -import type { WorkspaceOverview } from '../../../session/workspace-overview-context.js'; -import type { WorkspaceCwdInventory } from '../../../workspace/cwd-inventory.js'; +import type { WorkspaceOverview } from '../../../../session/workspace-overview-context.js'; +import type { WorkspaceCwdInventory } from '../../../../workspace/cwd-inventory.js'; import { renderWorkspaceContext } from '../workspace-context.js'; const topology = { @@ -32,7 +32,7 @@ describe('renderWorkspaceContext', () => { topology, } satisfies WorkspaceCwdInventory); - await expect(rendered).toMatchFileSnapshot('../__previews__/workspace-cwd-context.md'); + await expect(rendered).toMatchFileSnapshot('../__snapshots__/workspace-cwd-context.md'); expect(rendered).toMatch(/^\n/); expect(rendered).toContain('Project:\n- name: Brunch Project'); expect(rendered).toContain('Specifications:\n| id | title | nodes | sessions |'); @@ -63,7 +63,7 @@ describe('renderWorkspaceContext', () => { topology, } satisfies WorkspaceOverview); - await expect(rendered).toMatchFileSnapshot('../__previews__/workspace-overview-context.md'); + await expect(rendered).toMatchFileSnapshot('../__snapshots__/workspace-overview-context.md'); expect(rendered).toContain('| 1 | Context render house style | 42 | 3 |'); expect(rendered).not.toContain('session-1.jsonl'); expect(rendered).not.toMatch(/^#{1,6}\s/m); diff --git a/src/renderers/workspace/workspace-context.ts b/src/agents/contexts/workspace/workspace-context.ts similarity index 80% rename from src/renderers/workspace/workspace-context.ts rename to src/agents/contexts/workspace/workspace-context.ts index bbc84aa5a..cace719eb 100644 --- a/src/renderers/workspace/workspace-context.ts +++ b/src/agents/contexts/workspace/workspace-context.ts @@ -1,9 +1,9 @@ -import type { WorkspaceOverview } from '../../session/workspace-overview-context.js'; -import type { WorkspaceCwdInventory, WorkspaceTopologyEntry } from '../../workspace/cwd-inventory.js'; -import { inlineCode, joinMarkdownBlocks, markdownTable, markdownUl } from '../markdown.js'; -import { section } from '../section.js'; -import type { RenderTreeNode } from '../tree.js'; -import { renderTreeBlock } from '../tree.js'; +import type { WorkspaceOverview } from '../../../session/workspace-overview-context.js'; +import type { WorkspaceCwdInventory, WorkspaceTopologyEntry } from '../../../workspace/cwd-inventory.js'; +import { inlineCode, joinMarkdownBlocks, markdownTable, markdownUl } from '../primitives/markdown.js'; +import { section } from '../primitives/section.js'; +import type { RenderTreeNode } from '../primitives/tree.js'; +import { renderTreeBlock } from '../primitives/tree.js'; export function renderWorkspaceContext(context: WorkspaceCwdInventory | WorkspaceOverview): string { return section( diff --git a/src/agents/runtime/compose.ts b/src/agents/runtime/compose.ts index 0382c4151..918f3d8eb 100644 --- a/src/agents/runtime/compose.ts +++ b/src/agents/runtime/compose.ts @@ -1,8 +1,8 @@ import { selectElicitationGap } from '../../graph/elicitation-driver.js'; import type { ElicitationGap } from '../../graph/schema/elicitation-gaps.js'; import type { ResolvedBrunchAgentState } from '../../projections/session/runtime-state.js'; -import { renderSoftReadinessEstimate } from '../../renderers/session/readiness-estimate.js'; import type { AgentPromptSpecContext, AgentPromptWorkspaceContext } from '../contexts/seeds/turn-context.js'; +import { renderSoftReadinessEstimate } from '../contexts/session/readiness-estimate.js'; import { renderBrunchSkills, type PromptManifests } from './prompt-skills.js'; import { manifestsForState } from './state.js'; diff --git a/src/graph/README.md b/src/graph/README.md index fd4302ab8..474d3c454 100644 --- a/src/graph/README.md +++ b/src/graph/README.md @@ -117,7 +117,7 @@ not compare bare LSN values across sibling specs. - `.pi/extensions/brunch-data/graph/` — Pi tool adapters for `mutate_graph` and `read_graph`. - `rpc/` — graph projection handlers and synchronous response-capture wiring. - `projections/graph/` — topology stubs for deferred graph PROJECT seams; node-neighborhood consumers read `NodeNeighborhood` directly from `queries.ts`. -- `renderers/graph/` — reusable lossy markdown/text rendering over projected graph DTOs. +- `agents/contexts/graph/` — reusable model-facing graph context text over projected graph DTOs. - `.pi/extensions/agent-runtime/system-prompts/` — prompt composition consumes the read-only elicitation driver and the seed renderers consume graph reads. - `probes/` — graph proof drivers. diff --git a/src/graph/__tests__/command-executor.test.ts b/src/graph/__tests__/command-executor.test.ts index 831dc0fbe..47fe6410f 100644 --- a/src/graph/__tests__/command-executor.test.ts +++ b/src/graph/__tests__/command-executor.test.ts @@ -8,6 +8,7 @@ import { eq } from 'drizzle-orm'; import { describe, expect, it, beforeEach } from 'vitest'; +import { formatGraphOverview } from '../../agents/contexts/graph/graph-slice.js'; import { createDb, type BrunchDb } from '../../db/connection.js'; import { changeLog, @@ -19,7 +20,6 @@ import { reconciliationNeed, specs, } from '../../db/schema.js'; -import { formatGraphOverview } from '../../renderers/graph/graph-slice.js'; import { CommandExecutor } from '../command-executor.js'; import { queryGraph } from '../queries.js'; import { runCreateOnlyMutation } from './support/create-only-mutation.js'; diff --git a/src/probes/deterministic-exchange-script.ts b/src/probes/deterministic-exchange-script.ts index 4ee4d6c2c..38f87ee8d 100644 --- a/src/probes/deterministic-exchange-script.ts +++ b/src/probes/deterministic-exchange-script.ts @@ -16,8 +16,8 @@ import { SessionManager } from '@earendil-works/pi-coding-agent'; import type { PresentDetails } from '../.pi/extensions/exchanges/schemas/index.js'; +import { formatPresentQuestion } from '../agents/contexts/exchanges/present-question.js'; import { projectPresentQuestion } from '../projections/exchanges/present-question.js'; -import { formatPresentQuestion } from '../renderers/exchanges/present-question.js'; import { flushSessionManagerToFile } from '../session/flush-session-manager.js'; import { syntheticExchangeToolCallMessage, diff --git a/src/projections/README.md b/src/projections/README.md index 350382e77..309f3c5ee 100644 --- a/src/projections/README.md +++ b/src/projections/README.md @@ -34,7 +34,7 @@ Disposition: `✓` resolved (direct lock or accepted transitive proof) · `●` | `session/assistant-visible-watermark` | 2 | ✓ | Carrier projection over the authoritative `continuity-entry-classifier` watermark set. Unit tests guard seed/overview/own-mutation/`worldUpdate` carriers, narrow-read exclusion, and cross-spec failure. | | `session/continuity-entry-classifier` | 2 | ✓ | Shared FE-847 taxonomy for watermark-carrier vs continuity-only-non-debt vs debt-bearing entries; consumed by watermark projection and origination tail classification. | | `session/sweep-watermark` | 1 | ✓ | FE-861 D80-L sweep-window projection. `sweep-watermark.test.ts` locks the transcript-backed marker, conversational/digest tail classification, raw-background exclusion, monotonic idempotent advance, and graph-LSN watermark separation. | -| `workspace/workspace-context` | — | ✗ | Deleted/inlined. `read_workspace_context` and `renderers/workspace/workspace-context.ts` now consume `workspace/cwd-inventory.ts` and `session/workspace-overview-context.ts` source shapes directly; no replacement wrapper survives. | +| `workspace/workspace-context` | — | ✗ | Deleted/inlined. `read_workspace_context` and `agents/contexts/workspace/workspace-context.ts` now consume `workspace/cwd-inventory.ts` and `session/workspace-overview-context.ts` source shapes directly; no replacement wrapper survives. | | `workspace/workspace-state` | 4 | ✓ | `workspace-state.test.ts` — direct variant-shape invariant over `ready`, `needs_human`, and base `select_spec`; chrome/session-manager internals and retired phase/chat fields stay out of the DTO. | | `exchanges/request-choice` | 6 | ✓ | `request-choice.test.ts` (direct). | | `exchanges/present-question` | 6 | ✓ | Keep-transitive — `.pi/__tests__/structured-exchange-present-request.test.ts` proves question/body/options projection, and `session/exchange-projection.test.ts` proves the same details survive session reconstruction. | diff --git a/src/projections/graph/commit-result.ts b/src/projections/graph/commit-result.ts index 72351d695..fa6083eca 100644 --- a/src/projections/graph/commit-result.ts +++ b/src/projections/graph/commit-result.ts @@ -9,7 +9,7 @@ * - created refs, diagnostic ordering, and omission policy * * Used by: - * - renderers/graph/commit-result.ts + * - agents/contexts/graph/commit-result.ts * - .pi/extensions/brunch-data/graph/index.ts via mutate_graph tool results */ diff --git a/src/projections/graph/overview.ts b/src/projections/graph/overview.ts index 461930a8d..de257da88 100644 --- a/src/projections/graph/overview.ts +++ b/src/projections/graph/overview.ts @@ -9,7 +9,7 @@ * - ordered nodes/edges, omission counts, and truncation policy decisions * * Used by: - * - renderers/graph/overview.ts + * - agents/contexts/graph/graph-slice.ts * - .pi/extensions/brunch-data/graph/index.ts via graph overview tool results * - .pi/extensions/prompting.ts via pushed graph context */ diff --git a/src/projections/graph/reconciliation-needs.ts b/src/projections/graph/reconciliation-needs.ts index f1c9295f1..33db74d18 100644 --- a/src/projections/graph/reconciliation-needs.ts +++ b/src/projections/graph/reconciliation-needs.ts @@ -9,7 +9,7 @@ * - normalized target references and omission policy * * Future users: - * - renderers/graph/reconciliation-needs.ts + * - agents/contexts/graph/reconciliation-needs.ts * - pushed prompt context and/or future read tools */ diff --git a/src/renderers/README.md b/src/renderers/README.md index 3c242f878..2cfc5aa8c 100644 --- a/src/renderers/README.md +++ b/src/renderers/README.md @@ -1,92 +1,33 @@ -# renderers/ — reusable lossy text rendering +# renderers/ — human/product text rendering -SPEC decisions: D52-L, D60-L, D62-L, D83-L +SPEC decisions: D52-L, D60-L, D83-L ## Owns -Reusable lossy renderers that turn domain or projection inputs into markdown, compact text, TOON-like summaries, or toolResult content text. +`src/renderers/` now owns reusable text that is **not** deliberately model-facing: product/human print output and debug/report text. -**Context-render house style (D83-L).** LLM-facing agent context renders use one dialect: a markdown frame via **md-pen** (`markdown.ts` wrapper seam), uniform record sets as **TOON** via `@toon-format/toon` (`toon.ts` wrapper seam), file hierarchy as a fenced ` ```tree ` block from a pure-JS tree renderer (stringify-tree) fed by `workspace/cwd-inventory.ts` (never the system `tree` binary), and each top-level block wrapped in an XML-style `
` tag. Format follows reader legibility, not internal shape — prose where structure would mislead (e.g. the neighborhood no-structural-leak rule). Agent context clusters into `` / `` / `` scopes (D19-L); these are distinct from the `workspace.state` product-state projection (D60-L). Rollout is incremental (workspace + specification first); this frontier's ledger re-scopes around the dialect as renderers migrate. - -Renderers may import input types from `projections/`, `graph/`, `session/`, or `workspace/`, but they do not construct canonical DTOs, register Pi tools, handle RPC, or import web/app adapters. - -## Directory layout - -```pseudo +```text renderers/ - markdown.ts shared md-pen-backed markdown helpers (○ primitive) - toon.ts @toon-format/toon record-set helpers (○ primitive) - tree.ts stringify-tree ASCII hierarchy helpers (○ primitive) - section.ts XML-style context section wrapper (○ primitive) - graph/ graph overview/neighborhood/command markdown - session/ transcript + runtime-frame markdown - specification/ selected-specification context markdown - exchanges/ durable exchange markdown - workspace/ print/workspace-context markdown -``` - -## Dependency direction - -```pseudo -renderers/* -> projections/, graph/, session/, workspace/ [input types/data] -renderers/ x> .pi/, rpc/, app/, web/ +├── workspace/workspace-state.ts print-mode workspace state text +└── session/transcript.ts debug/report transcript markdown ``` -## Preview / golden authority +Agent-visible context text moved to `src/agents/contexts/`: -Renderer goldens stay test-local: each renderer's co-located test lives under `src/renderers//__tests__/` and calls the renderer directly, writing markdown snapshots to the sibling `src/renderers//__previews__/` via stock Vitest `toMatchFileSnapshot('../__previews__/.md')` — no custom snapshot helper. Use `npm run test:renderers:update` to review/accept renderer preview diffs. Do not introduce a shared `npm run render` harness until a second non-test preview consumer appears. +- graph overview/neighborhood context +- workspace/specification/session context-tool text +- structured-exchange tool-result text +- markdown/TOON/tree/section context formatting primitives -Ledger statuses: +## Boundary rules -- `● required` — durable renderer row; must have a golden plus at least one semantic invariant. -- `◐ partial` — real renderer with some oracle coverage but missing the required golden/invariant pair. -- `✓ locked` — required row is covered. -- `○ deferred/stub/primitive` — intentionally outside this coverage frontier. - -## Renderer ledger - -| Row | Owner | Status | Agent-context toolResult target | TUI / presenter target | Oracle / next | -| --- | --- | --- | --- | --- | --- | -| `graph/graph-slice.ts` (`formatGraphOverview`) | `read_graph` overview/list modes; origination context seed full graph overview | ✓ locked | `read_graph` text; `brunch.context_seed` graph section | Tool result markdown | G-D dual markdown tables: legend, plane·band node sections, impact-normalized edge table, uncapped golden/invariants, and a band-filtered invariant that dual-band nodes group under the requested band and nonmatching filtered nodes fail loud. | -| `graph/node-neighborhood.ts` (`formatNeighborhood`) | `read_graph` neighborhood mode | ✓ locked | `read_graph` text | Tool result markdown | G-C prose: anchor node, upstream/downstream/lateral sections, per-section compact density via `maxExpandedEdges`, deeper-hop relation line, and invariants for stable codes, `{hard}`-only strength, and no raw ids/role tokens. | -| `graph/commit-result.ts` | Future command-result text, if needed | ○ deferred | none current | none current | Leave outside until a live consumer appears. | -| `graph/reconciliation-needs.ts` | Future reconciliation rendering | ○ topology stub | none current | none current | Leave untouched until coherence/reconciliation surfaces activate. | -| `workspace/workspace-state.ts` | print-mode `workspace.state` | ◐ partial | n/a | Print-mode state text | Remaining: decide the `brunch print` house-style/status fork, then add preview/golden if it stays durable. | -| `workspace/workspace-context.ts` | `read_workspace_context`; origination context seed workspace section | ✓ locked | `read_workspace_context` text; `brunch.context_seed` workspace section | Tool result markdown | `workspace/__tests__/workspace-context.test.ts` + `workspace/__previews__/*`; invariants for `` wrapper, no sessions, table specs, fenced topology, and no ATX headings. | -| `specification/specification-context.ts` | `read_specification_context` | ✓ locked | `read_specification_context` text | Tool result markdown | `specification/__tests__/specification-context.test.ts` + `specification/__previews__/specification-context.md`; embeds the shared G-D graph overview and locks `` wrapper, Overview → Graph → Gaps → Sessions order, spec-scoped sessions, TOON gaps, and no ATX headings. | -| `session/readiness-estimate.ts` | Shared soft-readiness line for seed + specification context | ✓ locked | per-turn agent context seed; `read_specification_context` overview line | Tool result markdown where embedded | `turn-context.test.ts` + `specification-context.test.ts`; parity invariant keeps seed/specification readiness over the same full gap register. | -| `session/runtime-frame.ts` | `read_session_context` | ✓ locked | `read_session_context` text | Tool result markdown | `session/__tests__/runtime-frame.test.ts` + `session/__previews__/runtime-frame-ready.md`; invariant for projected handles. | -| `session/transcript.ts` | Brunch-semantic transcript rendering | ◐ partial | Probe/report transcript markdown | Transcript/report text, not Pi live display | Remaining: migrate `` transcript shape into the renderer home when the session context render is scoped. | -| `exchanges/request-answer.ts` | `request_answer` | ◐ partial | Request result text | Tool result `renderResult` via exchange markdown adapter | Remaining: request-family goldens for answered/non-answered branches; invariants for cancel/unavailable copy and comments. | -| `exchanges/request-choice.ts` | `request_choice` | ◐ partial | Request result text | Tool result `renderResult` via exchange markdown adapter | Remaining: request-family goldens for answered/non-answered branches; invariants for label escaping and comments. | -| `exchanges/request-choices.ts` | `request_choices` | ◐ partial | Request result text | Tool result `renderResult` via exchange markdown adapter | Remaining: request-family goldens for answered/non-answered branches; invariants for editor/error branches and comments. | -| `exchanges/request-review.ts` | `request_review` | ◐ partial | Request result text | Tool result `renderResult` via exchange markdown adapter | Remaining: request-family goldens for approve/change/reject + non-answered branches. | -| `exchanges/present-question.ts` | `present_question` | ◐ partial | Present result text | Tool result `renderResult` via exchange markdown adapter | Remaining: present-family golden for heading/body/options. | -| `exchanges/present-review-set.ts` | `present_review_set` | ◐ partial | Present result text / structural-illegal text | Tool result `renderResult` via exchange markdown adapter | Remaining: present-family review-set narration + no raw internal refs invariant. | -| `exchanges/present-candidates.ts` | `present_candidates` | ◐ partial | Present result text | Tool result `renderResult` via exchange markdown adapter | Projection/renderer/registration tests cover candidate title + user rubric rendering and meta-rubric suppression; remaining: house-style golden if candidate output becomes drift-prone. | -| `markdown.ts` | shared md-pen-backed markdown escaping/helpers | ○ primitive | indirect | indirect | Unit-covered substrate; existing graph goldens guard byte-stable helper behavior. | -| `toon.ts` | @toon-format/toon record-set + fenced-block helpers | ○ primitive | future context rows | future context rows | Unit-covered substrate; first real context render consumes it in Card 2/3. | -| `tree.ts` | stringify-tree hierarchy + fenced-block helpers | ○ primitive | future documents tree | future documents tree | Unit-covered substrate; workspace documents tree consumes it in Card 2. | -| `section.ts` | XML-style context section wrapper | ○ primitive | future context rows | future context rows | Unit-covered substrate; context scope renders consume it in Card 2/3. | - -## Agent-tool render anchor - -Tool-owned render targets are ledgered by their durable renderer row when they use `src/renderers/` directly: - -- Graph tools: `read_graph` overview/list modes are covered by `graph/graph-slice` (G-D) and neighborhood mode by `graph/node-neighborhood` (G-C); `mutate_graph` currently formats command outcomes in the graph extension adapter, not a reusable renderer row. **Gap:** `read_graph` `related` mode is rendered by `formatRelatedNodesResult` in `.pi/extensions/brunch-data/graph/command-adapter.ts` (not a `renderers/` row) and still emits structural leaks (`-[category/direction]->` arrows, raw `#id`, `plane/kind`) — it must migrate onto the prose vocabulary and relocate into `renderers/` (tracked under `renderer-golden-coverage` in `memory/PLAN.md`). -- Elicitation-gap tools: `read_elicitation_gaps` and `update_elicitation_gaps` format in `src/.pi/extensions/brunch-data/elicitation`; no renderer row is admitted until a second consumer or drift-prone reusable surface appears. -- Context tools: `read_workspace_context` is covered by `workspace/workspace-context`; `read_specification_context` is covered by `specification/specification-context`; `read_session_context` is covered by `session/runtime-frame`. -- Structured-exchange tools: `present_*` and `request_*` rows are the exchange renderer family above; TUI presentation currently delegates to each tool's `renderResult` adapter over the same markdown text, so lock mechanism follows the renderer row unless a component-specific display diverges. -- Base file floor (`read`, `grep`, `find`, `ls`) and dev-only tools (`brunch_session_query`, `brunch_introspect_query`) are Pi/dev tool surfaces, not renderer-frontier rows. - -## Entry-copy surfaces +```pseudo +rules: + renderers/ -> projections/, session/, workspace/ [human/product input types] + renderers/ x> .pi/, rpc/, app/, web/ [no adapters/transports] + renderers/ x> agents/contexts/ [does not own model-facing text] +``` -Provider-visible strings composed outside `src/renderers/` carry the same drift risk as renderer text. They are tracked here for wording-oracle disposition, but remain owned by their source modules. +## Migration note -| Surface | Owner | Status | Oracle / next | -| --- | --- | --- | --- | -| `kickTurnMessage` | `src/session/originate-assistant-turn.ts` | ◐ partial | Origination follow-up Card 3 locks D78-L wording; not part of renderer coverage cards. | -| Mention-staleness hints | `src/session/mention-ledger.ts` / turn-boundary reconciler | ○ review-only | No renderer row until copy changes or drift appears. | -| Session lifecycle notices | `src/.pi/extensions/session-hooks/session/lifecycle.ts` | ○ review-only | Keep owner-local unless promoted by a wording bug. | -| Compaction copy / anchor rationale | `src/.pi/extensions/compaction/index.ts` | ○ review-only | Contract prose, not a renderer row. | -| Seed framing | `src/agents/contexts/seeds/origination.ts` | ◐ partial | Covered through colocated origination seed tests today; promote only if wording churn continues. | +This directory intentionally no longer carries the context-render house-style ledger. That current agent-context topology lives in `src/agents/contexts/README.md`; `memory/SPEC.md` D83-L owns the decision event. diff --git a/src/session/README.md b/src/session/README.md index dd5e9f4db..c711a5e67 100644 --- a/src/session/README.md +++ b/src/session/README.md @@ -103,8 +103,8 @@ directly instead of growing a wrapper. | Shape | Canonical owner | Current consumers | Disposition / reason | | --- | --- | --- | --- | -| `cwd_inventory` | `workspace/cwd-inventory.ts` (`inspectWorkspaceCwdInventory`) | `read_workspace_context`, `renderers/workspace/workspace-context.ts` | Workspace-owned direct PULL read. The typed inventory already matches the tool/renderer seam, so no `projections/workspace/workspace-context` wrapper survives. | -| `workspace_overview` | `workspace-overview-context.ts` (`inspectWorkspaceOverview`) | `read_workspace_context`, origination seed context, `renderers/workspace/workspace-context.ts` | Session-side composition over graph specs and canonical session files. Same no-wrapper rationale as `cwd_inventory`: the source shape is already the consumer shape. | +| `cwd_inventory` | `workspace/cwd-inventory.ts` (`inspectWorkspaceCwdInventory`) | `read_workspace_context`, `agents/contexts/workspace/workspace-context.ts` | Workspace-owned direct PULL read. The typed inventory already matches the tool/renderer seam, so no `projections/workspace/workspace-context` wrapper survives. | +| `workspace_overview` | `workspace-overview-context.ts` (`inspectWorkspaceOverview`) | `read_workspace_context`, origination seed context, `agents/contexts/workspace/workspace-context.ts` | Session-side composition over graph specs and canonical session files. Same no-wrapper rationale as `cwd_inventory`: the source shape is already the consumer shape. | | `workspace_session_state` | `WorkspaceSessionCoordinator` (`WorkspaceSessionState`) | `projections/workspace/workspace-state.ts`, `chromeStateForWorkspace`, app/rpc/web workspace flows | Source union owned by the coordinator. Downstream code may flatten it, but the coordinator remains the authority for the narrow chrome snapshot and status-variant field set. | | `agent_runtime_vocab` | `schema/kinds.ts`, `schema/tool-names.ts` | `runtime-state.ts`, `projections/session/runtime-policy.ts`, `projections/session/affordances.ts`, `agents/runtime/state.ts`, `.pi/extensions/agent-runtime/orchestrator-stub/` | Pure vocabulary leaf for runtime axes, agent-role ids, and shared Brunch tool-name constants; imports nothing and mirrors D73-L's graph taxonomy direction on the session side. | | `agent_runtime_state` | `latestValidBrunchAgentStateEntryData` and transcript-backed runtime-state facts in `session/runtime-state.ts` | `projections/session/runtime-state.ts`, `projections/session/affordances.ts`, `.pi/extensions/agent-runtime/runtime/` | Transcript-backed source read. Projection/policy layers derive from these facts rather than storing parallel hidden runtime memory. | @@ -149,7 +149,7 @@ schema, and the product-state-gated rows must stay explicit deferred tripwires. - `projections/session/` — for reusable transcript-context DTO projection. - `projections/workspace/` — for reusable workspace-state DTO projection. - `renderers/session/` — for reusable transcript markdown rendering. -- `renderers/workspace/` — for workspace inventory / overview text rendering over source session read shapes. +- `agents/contexts/workspace/` — for workspace inventory / overview agent-context text over source session read shapes. - `rpc/` — for session.* and workspace.* RPC handlers. - `.pi/extensions/` — for session lifecycle hooks. diff --git a/src/session/specification-overview-context.ts b/src/session/specification-overview-context.ts index 47145bebc..640723b00 100644 --- a/src/session/specification-overview-context.ts +++ b/src/session/specification-overview-context.ts @@ -1,8 +1,8 @@ import { resolve } from 'node:path'; +import { type SpecificationContextRenderInput } from '../agents/contexts/specification/specification-context.js'; import { sortElicitationGapsForAsking } from '../graph/elicitation-driver.js'; import { openWorkspaceGraphRuntime } from '../graph/index.js'; -import { type SpecificationContextRenderInput } from '../renderers/specification/specification-context.js'; import { inspectWorkspaceOverview } from './workspace-overview-context.js'; export interface SpecificationOverviewContext extends SpecificationContextRenderInput { diff --git a/src/session/workspace-overview-context.ts b/src/session/workspace-overview-context.ts index 428404ffa..24cd960b6 100644 --- a/src/session/workspace-overview-context.ts +++ b/src/session/workspace-overview-context.ts @@ -1,7 +1,7 @@ import { basename, resolve } from 'node:path'; +import { renderWorkspaceContext } from '../agents/contexts/workspace/workspace-context.js'; import { openWorkspaceGraphRuntime } from '../graph/index.js'; -import { renderWorkspaceContext } from '../renderers/workspace/workspace-context.js'; import { inspectWorkspaceCwdInventory, type WorkspaceTopologyEntry } from '../workspace/cwd-inventory.js'; import type { ProjectIdentity } from '../workspace/project-identity.js'; import { inspectCanonicalSessionFiles } from './workspace-session-coordinator/canonical-session-files.js'; From b2155270e9b378e92baa4b77ee55bbdcc246ad06 Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Thu, 25 Jun 2026 18:54:04 +0200 Subject: [PATCH 16/54] Promote adapter model text into agents contexts --- memory/REFACTOR.md | 2 +- memory/SPEC.md | 2 +- src/.pi/__tests__/graph-tools.test.ts | 6 +- .../brunch-data/elicitation/index.ts | 44 ++----- .../brunch-data/graph/command-adapter.ts | 113 +----------------- src/.pi/extensions/brunch-data/graph/index.ts | 12 +- .../brunch-data/reconciliation/index.ts | 32 ++--- src/agents/contexts/README.md | 5 +- src/agents/contexts/elicitation.ts | 46 +++++++ src/agents/contexts/graph/README.md | 2 +- src/agents/contexts/graph/commit-result.ts | 60 +++++++--- .../contexts/graph/reconciliation-needs.ts | 51 +++++--- src/agents/contexts/graph/related-nodes.ts | 55 +++++++++ 13 files changed, 214 insertions(+), 216 deletions(-) create mode 100644 src/agents/contexts/elicitation.ts create mode 100644 src/agents/contexts/graph/related-nodes.ts diff --git a/memory/REFACTOR.md b/memory/REFACTOR.md index 3e73ba13e..fdbc99ab9 100644 --- a/memory/REFACTOR.md +++ b/memory/REFACTOR.md @@ -69,7 +69,7 @@ callers after refactor 4. ✓ Move foreground prompt composition and prompt-resource legality code into the new runtime home; leave Pi extension code as a thin hook adapter that imports the central composer. 5. ✓ Move per-turn pushed context composition and origination/session-entry seed composition into the new context seed home; update session and app callers to import from the central agent context layer. 6. ✓ Move reusable LLM-facing context renderers into the new context home, keeping product-only/human-only renderers outside unless they are deliberately agent-visible. -7. Promote adapter-local LLM text formatting into the new context home: graph mutation result text, related-node text, elicitation agenda/update text, and reconciliation agenda/update text. +7. ✓ Promote adapter-local LLM text formatting into the new context home: graph mutation result text, related-node text, elicitation agenda/update text, and reconciliation agenda/update text. 8. Consolidate golden/preview tests for prompt composition and agent context renderers under the new agent tree, preserving existing semantic invariants while making “what enters the model” reviewable in one place. 9. Add a boundary guard that prevents Pi extension adapters from owning Brunch-authored tool/session/prompt content text, while explicitly allowing tool schemas, labels, descriptions, and prompt snippets to remain adapter-owned. 10. Reconcile topology READMEs, SPEC/PLAN references, build scripts, and direct-import docs so the new owner is canonical and the old `.pi`/`renderers` ownership claims are retired. diff --git a/memory/SPEC.md b/memory/SPEC.md index 9b0d840eb..c3752b0f5 100644 --- a/memory/SPEC.md +++ b/memory/SPEC.md @@ -133,7 +133,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c - **D40-L — Runtime state is transcript-backed Brunch session-agent state, not hidden extension memory.** The architectural commitment is that Brunch session-agent posture remains transcript-backed Pi JSONL state rather than hidden extension memory: posture switches are user/system authority, the foreground session agent is derived from `op_mode`, child axes default back to `AUTO` when invalidated, and tool authority remains `op_mode`-gated rather than prompt-composition-owned. Runtime-state entries are Pi JSONL state-change facts, not assistant/user chat content: init and switch entries should render, when visible, as dim non-chat state rows analogous to Pi thinking/model-change rows, and must not enter LLM context as ordinary conversation. Current materialized state lives in [`src/session/README.md`](src/session/README.md), [`src/projections/README.md`](src/projections/README.md), [`src/.pi/README.md`](src/.pi/README.md), [`src/agents/README.md`](src/agents/README.md), [`src/agents/prompts/README.md`](src/agents/prompts/README.md), and [`src/projections/session/runtime-policy.ts`](src/projections/session/runtime-policy.ts). Depends on: D17-L, D23-L, D25-L, D39-L, D58-L, D59-L. Refined by: D85-L (the AUTO-able objective axes reduce to `strategy` + `lens`; `goal` is dropped from the runtime-state axis set and inlined into the agent role prompt). Supersedes: mode-only vocabulary, extension-local mutable state as authority, storing the foreground role as independent session state, the "runtime bundle / role preset" as one knob deriving model/thinking/resources, and binding prompt-resource location to `src/.pi/context/`. - **D34-L — Command containment separates visibility suppression from effect blocking.** Current Pi extension seams can hide unsupported slash suggestions with autocomplete wrapping and can cancel branch/session effects through lifecycle hooks, but they cannot strictly suppress exact interactive built-in commands before `InteractiveMode` dispatches them. Brunch-owned commands must use product-specific names and route writes through Brunch handlers/`CommandExecutor`; extension command collisions are not an override mechanism. Strict built-in command/keybinding policy is a Pi upstream/API ask, while POC safety relies on hiding generic affordances, blocking dangerous effects (`/fork`, `/clone`, raw session replacement), allowing native `/tree` as inspection/navigation, and failing fast on branched transcripts. Brunch's command-policy code should live in `src/.pi/extensions/commands/policy.ts`, merging branch/session-effect blocking with any product command allow/deny behavior instead of preserving a branch-only module. Depends on: D2-L, D24-L, A18-L. Supersedes: treating extension `input` handlers or command-name collisions as built-in command allowlisting. - **D35-L — Dynamic TUI chrome is a Brunch projection wrapper over Pi UI primitives.** The architectural commitment is that downstream TUI affordances call one Brunch-owned renderer (`renderBrunchChrome` or its successor) with a single activated product-state value rather than scattering raw `ctx.ui.setHeader` / `setFooter` / `setWidget` / title / working-indicator calls; the wrapper is stateless projection over canonical workspace/session/graph facts, never its own mutable state. Chrome is a project-first shell surface with selected-spec context — project name labels the cwd container, spec title labels the selected graph, session label distinguishes transcript instances — and a session label must never replace spec identity or graph truth. Chrome must not consume the status-key namespace for its own summary (`ctx.ui.setStatus` stays a lateral channel for other extensions), must not advertise unwired affordances, and RPC clients must rely only on surfaces Pi actually emits (header/footer/working-indicator are TUI-only in current Pi RPC mode). Current chrome state shape, render surfaces, telemetry/refresh, startup-header behavior, and status-key filtering live in [`src/.pi/extensions/chrome/README.md`](src/.pi/extensions/chrome/README.md); launch/activation wiring lives in [`src/.pi/extensions/README.md`](src/.pi/extensions/README.md). Depends on: D2-L, D21-L, D34-L, A18-L. Supersedes: treating Pi UI methods as direct downstream affordance APIs, rendering placeholder session state such as `unbound` after a session is activated, consuming the status-key namespace for chrome's own static summary, using spec title as the default session label, or allowing two unchanged Brunch-created default names to collide in one cwd, and the earlier resume/open-launches-stay-quiet clause (superseded 2026-06-11: the shipped, test-locked behavior headers every non-cancel activation). -- **D52-L — Source topology targets `src/{app, workspace, scripts, agents, .pi, db, graph, session, projections, renderers, rpc, web}` with directed layer dependencies.** Reusable projection and rendering modules live in top-level `src/projections/` and `src/renderers/` rather than whichever domain or adapter first needed them; `src/agents/` is the Pi-independent owner for Brunch-authored LLM context ingress (currently bundled agent prompt bodies, prompt-resource skills, prompt composition, prompt-resource/tool legality, seed context composition, and the central registry for prompt/skill file paths, with later context-rendering moves tracked by the LLM-context refactor; the `projections/session/runtime-policy.ts` registry import is a temporary migration edge until the foreground roster moves under `agents/runtime/`); domain layers (`graph/`, `session/`) and the reusable `projections` / `renderers` / `agents` layers must not import adapters, transports, app entrypoints, or web code; `graph/` is the only layer that imports `db/`, plus the single sanctioned `db/`→`graph/schema/kinds.ts` taxonomy edge (D73-L). The concrete per-directory ownership, layout sketch, and full import matrix are owned by [`src/README.md`](src/README.md). Depends on: D2-L, D4-L, D39-L, D40-L. Refined by: D73-L. Supersedes: scattering session domain files at `src/` root; treating Pi-only agents as a host-independent top-level `src/.pi/` layer; nesting prompt composition under `src/.pi/context/`; treating reusable `project` / `format` helpers as owned by whichever adapter first needed them; treating retired `.pi/agents` / `.pi/skills` locations as the long-term conceptual owner for Brunch-authored model-facing content. +- **D52-L — Source topology targets `src/{app, workspace, scripts, agents, .pi, db, graph, session, projections, renderers, rpc, web}` with directed layer dependencies.** Reusable projection and rendering modules live in top-level `src/projections/` and `src/renderers/` rather than whichever domain or adapter first needed them; `src/agents/` is the Pi-independent owner for Brunch-authored LLM context ingress (currently bundled agent prompt bodies, prompt-resource skills, prompt composition, prompt-resource/tool legality, seed context composition, reusable agent-visible renderers, adapter-local tool/session text promoted into contexts, and the central registry for prompt/skill file paths; the `projections/session/runtime-policy.ts` registry import is a temporary migration edge until the foreground roster moves under `agents/runtime/`); domain layers (`graph/`, `session/`) and the reusable `projections` / `renderers` / `agents` layers must not import adapters, transports, app entrypoints, or web code; `graph/` is the only layer that imports `db/`, plus the single sanctioned `db/`→`graph/schema/kinds.ts` taxonomy edge (D73-L). The concrete per-directory ownership, layout sketch, and full import matrix are owned by [`src/README.md`](src/README.md). Depends on: D2-L, D4-L, D39-L, D40-L. Refined by: D73-L. Supersedes: scattering session domain files at `src/` root; treating Pi-only agents as a host-independent top-level `src/.pi/` layer; nesting prompt composition under `src/.pi/context/`; treating reusable `project` / `format` helpers as owned by whichever adapter first needed them; treating retired `.pi/agents` / `.pi/skills` locations as the long-term conceptual owner for Brunch-authored model-facing content. - **D73-L — Domain enum taxonomy is owned by drizzle-free schema leaves; persistence and adapters are consumers, not the source.** The closed enum `const` arrays that define graph vocabulary — node kinds (`INTENT_KINDS`, `ORACLE_KINDS`, `DESIGN_KINDS`, `PLAN_KINDS`), `NODE_PLANES` (`intent`/`oracle`/`design`/`plan`), `NODE_BASES`, `EDGE_CATEGORIES`, `EDGE_STANCES`, `READINESS_BANDS`, `LENS_AFFINITIES`, `GAP_DISPOSITIONS`, and `GAP_PREDICATE_KINDS` — live in `graph/schema/kinds.ts`, a pure constants leaf that imports nothing (no drizzle, no `graph/atoms`). Both `db/schema.ts` (for `text({ enum })` column constraints, including the previously-inlined `plane` columns) and `graph/` domain modules import the arrays from this leaf; `graph/index.ts` re-exports them from the leaf so non-graph layers still avoid importing `db/` directly (I26-L). Session runtime axis vocabulary mirrors the same ownership direction in `session/schema/kinds.ts`: that leaf imports nothing and owns the `op_mode`, agent-role, `strategy`, `lens`, `auto`, and display-only planned mode choices consumed by `session/runtime-state.ts`, `projections/session/*`, and `agents/runtime/state.ts`; it deliberately contains no `goal` axis and no retired `READINESS_GRADES`. Derivations stay where they are read: `NODE_KIND_METADATA`, `formatGraphNodeCode`, `parseGraphNodeCode`, and `intentKindCategory` remain in `graph/schema/nodes.ts` (D62-L). The motivating defect: because `db/schema.ts` eagerly evaluates `sqliteTable(...)` and `verbatimModuleSyntax` emits even type-only imports at runtime, any value-import path from `web/` into the old taxonomy location pulled Drizzle into the browser bundle. Locating taxonomy in a drizzle-free leaf makes the `web/` build target structurally Drizzle-free (I44-L) and corrects the ownership direction so the domain, not the persistence layer, owns its vocabulary. Vocabulary migration status: `READINESS_GRADES` is retired (readiness is no longer a stored grade, D45-L), `ELICITATION_BACKLOG_STATUSES` is replaced by the `elicitation_gaps` disposition + predicate-shape enums (D65-L), and `READINESS_BANDS` stays. Depends on: D16-L, D52-L, D54-L, D62-L, D63-L, D64-L; I26-L. Supersedes: `db/schema.ts` owning the shared enum `const` arrays and the "enum literals flow outward from `db/schema.ts`" posture; the triplicated inline `['intent','oracle','design','plan']` plane literals. #### Data model & vocabulary diff --git a/src/.pi/__tests__/graph-tools.test.ts b/src/.pi/__tests__/graph-tools.test.ts index 565ec8ceb..ff806d998 100644 --- a/src/.pi/__tests__/graph-tools.test.ts +++ b/src/.pi/__tests__/graph-tools.test.ts @@ -1,6 +1,7 @@ import { Value } from 'typebox/value'; import { describe, expect, it } from 'vitest'; +import { formatMutateGraphResult } from '../../agents/contexts/graph/commit-result.js'; import { formatGraphOverview } from '../../agents/contexts/graph/graph-slice.js'; import { createDb, type BrunchDb } from '../../db/connection.js'; import { CommandExecutor } from '../../graph/command-executor.js'; @@ -15,10 +16,7 @@ import { type GraphVisibility, } from '../../graph/queries.js'; import { READINESS_BANDS } from '../../graph/schema/kinds.js'; -import { - translateMutateGraph, - formatMutateGraphResult, -} from '../extensions/brunch-data/graph/command-adapter.js'; +import { translateMutateGraph } from '../extensions/brunch-data/graph/command-adapter.js'; import { registerBrunchGraph, type GraphReaders } from '../extensions/brunch-data/graph/index.js'; import { ReadGraphParams } from '../extensions/brunch-data/graph/tool-schemas.js'; diff --git a/src/.pi/extensions/brunch-data/elicitation/index.ts b/src/.pi/extensions/brunch-data/elicitation/index.ts index bba8100dc..46b1ed0fe 100644 --- a/src/.pi/extensions/brunch-data/elicitation/index.ts +++ b/src/.pi/extensions/brunch-data/elicitation/index.ts @@ -19,6 +19,10 @@ import type { Static } from '@earendil-works/pi-ai'; import type { ExtensionAPI } from '@earendil-works/pi-coding-agent'; +import { + formatElicitationAgenda, + formatElicitationUpdateResult, +} from '../../../../agents/contexts/elicitation.js'; import { sortElicitationGapsForAsking } from '../../../../graph/elicitation-driver.js'; import type { CommandExecutor, @@ -156,12 +160,10 @@ export function registerBrunchElicitation(pi: ExtensionAPI, deps: BrunchElicitat const result: SpawnResult | DispositionResult = params.action === 'spawn' ? executeSpawn(deps, params) : executeSetDisposition(deps, params); - const text = - result.status === 'success' - ? `${params.action === 'spawn' ? 'Spawned gap' : 'Updated gap disposition'} (lsn ${result.lsn}).` - : `STRUCTURAL_ILLEGAL\n${result.diagnostics.map((d) => `- ${d.field}: ${d.message}`).join('\n')}`; - - return { content: [{ type: 'text' as const, text }], details: result }; + return { + content: [{ type: 'text' as const, text: formatElicitationUpdateResult(result, params.action) }], + details: result, + }; }, }); } @@ -236,33 +238,3 @@ function executeSetDisposition( resolvedByNodeId, }); } - -function formatElicitationAgenda( - agenda: readonly ElicitationGap[], - others: readonly ElicitationGap[] | undefined, -): string { - const lines: string[] = []; - if (agenda.length === 0) { - lines.push('[Elicitation agenda] No elicitation gaps are currently open for the selected spec.'); - } else { - lines.push(`[Elicitation agenda] ${agenda.length} open question(s), ranked:`); - agenda.forEach((gap, index) => { - lines.push( - `${index + 1}. ${oneLine(gap.question)} (refers to: ${gap.refersTo} · band: ${gap.band} · importance: ${gap.importance} · coverage: ${gap.coverage})`, - ); - }); - } - if (others && others.length > 0) { - lines.push(''); - lines.push(`[Not on the agenda] ${others.length} gap(s):`); - for (const gap of others) { - const state = gap.answered ? 'answered' : gap.disposition; - lines.push(`- ${oneLine(gap.question)} (${state})`); - } - } - return lines.join('\n'); -} - -function oneLine(value: string): string { - return value.trim().replaceAll(/\s+/g, ' '); -} diff --git a/src/.pi/extensions/brunch-data/graph/command-adapter.ts b/src/.pi/extensions/brunch-data/graph/command-adapter.ts index df62c651a..623520e3c 100644 --- a/src/.pi/extensions/brunch-data/graph/command-adapter.ts +++ b/src/.pi/extensions/brunch-data/graph/command-adapter.ts @@ -14,14 +14,11 @@ import type { GraphMutationNodeRef, GraphMutationOp, MutateGraphInput, - MutateGraphResult, - MutateGraphSuccess, RoleNamedEdgeDraft, StructuralIllegal, } from '../../../../graph/command-executor.js'; import { authoredEdgeEndpointFields } from '../../../../graph/index.js'; -import type { NodeNeighborhood } from '../../../../graph/queries.js'; -import { formatGraphNodeCode, parseGraphNodeCode } from '../../../../graph/schema/nodes.js'; +import { parseGraphNodeCode } from '../../../../graph/schema/nodes.js'; import type { ToolMutateGraphParams } from './tool-schemas.js'; export type ResolveGraphNodeCode = (code: string) => number | undefined; @@ -120,111 +117,3 @@ function normalizeEdgeRef( } return { status: 'valid', ref: { existing: nodeId } }; } - -// --------------------------------------------------------------------------- -// Result formatting -// --------------------------------------------------------------------------- - -/** - * Format a MutateGraphResult as Pi tool result text content. - * - * On success: human-readable summary with created ids. - * On structural_illegal: diagnostic listing for agent self-correction. - */ -export function formatMutateGraphResult(result: MutateGraphResult): string { - if (result.status === 'success') { - return formatCommitSuccess(result); - } - return formatStructuralIllegal(result); -} - -function formatCommitSuccess(result: MutateGraphSuccess): string { - const nodeEntries = Object.entries(result.createdNodes); - const lines: string[] = [`Graph mutated successfully (LSN ${result.lsn}).`]; - - if (nodeEntries.length > 0) { - const createdNodes = nodeEntries.map(([ref, node]) => `${ref} → ${node.code}`); - lines.push(`Nodes created: ${createdNodes.join(', ')}`); - } - if (result.createdEdges.length > 0) { - lines.push(`Edges created: ${result.createdEdges.map((id) => `#${id}`).join(', ')}`); - } - if (result.updatedNodes.length > 0) - lines.push(`Nodes updated: ${result.updatedNodes.map((id) => `#${id}`).join(', ')}`); - if (result.updatedEdges.length > 0) - lines.push(`Edges updated: ${result.updatedEdges.map((id) => `#${id}`).join(', ')}`); - if (result.deletedNodes.length > 0) - lines.push(`Nodes deleted: ${result.deletedNodes.map((id) => `#${id}`).join(', ')}`); - if (result.deletedEdges.length > 0) - lines.push(`Edges deleted: ${result.deletedEdges.map((id) => `#${id}`).join(', ')}`); - - return lines.join('\n'); -} - -export function formatStructuralIllegal(result: StructuralIllegal): string { - const lines: string[] = [ - 'STRUCTURAL_ILLEGAL: The batch was rejected. Fix the following issues and retry:', - '', - ]; - - for (const d of result.diagnostics) { - lines.push(`- ${d.field}: ${d.message}`); - } - - return lines.join('\n'); -} - -export interface RelatedNodesResult { - readonly status: 'success' | 'not_found'; - readonly anchors?: readonly NodeNeighborhood[]; -} - -export function formatRelatedNodesResult(result: RelatedNodesResult): string { - if (result.status === 'not_found') { - return 'One or more anchor nodes were not found in the selected spec.'; - } - - const anchors = result.anchors ?? []; - const found = anchors.filter( - (anchor): anchor is Extract => anchor.status === 'found', - ); - const related = new Map(found.flatMap((anchor) => anchor.related.map((node) => [node.id, node] as const))); - const edges = found.flatMap((anchor) => anchor.edges); - const nodesById = new Map([...found.map((anchor) => [anchor.node.id, anchor.node] as const), ...related]); - const lines = [ - `Related nodes: ${related.size} node(s), ${edges.length} edge(s).`, - `Anchors: ${found.map((anchor) => `[${formatGraphNodeCode(anchor.node.kind, anchor.node.kindOrdinal)}] ${anchor.node.title}`).join(', ')}`, - ]; - - if (related.size === 0) { - lines.push('Related: none'); - } else { - lines.push('Related:'); - for (const node of related.values()) { - lines.push( - ` - [${formatGraphNodeCode(node.kind, node.kindOrdinal)}] ${node.plane}/${node.kind}: "${node.title}"`, - ); - } - } - - if (edges.length === 0) { - lines.push('Edges: none'); - } else { - lines.push('Edges:'); - const anchorIds = new Set(found.map((anchor) => anchor.node.id)); - for (const edge of edges) { - const source = nodesById.get(edge.sourceId); - const target = nodesById.get(edge.targetId); - const sourceCode = source ? formatGraphNodeCode(source.kind, source.kindOrdinal) : `#${edge.sourceId}`; - const targetCode = target ? formatGraphNodeCode(target.kind, target.kindOrdinal) : `#${edge.targetId}`; - const direction = anchorIds.has(edge.sourceId) - ? 'outgoing' - : anchorIds.has(edge.targetId) - ? 'incoming' - : 'lateral'; - lines.push(` - ${sourceCode} -[${edge.category}/${direction}]-> ${targetCode}`); - } - } - - return lines.join('\n'); -} diff --git a/src/.pi/extensions/brunch-data/graph/index.ts b/src/.pi/extensions/brunch-data/graph/index.ts index 1f29e1d24..3b70d4b87 100644 --- a/src/.pi/extensions/brunch-data/graph/index.ts +++ b/src/.pi/extensions/brunch-data/graph/index.ts @@ -2,8 +2,13 @@ import type { ExtensionAPI, ToolDefinition } from '@earendil-works/pi-coding-agent'; +import { + formatMutateGraphResult, + formatStructuralIllegal, +} from '../../../../agents/contexts/graph/commit-result.js'; import { formatGraphOverview } from '../../../../agents/contexts/graph/graph-slice.js'; import { formatNeighborhood } from '../../../../agents/contexts/graph/node-neighborhood.js'; +import { formatRelatedNodesResult } from '../../../../agents/contexts/graph/related-nodes.js'; import type { CommandExecutor } from '../../../../graph/command-executor.js'; import type { EdgeCategory, @@ -20,12 +25,7 @@ import type { } from '../../../../graph/index.js'; import { graphMutationProductUpdates, type ProductUpdatePublisher } from '../../../../rpc/product-updates.js'; import { stampOwnMutationWatermark } from '../../../../session/prepare-next-turn.js'; -import { - translateMutateGraph, - formatMutateGraphResult, - formatRelatedNodesResult, - formatStructuralIllegal, -} from './command-adapter.js'; +import { translateMutateGraph } from './command-adapter.js'; import { MutateGraphParams, ReadGraphParams } from './tool-schemas.js'; export interface GraphReaders { diff --git a/src/.pi/extensions/brunch-data/reconciliation/index.ts b/src/.pi/extensions/brunch-data/reconciliation/index.ts index 6d1c6db4b..3e5cab333 100644 --- a/src/.pi/extensions/brunch-data/reconciliation/index.ts +++ b/src/.pi/extensions/brunch-data/reconciliation/index.ts @@ -11,6 +11,10 @@ import type { Static } from '@earendil-works/pi-ai'; import type { ExtensionAPI } from '@earendil-works/pi-coding-agent'; +import { + formatReconciliationNeeds, + formatReconciliationUpdateResult, +} from '../../../../agents/contexts/graph/reconciliation-needs.js'; import type { CommandExecutor, CreateReconNeedResult, @@ -126,11 +130,10 @@ export function registerBrunchReconciliation(pi: ExtensionAPI, deps: BrunchRecon async execute(_toolCallId, params) { const result = executeUpdate(deps, params); - const text = - result.status === 'success' - ? `${params.action === 'create' ? 'Created reconciliation need' : 'Resolved reconciliation need'} (lsn ${result.lsn}).` - : `STRUCTURAL_ILLEGAL\n${result.diagnostics.map((d) => `- ${d.field}: ${d.message}`).join('\n')}`; - return { content: [{ type: 'text' as const, text }], details: result }; + return { + content: [{ type: 'text' as const, text: formatReconciliationUpdateResult(result, params.action) }], + details: result, + }; }, }); } @@ -180,22 +183,3 @@ function executeResolve( function structuralIllegal(diagnostics: readonly Diagnostic[]): StructuralIllegal { return { status: 'structural_illegal', diagnostics }; } - -function formatReconciliationNeeds(needs: readonly ReconciliationNeed[]): string { - if (needs.length === 0) return '[Reconciliation needs] No reconciliation needs are currently open.'; - return [ - `[Reconciliation needs] ${needs.length} open item(s):`, - ...needs.map( - (need, index) => - `${index + 1}. ${need.kind} ${formatTarget(need.target)}${need.rationale ? ` — ${oneLine(need.rationale)}` : ''}`, - ), - ].join('\n'); -} - -function formatTarget(target: ReconciliationNeed['target']): string { - return target.kind === 'edge' ? `(edge ${target.edgeId})` : `(nodes ${target.aId} ↔ ${target.bId})`; -} - -function oneLine(value: string): string { - return value.trim().replaceAll(/\s+/g, ' '); -} diff --git a/src/agents/contexts/README.md b/src/agents/contexts/README.md index a171cce68..616bac7d9 100644 --- a/src/agents/contexts/README.md +++ b/src/agents/contexts/README.md @@ -10,7 +10,8 @@ SPEC decisions: D52-L, D58-L, D60-L, D76-L, D78-L, D83-L, D91-L, D96-L contexts/ ├── primitives/ markdown, TOON, tree, and section formatting helpers ├── seeds/ per-turn pushed context blocks and origination seed payloads -├── graph/ graph overview/neighborhood and planned graph result text +├── graph/ graph overview/neighborhood, related-node, mutation, reconciliation text +├── elicitation.ts elicitation agenda/update text ├── workspace/ context text ├── specification/ context text ├── session/ runtime-frame and readiness text @@ -34,4 +35,4 @@ Context golden files live beside their tests under `__snapshots__/` and use stoc ## Migration note -Reusable agent-visible renderers have moved here from `src/renderers/`. `src/renderers/` remains for human/product-only text such as print-mode workspace state and debug transcript output. Later refactor items promote adapter-local model text (for example related-node, mutation-result, elicitation, and reconciliation tool text) into this subtree. +Reusable agent-visible renderers have moved here from `src/renderers/`, and formerly adapter-local model text for graph mutation/related reads plus elicitation/reconciliation register tools now lives here too. `src/renderers/` remains for human/product-only text such as print-mode workspace state and debug transcript output. diff --git a/src/agents/contexts/elicitation.ts b/src/agents/contexts/elicitation.ts new file mode 100644 index 000000000..1be7ebc90 --- /dev/null +++ b/src/agents/contexts/elicitation.ts @@ -0,0 +1,46 @@ +import type { Diagnostic, ElicitationGap, StructuralIllegal } from '../../graph/index.js'; + +export function formatElicitationAgenda( + agenda: readonly ElicitationGap[], + others: readonly ElicitationGap[] | undefined, +): string { + const lines: string[] = []; + if (agenda.length === 0) { + lines.push('[Elicitation agenda] No elicitation gaps are currently open for the selected spec.'); + } else { + lines.push(`[Elicitation agenda] ${agenda.length} open question(s), ranked:`); + agenda.forEach((gap, index) => { + lines.push( + `${index + 1}. ${oneLine(gap.question)} (refers to: ${gap.refersTo} · band: ${gap.band} · importance: ${gap.importance} · coverage: ${gap.coverage})`, + ); + }); + } + if (others && others.length > 0) { + lines.push(''); + lines.push(`[Not on the agenda] ${others.length} gap(s):`); + for (const gap of others) { + const state = gap.answered ? 'answered' : gap.disposition; + lines.push(`- ${oneLine(gap.question)} (${state})`); + } + } + return lines.join('\n'); +} + +type ElicitationUpdateResult = { readonly status: 'success'; readonly lsn: number } | StructuralIllegal; + +export function formatElicitationUpdateResult( + result: ElicitationUpdateResult, + action: 'spawn' | 'set_disposition', +): string { + if (result.status === 'success') + return `${action === 'spawn' ? 'Spawned gap' : 'Updated gap disposition'} (lsn ${result.lsn}).`; + return formatStructuralIllegal(result.diagnostics); +} + +function formatStructuralIllegal(diagnostics: readonly Diagnostic[]): string { + return `STRUCTURAL_ILLEGAL\n${diagnostics.map((diagnostic) => `- ${diagnostic.field}: ${diagnostic.message}`).join('\n')}`; +} + +function oneLine(value: string): string { + return value.trim().replaceAll(/\s+/g, ' '); +} diff --git a/src/agents/contexts/graph/README.md b/src/agents/contexts/graph/README.md index 421535318..16df4c1d3 100644 --- a/src/agents/contexts/graph/README.md +++ b/src/agents/contexts/graph/README.md @@ -2,4 +2,4 @@ SPEC decisions: D60-L, D62-L, D83-L, D97-L -Owns reusable model-facing graph text: full selected-spec graph overviews, anchored neighborhoods, and planned graph result/context surfaces. Callers provide already-read graph/projection inputs; this directory formats them without reading storage or registering tools. +Owns reusable model-facing graph text: full selected-spec graph overviews, anchored neighborhoods, related-node reads, mutate-graph command results, and reconciliation-need agenda/update text. Callers provide already-read graph/projection inputs; this directory formats them without reading storage or registering tools. diff --git a/src/agents/contexts/graph/commit-result.ts b/src/agents/contexts/graph/commit-result.ts index 539c01f36..c90ff6be0 100644 --- a/src/agents/contexts/graph/commit-result.ts +++ b/src/agents/contexts/graph/commit-result.ts @@ -1,14 +1,46 @@ -/** - * Formats projected mutate_graph mutation results into model-facing text. - * - * Input: - * - projected output from projections/graph/commit-result.ts - * - * Output: - * - markdown summary for success or structural diagnostics - * - * Replaces/adapts: - * - .pi/extensions/brunch-data/graph/command-adapter.ts commit result formatting - */ - -export {}; +import type { + MutateGraphResult, + MutateGraphSuccess, + StructuralIllegal, +} from '../../../graph/command-executor.js'; + +/** Format a mutate_graph result as model-facing tool-result text. */ +export function formatMutateGraphResult(result: MutateGraphResult): string { + if (result.status === 'success') return formatCommitSuccess(result); + return formatStructuralIllegal(result); +} + +function formatCommitSuccess(result: MutateGraphSuccess): string { + const nodeEntries = Object.entries(result.createdNodes); + const lines: string[] = [`Graph mutated successfully (LSN ${result.lsn}).`]; + + if (nodeEntries.length > 0) { + const createdNodes = nodeEntries.map(([ref, node]) => `${ref} → ${node.code}`); + lines.push(`Nodes created: ${createdNodes.join(', ')}`); + } + if (result.createdEdges.length > 0) + lines.push(`Edges created: ${result.createdEdges.map((id) => `#${id}`).join(', ')}`); + if (result.updatedNodes.length > 0) + lines.push(`Nodes updated: ${result.updatedNodes.map((id) => `#${id}`).join(', ')}`); + if (result.updatedEdges.length > 0) + lines.push(`Edges updated: ${result.updatedEdges.map((id) => `#${id}`).join(', ')}`); + if (result.deletedNodes.length > 0) + lines.push(`Nodes deleted: ${result.deletedNodes.map((id) => `#${id}`).join(', ')}`); + if (result.deletedEdges.length > 0) + lines.push(`Edges deleted: ${result.deletedEdges.map((id) => `#${id}`).join(', ')}`); + + return lines.join('\n'); +} + +export function formatStructuralIllegal(result: StructuralIllegal): string { + const lines: string[] = [ + 'STRUCTURAL_ILLEGAL: The batch was rejected. Fix the following issues and retry:', + '', + ]; + + for (const diagnostic of result.diagnostics) { + lines.push(`- ${diagnostic.field}: ${diagnostic.message}`); + } + + return lines.join('\n'); +} diff --git a/src/agents/contexts/graph/reconciliation-needs.ts b/src/agents/contexts/graph/reconciliation-needs.ts index 16f6f987f..e15bd387b 100644 --- a/src/agents/contexts/graph/reconciliation-needs.ts +++ b/src/agents/contexts/graph/reconciliation-needs.ts @@ -1,15 +1,36 @@ -/** - * Formats projected reconciliation-need context into model-facing text. - * - * Input: - * - projected output from projections/graph/reconciliation-needs.ts - * - * Output: - * - markdown-framed TOON or equivalent compact text for LLM consumption - * - * Future users: - * - pushed prompt context - * - future graph read surfaces covering reconciliation work - */ - -export {}; +import type { Diagnostic, ReconciliationNeed, StructuralIllegal } from '../../../graph/index.js'; + +export function formatReconciliationNeeds(needs: readonly ReconciliationNeed[]): string { + if (needs.length === 0) return '[Reconciliation needs] No reconciliation needs are currently open.'; + return [ + `[Reconciliation needs] ${needs.length} open item(s):`, + ...needs.map( + (need, index) => + `${index + 1}. ${need.kind} ${formatTarget(need.target)}${need.rationale ? ` — ${oneLine(need.rationale)}` : ''}`, + ), + ].join('\n'); +} + +type ReconciliationUpdateResult = { readonly status: 'success'; readonly lsn: number } | StructuralIllegal; + +export function formatReconciliationUpdateResult( + result: ReconciliationUpdateResult, + action: 'create' | 'resolve', +): string { + if (result.status === 'success') { + return `${action === 'create' ? 'Created reconciliation need' : 'Resolved reconciliation need'} (lsn ${result.lsn}).`; + } + return formatStructuralIllegal(result.diagnostics); +} + +function formatStructuralIllegal(diagnostics: readonly Diagnostic[]): string { + return `STRUCTURAL_ILLEGAL\n${diagnostics.map((diagnostic) => `- ${diagnostic.field}: ${diagnostic.message}`).join('\n')}`; +} + +function formatTarget(target: ReconciliationNeed['target']): string { + return target.kind === 'edge' ? `(edge ${target.edgeId})` : `(nodes ${target.aId} ↔ ${target.bId})`; +} + +function oneLine(value: string): string { + return value.trim().replaceAll(/\s+/g, ' '); +} diff --git a/src/agents/contexts/graph/related-nodes.ts b/src/agents/contexts/graph/related-nodes.ts new file mode 100644 index 000000000..e6a2d9835 --- /dev/null +++ b/src/agents/contexts/graph/related-nodes.ts @@ -0,0 +1,55 @@ +import type { NodeNeighborhood } from '../../../graph/queries.js'; +import { formatGraphNodeCode } from '../../../graph/schema/nodes.js'; + +export interface RelatedNodesResult { + readonly status: 'success' | 'not_found'; + readonly anchors?: readonly NodeNeighborhood[]; +} + +export function formatRelatedNodesResult(result: RelatedNodesResult): string { + if (result.status === 'not_found') return 'One or more anchor nodes were not found in the selected spec.'; + + const anchors = result.anchors ?? []; + const found = anchors.filter( + (anchor): anchor is Extract => anchor.status === 'found', + ); + const related = new Map(found.flatMap((anchor) => anchor.related.map((node) => [node.id, node] as const))); + const edges = found.flatMap((anchor) => anchor.edges); + const nodesById = new Map([...found.map((anchor) => [anchor.node.id, anchor.node] as const), ...related]); + const lines = [ + `Related nodes: ${related.size} node(s), ${edges.length} edge(s).`, + `Anchors: ${found.map((anchor) => `[${formatGraphNodeCode(anchor.node.kind, anchor.node.kindOrdinal)}] ${anchor.node.title}`).join(', ')}`, + ]; + + if (related.size === 0) { + lines.push('Related: none'); + } else { + lines.push('Related:'); + for (const node of related.values()) { + lines.push( + ` - [${formatGraphNodeCode(node.kind, node.kindOrdinal)}] ${node.plane}/${node.kind}: "${node.title}"`, + ); + } + } + + if (edges.length === 0) { + lines.push('Edges: none'); + } else { + lines.push('Edges:'); + const anchorIds = new Set(found.map((anchor) => anchor.node.id)); + for (const edge of edges) { + const source = nodesById.get(edge.sourceId); + const target = nodesById.get(edge.targetId); + const sourceCode = source ? formatGraphNodeCode(source.kind, source.kindOrdinal) : `#${edge.sourceId}`; + const targetCode = target ? formatGraphNodeCode(target.kind, target.kindOrdinal) : `#${edge.targetId}`; + const direction = anchorIds.has(edge.sourceId) + ? 'outgoing' + : anchorIds.has(edge.targetId) + ? 'incoming' + : 'lateral'; + lines.push(` - ${sourceCode} -[${edge.category}/${direction}]-> ${targetCode}`); + } + } + + return lines.join('\n'); +} From 50712d8d791db8ad13ce25d3d282f290a08a1b7e Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Thu, 25 Jun 2026 18:55:24 +0200 Subject: [PATCH 17/54] Point prompt and context test scripts at agents --- memory/REFACTOR.md | 2 +- package.json | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/memory/REFACTOR.md b/memory/REFACTOR.md index fdbc99ab9..cc1afaf56 100644 --- a/memory/REFACTOR.md +++ b/memory/REFACTOR.md @@ -70,7 +70,7 @@ callers after refactor 5. ✓ Move per-turn pushed context composition and origination/session-entry seed composition into the new context seed home; update session and app callers to import from the central agent context layer. 6. ✓ Move reusable LLM-facing context renderers into the new context home, keeping product-only/human-only renderers outside unless they are deliberately agent-visible. 7. ✓ Promote adapter-local LLM text formatting into the new context home: graph mutation result text, related-node text, elicitation agenda/update text, and reconciliation agenda/update text. -8. Consolidate golden/preview tests for prompt composition and agent context renderers under the new agent tree, preserving existing semantic invariants while making “what enters the model” reviewable in one place. +8. ✓ Consolidate golden/preview tests for prompt composition and agent context renderers under the new agent tree, preserving existing semantic invariants while making “what enters the model” reviewable in one place. 9. Add a boundary guard that prevents Pi extension adapters from owning Brunch-authored tool/session/prompt content text, while explicitly allowing tool schemas, labels, descriptions, and prompt snippets to remain adapter-owned. 10. Reconcile topology READMEs, SPEC/PLAN references, build scripts, and direct-import docs so the new owner is canonical and the old `.pi`/`renderers` ownership claims are retired. diff --git a/package.json b/package.json index 459fbd354..b5cb7f0a0 100644 --- a/package.json +++ b/package.json @@ -46,9 +46,9 @@ "test:renderers": "vitest --run src/agents/contexts src/renderers", "test:renderers:watch": "vitest src/agents/contexts src/renderers", "test:renderers:update": "vitest --run src/agents/contexts src/renderers --update", - "test:prompts": "vitest --run src/.pi/extensions/system-prompts", - "test:prompts:watch": "vitest src/.pi/extensions/system-prompts", - "test:prompts:update": "vitest --run src/.pi/extensions/system-prompts --update", + "test:prompts": "vitest --run src/agents/runtime src/.pi/extensions/agent-runtime/system-prompts", + "test:prompts:watch": "vitest src/agents/runtime src/.pi/extensions/agent-runtime/system-prompts", + "test:prompts:update": "vitest --run src/agents/runtime src/.pi/extensions/agent-runtime/system-prompts --update", "lint": "oxlint", "lint:fix": "oxlint --fix", "fmt": "oxfmt", From 56302b959d29531bd5fffc85551ad360fe78c0d4 Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Thu, 25 Jun 2026 18:59:40 +0200 Subject: [PATCH 18/54] Guard agent context text ownership --- memory/REFACTOR.md | 2 +- src/.pi/__tests__/architecture.test.ts | 27 +++++++++++++++++++ .../exchanges/present-review-set.ts | 20 +++++++------- .../extensions/exchanges/request-response.ts | 3 ++- src/agents/contexts/README.md | 2 ++ .../contexts/exchanges/present-review-set.ts | 10 +++++++ .../contexts/exchanges/request-response.ts | 7 +++++ 7 files changed, 58 insertions(+), 13 deletions(-) create mode 100644 src/agents/contexts/exchanges/request-response.ts diff --git a/memory/REFACTOR.md b/memory/REFACTOR.md index cc1afaf56..4994fd206 100644 --- a/memory/REFACTOR.md +++ b/memory/REFACTOR.md @@ -71,7 +71,7 @@ callers after refactor 6. ✓ Move reusable LLM-facing context renderers into the new context home, keeping product-only/human-only renderers outside unless they are deliberately agent-visible. 7. ✓ Promote adapter-local LLM text formatting into the new context home: graph mutation result text, related-node text, elicitation agenda/update text, and reconciliation agenda/update text. 8. ✓ Consolidate golden/preview tests for prompt composition and agent context renderers under the new agent tree, preserving existing semantic invariants while making “what enters the model” reviewable in one place. -9. Add a boundary guard that prevents Pi extension adapters from owning Brunch-authored tool/session/prompt content text, while explicitly allowing tool schemas, labels, descriptions, and prompt snippets to remain adapter-owned. +9. ✓ Add a boundary guard that prevents Pi extension adapters from owning Brunch-authored tool/session/prompt content text, while explicitly allowing tool schemas, labels, descriptions, and prompt snippets to remain adapter-owned. 10. Reconcile topology READMEs, SPEC/PLAN references, build scripts, and direct-import docs so the new owner is canonical and the old `.pi`/`renderers` ownership claims are retired. ## Decisions diff --git a/src/.pi/__tests__/architecture.test.ts b/src/.pi/__tests__/architecture.test.ts index 822ed3a76..7fe133fa7 100644 --- a/src/.pi/__tests__/architecture.test.ts +++ b/src/.pi/__tests__/architecture.test.ts @@ -32,6 +32,15 @@ const runtimeRegistryExpectations = [ }, ]; +const modelTextAdapterDirs = [ + join(projectRoot, 'src/.pi/extensions/brunch-data'), + join(projectRoot, 'src/.pi/extensions/exchanges'), +]; + +const allowedModelTextAdapterFiles = new Set([ + 'src/.pi/extensions/exchanges/shared/markdown.ts', // TUI display adapter, not provider text ownership. +]); + describe('agents topology', () => { it('removes the legacy .pi context source', async () => { await expect(readdir(legacyContextPath)).rejects.toThrow(); @@ -59,6 +68,24 @@ describe('agents topology', () => { } } }); + + it('keeps Pi tool adapters from owning Brunch-authored model text', async () => { + const files = (await Promise.all(modelTextAdapterDirs.map((dir) => listSourceFiles(dir)))).flat(); + + for (const file of files) { + const rel = relative(projectRoot, file); + if (rel.endsWith('.test.ts') || rel.includes('/__tests__/') || allowedModelTextAdapterFiles.has(rel)) { + continue; + } + const content = await readFile(file, 'utf8'); + expect(content, `${rel} must import model-facing formatters from src/agents/contexts`).not.toMatch( + /function\s+format[A-Z]/, + ); + expect(content, `${rel} must not inline provider text content`).not.toMatch( + /content:\s*\[\{\s*type:\s*'text'\s+as\s+const,\s*text:\s*`/, + ); + } + }); }); async function listSourceFiles(dir: string): Promise { diff --git a/src/.pi/extensions/exchanges/present-review-set.ts b/src/.pi/extensions/exchanges/present-review-set.ts index 693c9053c..5116f00f7 100644 --- a/src/.pi/extensions/exchanges/present-review-set.ts +++ b/src/.pi/extensions/exchanges/present-review-set.ts @@ -1,6 +1,9 @@ import { defineTool } from '@earendil-works/pi-coding-agent'; -import { formatPresentReviewSet } from '../../../agents/contexts/exchanges/present-review-set.js'; +import { + formatExchangeStructuralIllegal, + formatPresentReviewSet, +} from '../../../agents/contexts/exchanges/present-review-set.js'; import type { CommandExecutor, StructuralIllegal } from '../../../graph/command-executor.js'; import type { ReviewSetProposalPayload } from '../../../graph/review-set.js'; import { projectPresentReviewSet } from '../../../projections/exchanges/present-review-set.js'; @@ -47,7 +50,10 @@ export function createPresentReviewSetTool(deps?: ReviewSetStructuredExchangeDep { field: 'present_review_set', message: 'review-set graph dependencies unavailable' }, ], }; - return { content: [{ type: 'text' as const, text: formatStructuralIllegal(details) }], details }; + return { + content: [{ type: 'text' as const, text: formatExchangeStructuralIllegal(details) }], + details, + }; } const dryRun = deps.commandExecutor.dryRunAcceptReviewSet({ @@ -57,7 +63,7 @@ export function createPresentReviewSetTool(deps?: ReviewSetStructuredExchangeDep }); if (dryRun.status === 'structural_illegal') { return { - content: [{ type: 'text' as const, text: formatStructuralIllegal(dryRun) }], + content: [{ type: 'text' as const, text: formatExchangeStructuralIllegal(dryRun) }], details: dryRun, }; } @@ -86,11 +92,3 @@ export function createPresentReviewSetTool(deps?: ReviewSetStructuredExchangeDep } export const presentReviewSetTool = createPresentReviewSetTool(); - -function formatStructuralIllegal(result: { - readonly diagnostics: readonly { readonly field: string; readonly message: string }[]; -}): string { - return ['# STRUCTURAL_ILLEGAL', '', ...result.diagnostics.map((d) => `- ${d.field}: ${d.message}`)].join( - '\n', - ); -} diff --git a/src/.pi/extensions/exchanges/request-response.ts b/src/.pi/extensions/exchanges/request-response.ts index 380b396c2..8ae97ab0c 100644 --- a/src/.pi/extensions/exchanges/request-response.ts +++ b/src/.pi/extensions/exchanges/request-response.ts @@ -1,5 +1,6 @@ import { defineTool } from '@earendil-works/pi-coding-agent'; +import { formatRequestResponseDiagnostic } from '../../../agents/contexts/exchanges/request-response.js'; import type { LiveExchangeAwaiter } from '../../../session/live-exchange-broker.js'; import { piSchema } from './pi-schema.js'; import { @@ -44,7 +45,7 @@ function diagnostic( } function diagnosticResult(details: RequestResponseDiagnosticDetails) { - return { content: [{ type: 'text' as const, text: `# Response\n\n_${details.message}_` }], details }; + return { content: [{ type: 'text' as const, text: formatRequestResponseDiagnostic(details) }], details }; } function assertNever(value: never): never { diff --git a/src/agents/contexts/README.md b/src/agents/contexts/README.md index 616bac7d9..010208b57 100644 --- a/src/agents/contexts/README.md +++ b/src/agents/contexts/README.md @@ -29,6 +29,8 @@ rules: renderers/ x> agents/contexts/ [human/product renderers do not own model text] ``` +`src/.pi/__tests__/architecture.test.ts` guards the adapter half of this boundary for `brunch-data` and structured-exchange tools: Pi adapters may own schemas, labels, descriptions, prompt snippets, and TUI rendering, but provider-visible Brunch text must be imported from this subtree rather than formatted inline. + ## Snapshot convention Context golden files live beside their tests under `__snapshots__/` and use stock Vitest file snapshots. There is no separate preview writer. diff --git a/src/agents/contexts/exchanges/present-review-set.ts b/src/agents/contexts/exchanges/present-review-set.ts index 7d3bf1066..74c105f88 100644 --- a/src/agents/contexts/exchanges/present-review-set.ts +++ b/src/agents/contexts/exchanges/present-review-set.ts @@ -1,6 +1,16 @@ import { roleNamedEdgeDraftEndpoints } from '../../../graph/command-executor/role-named-edge-draft.js'; import type { PresentReviewSetProjection } from '../../../projections/exchanges/present-review-set.js'; +export function formatExchangeStructuralIllegal(result: { + readonly diagnostics: readonly { readonly field: string; readonly message: string }[]; +}): string { + return [ + '# STRUCTURAL_ILLEGAL', + '', + ...result.diagnostics.map((diagnostic) => `- ${diagnostic.field}: ${diagnostic.message}`), + ].join('\n'); +} + export function formatPresentReviewSet(projection: PresentReviewSetProjection): string { const payload = projection.payload; const lines = [ diff --git a/src/agents/contexts/exchanges/request-response.ts b/src/agents/contexts/exchanges/request-response.ts new file mode 100644 index 000000000..8c102c9ef --- /dev/null +++ b/src/agents/contexts/exchanges/request-response.ts @@ -0,0 +1,7 @@ +export interface RequestResponseDiagnosticTextInput { + readonly message: string; +} + +export function formatRequestResponseDiagnostic(input: RequestResponseDiagnosticTextInput): string { + return `# Response\n\n_${input.message}_`; +} From f3302469bff2b7935443aa09662012c51164c653 Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Fri, 26 Jun 2026 08:59:40 +0200 Subject: [PATCH 19/54] Reconcile agent context topology docs --- docs/design/ONTOLOGY_REVIEW_PROTOCOL.md | 2 +- docs/design/STRUCTURED_EXCHANGE_COLLAPSE.md | 2 +- memory/PLAN.md | 6 +- memory/REFACTOR.md | 103 --- memory/SPEC.md | 8 +- src/.pi/README.md | 3 +- src/.pi/extensions/README.md | 6 +- .../dev-mode/session-query/README.md | 2 +- src/.pi/extensions/exchanges/README.md | 2 +- .../extensions/exchanges/schemas/README.md | 4 +- src/README.md | 12 +- src/agents/README.md | 4 +- src/agents/contexts/seeds/README.md | 2 +- src/agents/prompts/README.md | 12 +- src/agents/runtime/README.md | 2 +- src/app/README.md | 5 +- src/treedocs.yaml | 816 ++++++++---------- 17 files changed, 417 insertions(+), 574 deletions(-) delete mode 100644 memory/REFACTOR.md diff --git a/docs/design/ONTOLOGY_REVIEW_PROTOCOL.md b/docs/design/ONTOLOGY_REVIEW_PROTOCOL.md index df20a5d04..806825c36 100644 --- a/docs/design/ONTOLOGY_REVIEW_PROTOCOL.md +++ b/docs/design/ONTOLOGY_REVIEW_PROTOCOL.md @@ -413,7 +413,7 @@ Routing: Heuristics are the **method-differentiation layer** (§6.1), not ancillary. They are currently scattered (the kind-discrimination rules in -`src/.pi/skills/methods/commit-graph/SKILL.md`, `ELICITATION_QUESTIONS.md`, +`src/agents/skills/methods/commit-graph/SKILL.md`, `ELICITATION_QUESTIONS.md`, `ELICITATION_LENSES.md`, this doc); collating them into one inlinable source is a named follow-on (§9). diff --git a/docs/design/STRUCTURED_EXCHANGE_COLLAPSE.md b/docs/design/STRUCTURED_EXCHANGE_COLLAPSE.md index d65d0a63c..31c7a6b7e 100644 --- a/docs/design/STRUCTURED_EXCHANGE_COLLAPSE.md +++ b/docs/design/STRUCTURED_EXCHANGE_COLLAPSE.md @@ -112,7 +112,7 @@ Borrow **C's derive-from-pending mechanism as the migration path**: during trans - **`src/.pi/extensions/exchanges/`** — the four `request_*.ts` tools collapse into one `request-response.ts`; `present_*.ts` tools gain a server-owned `responseKind` on their result; `respondsToPresentTool` removed from params. - **`src/projections/exchanges/`** — request projections collapse behind a normalized pending-exchange record (`{ presentToolName, exchangeId, responseKind, consumedAt? }`); recovery maps unmatched presents → `request_response`. -- **`src/renderers/exchanges/`** — present renderers unchanged; the four request renderers collapse behind one dispatcher keyed by `responseKind`. +- **`src/agents/contexts/exchanges/`** — present renderers unchanged; the four request renderers collapse behind one dispatcher keyed by `responseKind`. - **`schemas/`** — delete `respondsToPresentTool` and model-facing `tool_meta.next`; add a `request_response` params schema (`{ exchangeId }` + small shared options); `tool_meta.curr/next` becomes internal/derivation-only, no longer a model contract. ### Interaction with the broker / web-driver path (D84-L/D86-L) diff --git a/memory/PLAN.md b/memory/PLAN.md index b9a19d752..417d3b74e 100644 --- a/memory/PLAN.md +++ b/memory/PLAN.md @@ -56,7 +56,7 @@ Brunch-next has delivered the original composition spine: the host, sealed Pi pr context-pipeline/ ├── PULL graph + session reads ✓ done ├── PROJECT projections/ ✓ done -├── RENDER renderers/ ◐ open: renderer-golden-coverage (FE-870) +├── RENDER agents/contexts + renderers/ ◐ open: renderer-golden-coverage (FE-870) └── COMPOSE system-prompts + skills ✓ done* *COMPOSE has one deferred full-stack real-rendered-context tripwire owned by RENDER. @@ -83,7 +83,7 @@ context-pipeline/ - `orchestrator-tool-port` (FE-1087) — **scoped.** Port the external `brunch cook` orchestrator into execute-mode tools without granting the foreground orchestrator direct shell/file-write authority. First active scope: `memory/cards/orchestrator-tool-port--plan-check-tool.md`. - `elicitor-project` (FE-1085) — **design-gated.** Cross-plane derivation (requirements -> design, design -> oracles) remains undesigned under A33-L; run `ln-design` before any scope/build. - `data-model-legibility` — **active.** Single canonical home for data-model meta-guidance, with closed-vocabulary tables generated from the typed `graph/schema` sources (D97-L). Design verdict landed (Shape C); first tracer landed (generated kind→band table + `check:data-model` drift guard, cited by `methods/capture`). Remaining: edge-category + detail-form tables, the authored judgment layer, and the subtypes→`detail` remodel. -- `renderer-golden-coverage` — **active parallel coverage track.** Remaining RENDER work: ``, `renderGraphSeed`, `exchanges/*`, `formatRelatedNodesResult` relocation/repair, and the `brunch print` fork. +- `renderer-golden-coverage` — **active parallel coverage track.** Remaining RENDER work lives by audience: model-facing context surfaces under `agents/contexts/`, human/product text under `renderers/`. Remaining rows need fresh scoping against `src/agents/contexts/README.md` and `src/renderers/README.md`. - `exchange-symmetry-audit` — **earned cleanup.** Delete-oriented audit of the exchange projection/renderer split; not a capability blocker. ### Parallel / Low-Conflict @@ -165,7 +165,7 @@ context-pipeline/ - **Kind:** coverage + build / hardening - **Status:** next / active parallel. Substrate, ``, ``, graph overview/neighborhood renders, and band-filtered graph slice hardening are done. Remaining work needs a fresh `ln-scope` pass. - **Objective:** Finish the RENDER stage: ``, `renderGraphSeed`, `exchanges/*`, `formatRelatedNodesResult` structural-leak repair + relocation into `renderers/`, and the `brunch print` house-style-vs-status fork. -- **Acceptance:** `src/renderers/README.md` carries the closed ledger; required rows are built in the house style and locked with focused goldens/semantic invariants; no adapter/transport imports enter `renderers/`. +- **Acceptance:** `src/agents/contexts/README.md` and `src/renderers/README.md` carry the audience split; required model-facing rows are built in the house style and locked with focused goldens/semantic invariants; no adapter/transport imports enter `agents/contexts/` or `renderers/`. - **Traceability:** D19-L, D52-L, D60-L, D62-L, D83-L. ### exchange-symmetry-audit diff --git a/memory/REFACTOR.md b/memory/REFACTOR.md deleted file mode 100644 index 4994fd206..000000000 --- a/memory/REFACTOR.md +++ /dev/null @@ -1,103 +0,0 @@ -## Problem Statement - -Brunch currently has no single owner for **LLM context ingress**. Agent-facing text is spread by adapter or historical layer: prompt bodies live under the Pi surface, prompt composition and prompt-resource legality live under Pi extension internals, pushed context seed composition lives under session modules, reusable context renderers live under the generic renderer seam, and several tool-result texts are still formatted inside tool adapters. - -That makes it hard to review or test the actual bytes and vocabulary entering the model. A developer asking “what does the agent see?” has to traverse Pi extensions, session helpers, renderers, graph adapters, and skill metadata. The current topology answers “which runtime adapter consumes this?” better than “who controls agent context?” - -```pseudo tree -current LLM-context ownership -├── Pi markdown bodies -│ └── .pi agent body resources -├── Pi prompt extension internals -│ ├── foreground prompt composition -│ ├── prompt-resource manifest rendering -│ └── prompt-resource/tool legality helper -├── session helpers -│ ├── per-turn pushed context seed -│ └── origination/session-entry context seed -├── generic renderers -│ ├── graph/spec/workspace/session context text -│ └── exchange markdown text -└── adapter-local formatters - ├── graph mutation/read-related diagnostics - ├── elicitation agenda text - └── reconciliation agenda text -``` - -## Solution - -Create `src/agents/` as the central, Pi-independent owner of agent prompt bodies, Brunch prompt-resource skills, prompt composition policy, and all Brunch-authored LLM-context renderings. Pi extensions, session origination code, probes, and tools become callers: they gather data, then ask `src/agents/` to produce the model-facing text. - -Keep tool schemas beside the tools. Schemas are the adapter contract. But tool **content**, session-entry **content**, and prompt **content** should be developed and golden-tested in the agent context home. - -```pseudo tree -desired LLM-context ownership -src/agents/ -├── prompts/ -│ ├── foreground agent bodies -│ └── background agent bodies with spawn metadata where needed -├── skills/ -│ ├── strategies/ -│ ├── lenses/ -│ └── methods/ -├── runtime/ -│ ├── foreground prompt composition -│ ├── prompt-resource manifest loading/rendering -│ ├── prompt-resource/tool legality projection -│ └── agent body loading/path registry -└── contexts/ - ├── primitives: markdown, section, tree, toon helpers for agent context - ├── graph: overview, neighborhood, related, mutation-result text - ├── workspace/specification/session: context blocks and runtime frames - ├── exchanges: present/request markdown - ├── elicitation: gap agenda/update text - ├── reconciliation: need agenda/update text - └── seeds: per-turn and origination/session-entry context composition - -callers after refactor -├── Pi extensions: register tools/hooks, gather deps, call src/agents/* for text -├── session origination: gathers graph/workspace/gaps, calls src/agents/contexts/seeds -├── dev/probes: use agent-context renderers for probe-visible artifacts -└── product CLI/human renderers: remain outside unless the text enters LLM context -``` - -## Commits - -1. ✓ Add the new `src/agents` topology and README as an empty central owner, and introduce central path/registry helpers that still point at the existing prompt and skill homes. -2. ✓ Move agent prompt bodies into the new prompt home and update foreground/body loading, background subagent loading, build asset copying, and prompt-body tests without changing prompt bytes. -3. ✓ Move Brunch prompt-resource skills into the new skills home and update manifest loading, build asset copying, resource-location snapshots, and skill topology docs without changing skill bytes. -4. ✓ Move foreground prompt composition and prompt-resource legality code into the new runtime home; leave Pi extension code as a thin hook adapter that imports the central composer. -5. ✓ Move per-turn pushed context composition and origination/session-entry seed composition into the new context seed home; update session and app callers to import from the central agent context layer. -6. ✓ Move reusable LLM-facing context renderers into the new context home, keeping product-only/human-only renderers outside unless they are deliberately agent-visible. -7. ✓ Promote adapter-local LLM text formatting into the new context home: graph mutation result text, related-node text, elicitation agenda/update text, and reconciliation agenda/update text. -8. ✓ Consolidate golden/preview tests for prompt composition and agent context renderers under the new agent tree, preserving existing semantic invariants while making “what enters the model” reviewable in one place. -9. ✓ Add a boundary guard that prevents Pi extension adapters from owning Brunch-authored tool/session/prompt content text, while explicitly allowing tool schemas, labels, descriptions, and prompt snippets to remain adapter-owned. -10. Reconcile topology READMEs, SPEC/PLAN references, build scripts, and direct-import docs so the new owner is canonical and the old `.pi`/`renderers` ownership claims are retired. - -## Decisions - -- Build or modify a new `agents` module as the central LLM-context ingress owner. -- Keep the name `skills` for Brunch prompt-resource skill bodies, not `resources`. -- Keep Pi extension modules as runtime adapters only: registration, hook binding, dependency gathering, and tool schemas stay there; Brunch-authored model-facing content moves out. -- Keep tool schemas near their tools because they are provider/adapter contracts, even though they are visible to the model. -- Split generic rendering by audience: agent-visible context text moves to the agent context home; product-only human renderers stay outside unless later made model-visible. -- Background agent metadata may remain frontmatter in prompt markdown, but discovery remains code-owned through explicit registries. -- Topology READMEs to update or retire include the Pi surface README, Pi agents README, Pi skills README, Pi extensions README, renderer README, session README, and root source README. - -## Testing Decisions - -- Existing prompt composition previews and renderer goldens are the core safety net; move or re-anchor them rather than rewriting expected content casually. -- First priority is byte stability for existing prompt bodies, skill bodies, and model-facing render outputs. -- Keep semantic invariants for graph code rendering, no raw structural leaks where already locked, readiness estimate parity, prompt-resource legality, and active-tool/prompt-manifest alignment. -- Add one boundary/architecture test after the moves: extension adapters may register schemas and call central renderers, but should not define Brunch-authored result/session/prompt body text locally. -- Run the full gate after each commit-sized move because this is import/topology-heavy and build asset copying is part of product behavior. - -## Out of Scope - -- Changing graph ontology vocabulary, node kinds, edge categories, readiness bands, or detail schemas. -- Rewriting prompt/skill prose for quality beyond path-sensitive updates required by the move. -- Changing tool schemas, tool availability, runtime policy behavior, or subagent spawnability. -- Changing Pi sealed-profile behavior or ambient discovery rules. -- Building a new renderer framework or generalized preview harness beyond relocating existing goldens and adding the boundary guard. -- Moving product-only CLI text unless it becomes agent-visible. -- Changing transcript debug rendering unless a later slice decides transcript reports are agent context rather than human/probe artifacts. diff --git a/memory/SPEC.md b/memory/SPEC.md index c3752b0f5..dee7afb46 100644 --- a/memory/SPEC.md +++ b/memory/SPEC.md @@ -427,13 +427,13 @@ src/agents/ SKILL.md path list + family/kind/legality metadata src/.pi/ extensions/ - system-prompts/ [ts] before_agent_start hook adapter + world-read cache - runtime/ [ts] Pi active-tool adapter over agents/runtime policy - context/*.ts [ts] D60-L pull-tool context surface (read_workspace_context, read_session_context) + agent-runtime/system-prompts/ [ts] before_agent_start hook adapter + world-read cache + agent-runtime/runtime/ [ts] Pi active-tool adapter over agents/runtime policy + brunch-data/context/*.ts [ts] D60-L pull-tool context surface (read_workspace_context, read_session_context) ``` - Manifest availability is code-owned, not filesystem-discovered: `agents/runtime/state.ts` binds each legal axis value to an explicit `src/agents/skills///SKILL.md` path and each live agent role to its `src/agents/prompts//SYSTEM.md` location. It loads prompt-resource `name` and `description` from `SKILL.md` frontmatter through pi's loader with `includeDefaults: false` and an explicit `skillPaths` list. `composeAgentPrompt()` emits legal resource bindings; the prompt extension reads the selected agent body explicitly and passes it into the pure composer. This keeps the legal set sealed while making the file body/frontmatter the description source of truth. -- The D60-L agent-context orchestration layer (TypeScript) lives in `.pi/extensions/agent-runtime/system-prompts/seed/` (pushed contexts) and `.pi/extensions/brunch-data/context/` (read tools), surfaced as the header's compact pushed context or via the read tools; reusable text renderers live in `renderers/`, and contexts are not part of the `read`-on-demand resource manifest and carry no `` family. +- The D60-L agent-context orchestration layer (TypeScript) lives in `src/agents/contexts/`: `seeds/` owns compact pushed/origination context, while `workspace/`, `specification/`, `session/`, `graph/`, `elicitation.ts`, and `exchanges/` own provider-visible context-tool and tool-result text. `.pi/extensions/agent-runtime/system-prompts/` and `.pi/extensions/brunch-data/context/` are adapters that gather data and call those renderers. Contexts are not part of the `read`-on-demand resource manifest and carry no `` family. - Workspace **posture** is workspace-scoped product state persisted in `.brunch/workspace.json`, not spec state, session state, or graph truth. D57-L keeps it off the spec row and graph; D58-L composition injects known posture values into the runtime header as an axis of agent influence, and the `capture-posture` goal (D59-L) can confirm or refine those values conversationally. - Readiness is judged just-in-time per requested capability, not as a user-facing workflow stepper, a stored grade, a session-local phase, or a graph-node-kind whitelist. There is no `readiness_grade` on the spec row (D45-L); capability-readiness (D74-L) is evaluated over the relevant `elicitation_gaps`, and D64-L readiness bands describe non-exclusive evidence groupings feeding the readiness-estimate rollup, goal selection, and context filtering. The soft readiness estimate may surface in UI but gates nothing. A future structural milestone gate for export/plan/execute op-modes is deferred until such an op-mode exists; before readiness grows beyond the current tracer, Brunch still needs a real evaluator path for `manual` gaps and a more differentiated per-capability map than the shared grounding floor (A27-L). - Prompt resources and Pi skills are both progressive-disclosure mechanisms, but they are not authority. Brunch code owns runtime-state projection, legal tuple filtering, capability-readiness/allow-list gating, tool activation, and tool-call blocking. Explicit user/system pins remain visible when readiness negotiates; negotiation changes AUTO choices, method/tool availability, and response posture rather than authority. Pi-native skills may be used for startup-scoped capabilities; runtime-state-specific objective/method availability is advertised through Brunch's per-turn manifest so ambient user/project resources cannot leak into product behavior. diff --git a/src/.pi/README.md b/src/.pi/README.md index 93f74a3c9..4f0bacbee 100644 --- a/src/.pi/README.md +++ b/src/.pi/README.md @@ -15,7 +15,8 @@ This directory is Brunch's sealed Pi-harness surface. It contains product extens - Pi JSONL/session semantics and workspace/session coordination — `session/`. - Product JSON-RPC handlers — `rpc/`. - React client UI — `web/`. -- Reusable product projection/rendering once hoisted — target `projections/` and `renderers/` seams. +- Brunch-authored model-facing prompt/context text — `agents/`. +- Reusable product projection/rendering — `projections/`, `agents/contexts/`, and `renderers/` by audience. ## Layout diff --git a/src/.pi/extensions/README.md b/src/.pi/extensions/README.md index 3c2fa0058..4b34397c0 100644 --- a/src/.pi/extensions/README.md +++ b/src/.pi/extensions/README.md @@ -11,7 +11,7 @@ Pi-facing registration and adaptation only: lifecycle hooks, agent tool definiti - Agent role prompt definitions, skill resource bodies, prompt composition, and prompt-resource legality — `agents/`. `agent-runtime/` is now only the Pi hook/tool adapter for that central policy. - Graph truth, graph mutation policy, or graph readers — top-level `graph/`. - Pi JSONL/session semantics, runtime-state projection, workspace coordination, or transcript exchange projection — top-level `session/`, `projections/`, and related domain seams. -- Reusable DTO projection or reusable markdown/text rendering — top-level `projections/` and `renderers/`. +- Reusable DTO projection or reusable markdown/text rendering — top-level `projections/`, `agents/contexts/` for model-facing text, and `renderers/` for human/product text. - Product transport handlers — `rpc/`, `app/`, and `web/`. ## Directory layout @@ -51,7 +51,7 @@ extensions/ ```pseudo rules: - .pi/extensions/* -> agents/, .pi/components/, graph/, session/, projections/, renderers/ [adapter imports allowed] + .pi/extensions/* -> agents/, .pi/components/, graph/, session/, projections/ [adapter imports allowed] .pi/extensions/* x> db/ [no direct storage] graph/, session/ x> .pi/ [domain layers never import adapters] agents/prompts/ x> .pi/extensions/ [prompt bodies do not register Pi hooks] @@ -67,4 +67,4 @@ rules: `exchanges/schemas/` is the intentional current exception to "adapter-only": it owns the Zod-authored structured-exchange details schema per D37-L/D41-L until a separate schema-ownership slice moves or names that seam. Zod-to-Pi `TSchema` conversion is confined to two per-plane adapters: `exchanges/pi-schema.ts` (structured-exchange) and `shared/pi-tool-schema.ts` (dev-gated query tools). Both export JSON Schema draft 2020-12 (`z.toJSONSchema`), which strict provider validators require. -`exchanges/shared/markdown.ts` contains Pi-rendering helpers. Move only reusable product markdown/text rendering into the future renderer seam; keep Pi `renderCall` / `renderResult` widgets and UI-only message components local to `.pi/`. +`exchanges/shared/markdown.ts` contains Pi-rendering helpers. Keep Pi `renderCall` / `renderResult` widgets and UI-only message components local to `.pi/`; reusable provider-visible exchange result text belongs in `agents/contexts/exchanges/`. diff --git a/src/.pi/extensions/dev-mode/session-query/README.md b/src/.pi/extensions/dev-mode/session-query/README.md index 1595342fc..26b77b97d 100644 --- a/src/.pi/extensions/dev-mode/session-query/README.md +++ b/src/.pi/extensions/dev-mode/session-query/README.md @@ -9,7 +9,7 @@ Dev-gated, read-only Pi tool registration for `brunch_session_query`: predicate ## Does NOT own - Provider-payload capture or `/introspect` reporting — sibling `../introspection/` owns the payload plane. -- Prompt-resource manifests or product prompt behavior — `.pi/extensions/agent-runtime/runtime/` (manifest/legality), `.pi/extensions/agent-runtime/system-prompts/` (composition), and the `.pi/agents/` + `.pi/skills/` markdown bodies. +- Prompt-resource manifests or product prompt behavior — `src/agents/runtime/` (manifest/legality + composition), `src/agents/prompts/`, and `src/agents/skills/`, with `.pi/extensions/agent-runtime/` only adapting those seams into Pi hooks. - Product transcript/domain projection — top-level `session/` and `projections/` seams. ## Boundary rules diff --git a/src/.pi/extensions/exchanges/README.md b/src/.pi/extensions/exchanges/README.md index 351f45f55..b54df7496 100644 --- a/src/.pi/extensions/exchanges/README.md +++ b/src/.pi/extensions/exchanges/README.md @@ -61,7 +61,7 @@ so the tool casts the runtime `ctx` once at the boundary. ## Dependency rules ```pseudo -exchanges/* -> schemas/, projections/exchanges/, renderers/exchanges/ +exchanges/* -> schemas/, projections/exchanges/, agents/contexts/exchanges/ exchanges/shared/ -> shared UI dispatch only; no tool-result detail literals exchanges/schemas/ -> zod only (pi-schema.ts is the lone TSchema adapter) ``` diff --git a/src/.pi/extensions/exchanges/schemas/README.md b/src/.pi/extensions/exchanges/schemas/README.md index 15717ce64..df8c1a35d 100644 --- a/src/.pi/extensions/exchanges/schemas/README.md +++ b/src/.pi/extensions/exchanges/schemas/README.md @@ -44,14 +44,14 @@ chain active Pi tool / session trigger / RPC editor relay -> parse params or relay payload at the entry boundary -> projections/exchanges/* constructs details -> relevant details Zod schema parses result - -> renderers/exchanges/* renders durable markdown + -> agents/contexts/exchanges/* renders provider-visible durable markdown ``` - Active `.pi/extensions/exchanges/*.ts` files own Pi registration and UI collection only. - `../pi-schema.ts` is the only Zod JSON Schema to Pi `TSchema` adapter. - `present_review_set.payload` is a **graph-owned boundary-teaching schema** (`zReviewSetProposalPayloadForBoundary` in `graph/review-set.ts`), not `z.unknown()`: the param boundary rejects a JSON string, the wrong tool's shape (e.g. `mutate_graph`'s `{createBasis, ops}`), and malformed nested companions such as `grounding: string`. The full requiredness/field-diagnostic contract stays owned by `validateReviewSetPayloadShape` in the same graph module; the boundary schema advertises `lens`, `epistemicStatus`, `grounding {summary, support[]}`, `pitch {title, narrative}`, `entityDrafts[]`, and role-named `edgeDrafts[]` so the model sees the nested structure before the deep validator runs. - `projections/exchanges/*` is the only construction boundary for active present/request `toolResult.details`. -- `renderers/exchanges/*` owns durable markdown for active present/request emissions. +- `agents/contexts/exchanges/*` owns durable provider-visible markdown for active present/request emissions. - Session pending exchange recovery projects from canonical present/request details; it does not author a TypeBox semantic schema. - The RPC/editor relay is an intentional current product fallback and must still emit canonical details through projectors. - The proof-era `brunch.structured_exchange.result` details model is retired. diff --git a/src/README.md b/src/README.md index 4caf5170d..5591a07ab 100644 --- a/src/README.md +++ b/src/README.md @@ -47,10 +47,10 @@ rules: workspace/ -> constants/ or workspace-local files only projections/* -> agents/, graph/, session/, workspace/ [read/domain imports allowed; agents/ is temporary registry edge] renderers/* -> projections/, session/, workspace/ as needed for human/product input types - agents/ -> graph/, session/, renderers/ [agent-visible text over already-read facts] - .pi/ -> agents/, graph/, session/, projections/, renderers/ [Pi runtime adapters/resources] - rpc/ -> graph/, session/, projections/, renderers/ - app/ -> graph/, session/, projections/, renderers/ + agents/ -> graph/, projections/, session/, workspace/ [agent-visible text over already-read facts] + .pi/ -> agents/, graph/, session/, projections/ [Pi runtime adapters/resources] + rpc/ -> graph/, session/, projections/ + app/ -> agents/, graph/, session/, projections/, renderers/ graph/, session/ x> .pi/, rpc/, app/, web/ projections/ x> .pi/, rpc/, app/, web/ renderers/ x> .pi/, rpc/, app/, web/ @@ -62,9 +62,9 @@ Rules: - `workspace/` owns cwd-scoped identity, inventory, and workspace default-state persistence. It must not import Pi, session, graph, DB, projection, renderer, adapter, transport, app, or web modules. - `graph/` imports from `db/`. No other layer imports `db/` directly. - `agents/` owns the Brunch-authored LLM-context ingress seam. Today it hosts agent prompt bodies, prompt-resource skills, prompt composition, prompt-resource/tool legality, context seed composition, reusable agent-visible context renderers, and the central file registry. The current `projections/session/runtime-policy.ts` import of this registry is a migration edge only: once the foreground roster moves under `agents/runtime/`, projections should stop depending on `agents/`. -- `.pi/` owns Pi-harness extensions/components and no longer hosts Brunch-authored prompt bodies or prompt-resource skills. +- `.pi/` owns Pi-harness extensions/components and no longer hosts Brunch-authored prompt bodies, prompt-resource skills, prompt composition, or provider-visible tool/session text. - `.pi/extensions/` registers Pi tools/hooks/UI affordances and delegates product semantics outward. -- `projections/` owns reusable structured output; `renderers/` owns reusable lossy text output. +- `projections/` owns reusable structured output; `agents/contexts/` owns reusable model-facing text; `renderers/` owns human/product-only lossy text output. - `web/` is a separate Vite build target. ## Migration notes diff --git a/src/agents/README.md b/src/agents/README.md index 75481ba50..7f578806a 100644 --- a/src/agents/README.md +++ b/src/agents/README.md @@ -23,7 +23,7 @@ agents/ rules: agents/registry.ts -> agents/prompts/*/SYSTEM.md [body file locations] agents/registry.ts -> agents/skills/*/*/SKILL.md [prompt-resource locations] - agents/contexts/ -> graph/, session/, renderers/ [agent-visible text over already-read facts] + agents/contexts/ -> graph/, projections/, session/, workspace/ [agent-visible text over already-read facts] .pi/extensions/* -> agents/ [adapters ask for Brunch-authored context] session/ -> agents/contexts/seeds/ [origination asks for seed payload text] projections/session/runtime-policy.ts -> agents/registry.ts [temporary roster-location edge] @@ -32,4 +32,4 @@ rules: ## Migration note -This directory is intentionally mid-migration. Agent prompt bodies, prompt-resource skills, prompt composition, prompt-resource/tool legality, seed context composition, and reusable agent-visible context renderers have moved here byte-stably. Later slices promote remaining adapter-local model text here; Pi extensions remain runtime adapters that register hooks/tools and call this layer. +Agent prompt bodies, prompt-resource skills, prompt composition, prompt-resource/tool legality, seed context composition, reusable agent-visible context renderers, and formerly adapter-local model-facing text live here. Pi extensions remain runtime adapters that register hooks/tools, gather data, and call this layer for Brunch-authored text. diff --git a/src/agents/contexts/seeds/README.md b/src/agents/contexts/seeds/README.md index 32161ad93..20a2ca31e 100644 --- a/src/agents/contexts/seeds/README.md +++ b/src/agents/contexts/seeds/README.md @@ -15,7 +15,7 @@ Both modules are pure over already-read data. Callers own PULL: graph reads, gap ```pseudo rules: - seeds/* -> graph/, renderers/, session/schema [format already-read facts] + seeds/* -> agents/contexts/*, graph/, session/schema [format already-read facts] .pi/extensions/ -> seeds/ [foreground/background prompt adapters] session/ -> seeds/origination.ts [append choreography uses seed text] seeds/ x> .pi/, app/, rpc/ [no host, adapter, or transport effects] diff --git a/src/agents/prompts/README.md b/src/agents/prompts/README.md index c6d1d2678..cbf615e2d 100644 --- a/src/agents/prompts/README.md +++ b/src/agents/prompts/README.md @@ -27,13 +27,13 @@ This directory is markdown-only. It carries no TypeScript and registers no Pi ho ## Does NOT own -- Foreground prompt composition + pushed seed contexts — `.pi/extensions/agent-runtime/system-prompts/` until the runtime/context move slices land. -- Background prompt assembly and injected-world child-session wiring — `.pi/extensions/subagents/`. -- Prompt-resource manifest selection + tool/method legality — `.pi/extensions/agent-runtime/runtime/` and `src/projections/session/runtime-policy.ts` until the runtime move slice lands. +- Foreground prompt composition, pushed seed contexts, prompt-resource manifest selection, or tool/method legality — `src/agents/runtime/` and `src/agents/contexts/seeds/`. +- Background prompt assembly and injected-world child-session wiring — `src/.pi/extensions/subagents/`. - Strategy/lens/method prompt-resource skills — `src/agents/skills/`. -- Reusable lossy text/markdown rendering — `renderers/` until agent-visible renderers move. -- Pi tool definitions, lifecycle hooks, UI, and background child-session loading/running — `.pi/extensions/*`. +- Reusable model-facing context text — `src/agents/contexts/`. +- Human/product-only text rendering — `src/renderers/`. +- Pi tool definitions, lifecycle hooks, UI, and background child-session loading/running — `src/.pi/extensions/*`. ## Migration note -This directory is the first moved content home under `src/agents/`. Pi extension code remains a runtime adapter: it loads foreground bodies and background agent definitions through `src/agents/registry.ts`, not through extension-local paths or directory discovery. `pi-coder` records Pi's `buildSystemPrompt` worked-example baseline while D58-L's augment-vs-replace question stays open. +Pi extension code remains a runtime adapter: it loads foreground bodies and background agent definitions through `src/agents/registry.ts`, not through extension-local paths or directory discovery. `pi-coder` records Pi's `buildSystemPrompt` worked-example baseline while D58-L's augment-vs-replace question stays open. diff --git a/src/agents/runtime/README.md b/src/agents/runtime/README.md index 04173ab71..2f631fb5a 100644 --- a/src/agents/runtime/README.md +++ b/src/agents/runtime/README.md @@ -21,7 +21,7 @@ runtime/ ```pseudo rules: agents/runtime -> agents/registry, agents/prompts, agents/skills - agents/runtime -> graph/, projections/, renderers/, session/ [read/projection types and helpers] + agents/runtime -> agents/contexts, graph/, projections/, session/ [read/projection types and helpers] .pi/extensions/agent-runtime/* -> agents/runtime [adapter calls central policy] agents/runtime x> .pi extension hooks/tools [no Pi registration side effects] ``` diff --git a/src/app/README.md b/src/app/README.md index f3688c147..9d704fb78 100644 --- a/src/app/README.md +++ b/src/app/README.md @@ -17,11 +17,12 @@ Current entrypoints: ## Does not own - Graph truth, command execution, or persistence — `graph/` and `db/`. -- Pi registrars, prompt resources, and reusable Pi UI components — `.pi/`. +- Pi registrars and reusable Pi UI components — `.pi/`. +- Agent prompt resources and model-facing context text — `agents/`. - Session transcript semantics, binding, and workspace/session coordination — `session/`. - JSON-RPC method semantics — `rpc/`. - React client code — `web/`. ## Dependency direction -`app/` may import from `.pi/`, `graph/`, `session/`, `rpc/`, `projections/`, and `renderers/` to compose product modes. Domain layers must not import `app/`. +`app/` may import from `.pi/`, `agents/`, `graph/`, `session/`, `rpc/`, `projections/`, and `renderers/` to compose product modes. Domain layers must not import `app/`. diff --git a/src/treedocs.yaml b/src/treedocs.yaml index 07c3d2078..79daf45a0 100644 --- a/src/treedocs.yaml +++ b/src/treedocs.yaml @@ -10,534 +10,478 @@ overrides: max_description_length: 120 use_gitignore: true project: - last_updated: '2026-06-24' + last_updated: '2026-06-25' name: src version: 0.0.0 schema_version: 0.2.0 -signature: sha256:213d96bfd3398512a050a2eb8cf00c18ba286c3e51a1bb19e2ebc7f11f585f39 +signature: sha256:396f29ef335a810829849d13163bf80bf75f19be8db7441923527b3815908c87 tree: .pi: - README.md: 'Documents pi runtime resources, extensions, skills, and TUI components.' - _doc: 'Pi runtime resources, extensions, skills, and TUI components.' - agents: - README.md: 'Documents pi agent role prompt definitions.' - _doc: 'Pi agent role prompt definitions.' - elicitor: - SYSTEM.md: 'Defines the elicitor Pi agent system prompt.' - _doc: 'Contains elicitor resources for agents.' - explorer: - SYSTEM.md: 'Defines the explorer Pi agent system prompt.' - _doc: 'Contains explorer resources for agents.' - pi-coder: - SYSTEM.md: 'Defines the pi coder Pi agent system prompt.' - _doc: 'Contains pi coder resources for agents.' - projector: - SYSTEM.md: 'Defines the projector Pi agent system prompt.' - _doc: 'Contains projector resources for agents.' - researcher: - SYSTEM.md: 'Defines the researcher Pi agent system prompt.' - _doc: 'Contains researcher resources for agents.' - reviewer: - SYSTEM.md: 'Defines the reviewer Pi agent system prompt.' - _doc: 'Contains reviewer resources for agents.' + README.md: 'Documents this source subtree.' components: - README.md: 'Documents reusable Pi TUI components and visual assets.' - _doc: 'Reusable Pi TUI components and visual assets.' - alternatives.ts: 'Implements alternatives for components.' - brunch-identity.ts: 'Implements brunch identity for components.' - brunch-version.ts: 'Implements brunch version for components.' - cards.ts: 'Implements cards for components.' - chrome-header.ts: 'Implements chrome header for components.' - lateral-padding.ts: 'Implements lateral padding for components.' - multi-choice-picker.ts: 'Implements multi choice picker for components.' + README.md: 'Documents this source subtree.' + alternatives.ts: 'Implements alternatives.' + brunch-identity.ts: 'Implements brunch identity.' + brunch-version.ts: 'Implements brunch version.' + cards.ts: 'Implements cards.' + chrome-header.ts: 'Implements chrome header.' + lateral-padding.ts: 'Implements lateral padding.' + multi-choice-picker.ts: 'Implements multi choice picker.' runtime-posture: - _doc: 'Runtime posture picker components.' - axis-picker.ts: 'Implements axis picker for runtime posture.' - strategy-picker.ts: 'Implements strategy picker for runtime posture.' + axis-picker.ts: 'Implements axis picker.' + strategy-picker.ts: 'Implements strategy picker.' tui-lab: - _doc: 'Shared experimental TUI styling primitives.' index.ts: 'Exports the public module surface.' - segment-track.ts: 'Implements segment track for tui lab.' - style-palette.ts: 'Implements style palette for tui lab.' + segment-track.ts: 'Implements segment track.' + style-palette.ts: 'Implements style palette.' workspace-dialog: - _doc: 'Workspace selection dialog model, view, and preflight runner.' assets: - _doc: 'Workspace dialog logo and image assets.' - brunch-logo-quad-56x18-240.ansi: 'ANSI Brunch logo art asset.' - brunch-logo-quad-56x18.ansi: 'ANSI Brunch logo art asset.' - brunch.png: 'Brunch logo image asset.' - component.ts: 'Implements component for workspace dialog.' + brunch-logo-quad-56x18-240.ansi: 'ANSI art asset.' + brunch-logo-quad-56x18.ansi: 'ANSI art asset.' + brunch.png: 'Image asset.' + component.ts: 'Implements component.' index.ts: 'Exports the public module surface.' - model.ts: 'Implements model for workspace dialog.' - preflight.ts: 'Implements preflight for workspace dialog.' - workspace-dialog.ts: 'Implements workspace dialog for components.' + model.ts: 'Implements model.' + preflight.ts: 'Implements preflight.' + workspace-dialog.ts: 'Implements workspace dialog.' extensions: - README.md: 'Documents pi extension registrars and Brunch tool adapters.' - _doc: 'Pi extension registrars and Brunch tool adapters.' + README.md: 'Documents this source subtree.' + agent-runtime: + index.ts: 'Exports the public module surface.' + orchestrator-stub: + index.ts: 'Exports the public module surface.' + runtime: + authority-matrix.test.ts: 'Tests authority matrix behavior.' + index.ts: 'Exports the public module surface.' + system-prompts: + index.ts: 'Exports the public module surface.' + world-reads.ts: 'Implements world reads.' + brunch-data: + context: + get-cwd.ts: 'Implements get cwd.' + get-specification.ts: 'Implements get specification.' + index.ts: 'Exports the public module surface.' + session-binding.ts: 'Implements session binding.' + elicitation: + index.test.ts: 'Tests index behavior.' + index.ts: 'Exports the public module surface.' + graph: + command-adapter.ts: 'Implements command adapter.' + index.ts: 'Exports the public module surface.' + tool-schemas.ts: 'Implements tool schemas.' + index.ts: 'Exports the public module surface.' + reconciliation: + index.test.ts: 'Tests index behavior.' + index.ts: 'Exports the public module surface.' chrome: - README.md: 'Documents pi shell chrome integration for Brunch.' - _doc: 'Pi shell chrome integration for Brunch.' + README.md: 'Documents this source subtree.' index.ts: 'Exports the public module surface.' commands: - _doc: 'Command policy extension wiring.' index.ts: 'Exports the public module surface.' - policy.ts: 'Defines policy for commands.' + policy.ts: 'Implements policy.' compaction: - _doc: 'Session compaction contract definitions.' index.ts: 'Exports the public module surface.' - context: - _doc: 'Pi context tools for workspace and specification reads.' - get-cwd.ts: 'Implements get cwd for context.' - get-specification.ts: 'Implements get specification for context.' - index.ts: 'Exports the public module surface.' - session-binding.ts: 'Implements session binding for context.' - elicitation: - _doc: 'Elicitation-gap Pi tools and tests.' - index.test.ts: 'Tests index test behavior.' + dev-mode: index.ts: 'Exports the public module surface.' + introspect-query: + README.md: 'Documents this source subtree.' + index.test.ts: 'Tests index behavior.' + index.ts: 'Exports the public module surface.' + introspection: + README.md: 'Documents this source subtree.' + debug-cache.ts: 'Implements debug cache.' + index.ts: 'Exports the public module surface.' + session-query: + README.md: 'Documents this source subtree.' + index.test.ts: 'Tests index behavior.' + index.ts: 'Exports the public module surface.' exchanges: - README.md: 'Documents structured-exchange Pi tools and UI sources.' - _doc: 'Structured-exchange Pi tools and UI sources.' + README.md: 'Documents this source subtree.' index.ts: 'Exports the public module surface.' - pi-schema.ts: 'Defines pi schema for exchanges.' - present-candidates.ts: 'Implements present candidates for exchanges.' - present-question.ts: 'Implements present question for exchanges.' - present-review-set.ts: 'Implements present review set for exchanges.' - request-response.ts: 'Implements request response for exchanges.' + pi-schema.ts: 'Implements pi schema.' + present-candidates.ts: 'Implements present candidates.' + present-question.ts: 'Implements present question.' + present-review-set.ts: 'Implements present review set.' + request-response.ts: 'Implements request response.' schemas: - README.md: 'Documents structured-exchange boundary schemas.' - _doc: 'Structured-exchange boundary schemas.' - capture.ts: 'Implements capture for schemas.' - editor.ts: 'Implements editor for schemas.' + README.md: 'Documents this source subtree.' + capture.ts: 'Implements capture.' + editor.ts: 'Implements editor.' index.ts: 'Exports the public module surface.' - params.ts: 'Implements params for schemas.' - present.ts: 'Implements present for schemas.' - request.ts: 'Implements request for schemas.' - shared.ts: 'Implements shared for schemas.' + params.ts: 'Implements params.' + present.ts: 'Implements present.' + request.ts: 'Implements request.' + shared.ts: 'Implements shared.' shared: - _doc: 'Shared structured-exchange UI source helpers.' - answer-source.ts: 'Implements answer source for shared.' - choice-source.ts: 'Implements choice source for shared.' - choices-editor.ts: 'Implements choices editor for shared.' - markdown.ts: 'Implements markdown for shared.' - recovery.ts: 'Implements recovery for shared.' - review-source.ts: 'Implements review source for shared.' - ui-context.ts: 'Implements ui context for shared.' - graph: - _doc: 'Graph Pi tool schemas and command adapters.' - command-adapter.ts: 'Implements command adapter for graph.' - index.ts: 'Exports the public module surface.' - tool-schemas.ts: 'Defines tool schemas for graph.' - introspect-query: - README.md: 'Documents dev-gated introspection query tool.' - _doc: 'Dev-gated introspection query tool.' - index.test.ts: 'Tests index test behavior.' - index.ts: 'Exports the public module surface.' - introspection: - README.md: 'Documents dev-only agent-input introspection capture.' - _doc: 'Dev-only agent-input introspection capture.' - debug-cache.ts: 'Implements debug cache for introspection.' - index.ts: 'Exports the public module surface.' + answer-source.ts: 'Implements answer source.' + choice-source.ts: 'Implements choice source.' + choices-editor.ts: 'Implements choices editor.' + markdown.ts: 'Implements markdown.' + recovery.ts: 'Implements recovery.' + review-source.ts: 'Implements review source.' + ui-context.ts: 'Implements ui context.' mentions: - _doc: 'Pi mention autocomplete integration.' - index.ts: 'Exports the public module surface.' - reconciliation: - _doc: 'Reconciliation-need Pi tools and tests.' - index.test.ts: 'Tests index test behavior.' - index.ts: 'Exports the public module surface.' - runtime: - _doc: 'Operational-mode policy and runtime state extension.' - authority-matrix.test.ts: 'Tests authority matrix test behavior.' index.ts: 'Exports the public module surface.' - state.test.ts: 'Tests state test behavior.' - state.ts: 'Implements state for runtime.' - session: - _doc: 'Pi session lifecycle hooks.' - lifecycle.test.ts: 'Tests lifecycle test behavior.' - lifecycle.ts: 'Implements lifecycle for session.' - session-query: - README.md: 'Documents dev session-log query tool.' - _doc: 'Dev session-log query tool.' - index.test.ts: 'Tests index test behavior.' + session-hooks: index.ts: 'Exports the public module surface.' + session: + lifecycle.test.ts: 'Tests lifecycle behavior.' + lifecycle.ts: 'Implements lifecycle.' shared: - _doc: 'Shared Pi extension schema and query helpers.' - pi-tool-schema.ts: 'Defines pi tool schema for shared.' - query-projection.ts: 'Projects query projection for shared.' + pi-tool-schema.ts: 'Implements pi tool schema.' + query-projection.ts: 'Implements query projection.' subagents: - README.md: 'Documents sealed Brunch subagent extension implementation.' - _doc: 'Sealed Brunch subagent extension implementation.' - agents.ts: 'Implements agents for subagents.' - config.json: 'Configures config data.' - config.ts: 'Implements config for subagents.' - index.ts: 'Exports the public module surface.' - prompt-assembly.ts: 'Implements prompt assembly for subagents.' - session.ts: 'Implements session for subagents.' - subagents.test.ts: 'Tests subagents test behavior.' - system-prompts: - __previews__: - _doc: 'Generated system-prompt previews.' - elicitor--auto-floor-gaps-open.md: 'Preview output for elicitor auto floor gaps open.' - elicitor--auto-high-coverage.md: 'Preview output for elicitor auto high coverage.' - elicitor--pinned-strategy-lens.md: 'Preview output for elicitor pinned strategy lens.' - elicitor--pushed-context.md: 'Preview output for elicitor pushed context.' - _doc: 'Dynamic system-prompt composition resources.' - compose.ts: 'Implements compose for system prompts.' + README.md: 'Documents this source subtree.' + agents.ts: 'Implements agents.' + config.json: 'Configuration data.' + config.ts: 'Implements config.' index.ts: 'Exports the public module surface.' - prompt-skills.ts: 'Implements prompt skills for system prompts.' - world-reads.ts: 'Implements world reads for system prompts.' + prompt-assembly.ts: 'Implements prompt assembly.' + session.ts: 'Implements session.' + subagents.test.ts: 'Tests subagents behavior.' tui-lab: - _doc: 'Pi TUI laboratory command extension.' index.ts: 'Exports the public module surface.' - web: - _doc: 'Web search and fetch Pi tools.' + web-tools: index.ts: 'Exports the public module surface.' - web-fetch.ts: 'Implements web fetch for web.' - web-search.ts: 'Implements web search for web.' - web-tools.test.ts: 'Tests web tools test behavior.' + web: + index.ts: 'Exports the public module surface.' + web-fetch.ts: 'Implements web fetch.' + web-search.ts: 'Implements web search.' + web-tools.test.ts: 'Tests web tools behavior.' workspace: - _doc: 'Workspace lifecycle and product-update hooks.' index.ts: 'Exports the public module surface.' - settings.json: 'Configures Brunch Pi extensions and runtime settings.' + settings.json: 'Configuration data.' + README.md: 'Documents this source subtree.' + agents: + README.md: 'Documents this source subtree.' + contexts: + README.md: 'Documents this source subtree.' + elicitation.ts: 'Implements elicitation.' + exchanges: + README.md: 'Documents this source subtree.' + present-candidates.ts: 'Implements present candidates.' + present-question.ts: 'Implements present question.' + present-review-set.ts: 'Implements present review set.' + request-answer.ts: 'Implements request answer.' + request-choice.ts: 'Implements request choice.' + request-choices.ts: 'Implements request choices.' + request-response.ts: 'Implements request response.' + request-review.ts: 'Implements request review.' + graph: + README.md: 'Documents this source subtree.' + __snapshots__: + graph-overview-kind-band-spread.md: 'Markdown resource.' + neighborhood-brunch-self-MOD1-hops2.md: 'Markdown resource.' + neighborhood-brunch-self-REQ1.md: 'Markdown resource.' + neighborhood-code-health-REQ1.md: 'Markdown resource.' + neighborhood-hub-REQ1-compact.md: 'Markdown resource.' + neighborhood-hub-REQ1-hops2.md: 'Markdown resource.' + neighborhood-hub-REQ1.md: 'Markdown resource.' + commit-result.ts: 'Implements commit result.' + graph-slice.ts: 'Implements graph slice.' + node-neighborhood.ts: 'Implements node neighborhood.' + reconciliation-needs.ts: 'Implements reconciliation needs.' + related-nodes.ts: 'Implements related nodes.' + primitives: + README.md: 'Documents this source subtree.' + markdown.ts: 'Implements markdown.' + section.ts: 'Implements section.' + toon.ts: 'Implements toon.' + tree.ts: 'Implements tree.' + seeds: + README.md: 'Documents this source subtree.' + origination.ts: 'Implements origination.' + turn-context.ts: 'Implements turn context.' + session: + README.md: 'Documents this source subtree.' + __snapshots__: + runtime-frame-ready.md: 'Markdown resource.' + readiness-estimate.ts: 'Implements readiness estimate.' + runtime-frame.ts: 'Implements runtime frame.' + specification: + README.md: 'Documents this source subtree.' + __snapshots__: + specification-context.md: 'Markdown resource.' + specification-context.ts: 'Implements specification context.' + workspace: + README.md: 'Documents this source subtree.' + __snapshots__: + workspace-cwd-context.md: 'Markdown resource.' + workspace-overview-context.md: 'Markdown resource.' + workspace-context.ts: 'Implements workspace context.' + prompts: + README.md: 'Documents this source subtree.' + elicitor: + SYSTEM.md: 'Defines the elicitor agent system prompt.' + explorer: + SYSTEM.md: 'Defines the explorer agent system prompt.' + orchestrator: + SYSTEM.md: 'Defines the orchestrator agent system prompt.' + pi-coder: + SYSTEM.md: 'Defines the pi-coder agent system prompt.' + projector: + SYSTEM.md: 'Defines the projector agent system prompt.' + researcher: + SYSTEM.md: 'Defines the researcher agent system prompt.' + reviewer: + SYSTEM.md: 'Defines the reviewer agent system prompt.' + registry.ts: 'Implements registry.' + runtime: + README.md: 'Documents this source subtree.' + __snapshots__: + elicitor--auto-floor-gaps-open.md: 'Markdown resource.' + elicitor--auto-high-coverage.md: 'Markdown resource.' + elicitor--pinned-strategy-lens.md: 'Markdown resource.' + elicitor--pushed-context.md: 'Markdown resource.' + compose.ts: 'Implements compose.' + prompt-skills.ts: 'Implements prompt skills.' + state.ts: 'Implements state.' skills: - README.md: 'Documents prompt-resource skills for Brunch agents.' + README.md: 'Documents this source subtree.' __fixtures__: - _doc: 'Skill validation fixtures.' unlisted-fixture: - SKILL.md: 'Defines the unlisted fixture prompt skill.' - _doc: 'Fixture skill intentionally absent from listings.' - _doc: 'Prompt-resource skills for Brunch agents.' + SKILL.md: 'Defines the unlisted-fixture prompt-resource skill.' lenses: - README.md: 'Documents topical focus prompt lenses.' - _doc: 'Topical focus prompt lenses.' + README.md: 'Documents this source subtree.' design: - SKILL.md: 'Defines the design prompt skill.' - _doc: 'Contains design resources for lenses.' + SKILL.md: 'Defines the design prompt-resource skill.' intent: - SKILL.md: 'Defines the intent prompt skill.' - _doc: 'Contains intent resources for lenses.' + SKILL.md: 'Defines the intent prompt-resource skill.' oracle: - SKILL.md: 'Defines the oracle prompt skill.' - _doc: 'Contains oracle resources for lenses.' + SKILL.md: 'Defines the oracle prompt-resource skill.' methods: - _doc: 'Task method prompt skills.' capture: - SKILL.md: 'Defines the capture prompt skill.' - _doc: 'Contains capture resources for methods.' + SKILL.md: 'Defines the capture prompt-resource skill.' commit-graph: - SKILL.md: 'Defines the commit graph prompt skill.' - _doc: 'Contains commit graph resources for methods.' + SKILL.md: 'Defines the commit-graph prompt-resource skill.' elicit-by-question: - SKILL.md: 'Defines the elicit by question prompt skill.' - _doc: 'Contains elicit by question resources for methods.' + SKILL.md: 'Defines the elicit-by-question prompt-resource skill.' explore-and-characterize: - SKILL.md: 'Defines the explore and characterize prompt skill.' - _doc: 'Contains explore and characterize resources for methods.' + SKILL.md: 'Defines the explore-and-characterize prompt-resource skill.' generate-proposal: - SKILL.md: 'Defines the generate proposal prompt skill.' - _doc: 'Contains generate proposal resources for methods.' + SKILL.md: 'Defines the generate-proposal prompt-resource skill.' + probes.md: 'Markdown resource.' + references: + design.md: 'Markdown resource.' + intent.md: 'Markdown resource.' + oracle.md: 'Markdown resource.' ingest-paste: - SKILL.md: 'Defines the ingest paste prompt skill.' - _doc: 'Contains ingest paste resources for methods.' + SKILL.md: 'Defines the ingest-paste prompt-resource skill.' read-context: - SKILL.md: 'Defines the read context prompt skill.' - _doc: 'Contains read context resources for methods.' + SKILL.md: 'Defines the read-context prompt-resource skill.' read-referenced-documents: - SKILL.md: 'Defines the read referenced documents prompt skill.' - _doc: 'Contains read referenced documents resources for methods.' + SKILL.md: 'Defines the read-referenced-documents prompt-resource skill.' review-for-gaps: - SKILL.md: 'Defines the review for gaps prompt skill.' - _doc: 'Contains review for gaps resources for methods.' + SKILL.md: 'Defines the review-for-gaps prompt-resource skill.' run-structured-exchange: - SKILL.md: 'Defines the run structured exchange prompt skill.' - _doc: 'Contains run structured exchange resources for methods.' + SKILL.md: 'Defines the run-structured-exchange prompt-resource skill.' strategies: - README.md: 'Documents interaction strategy prompt skills.' - _doc: 'Interaction strategy prompt skills.' + README.md: 'Documents this source subtree.' freestyle: - SKILL.md: 'Defines the freestyle prompt skill.' - _doc: 'Contains freestyle resources for strategies.' + SKILL.md: 'Defines the freestyle prompt-resource skill.' step-wise-decision-tree: - SKILL.md: 'Defines the step wise decision tree prompt skill.' - _doc: 'Contains step wise decision tree resources for strategies.' + SKILL.md: 'Defines the step-wise-decision-tree prompt-resource skill.' step-wise-disambiguate: - SKILL.md: 'Defines the step wise disambiguate prompt skill.' - _doc: 'Contains step wise disambiguate resources for strategies.' - README.md: 'Documents the directory topology and ownership.' + SKILL.md: 'Defines the step-wise-disambiguate prompt-resource skill.' app: - README.md: 'Documents CLI and runtime composition entry points.' - _doc: 'CLI and runtime composition entry points.' - brunch-tui.ts: 'Launches the Brunch TUI wrapper process.' - brunch.ts: 'Launches the Brunch CLI runtime.' - pi-extensions.ts: 'Composes Brunch Pi extensions for the app runtime.' - pi-settings.ts: 'Builds Pi settings with Brunch extension factories.' - pi-subagents.ts: 'Composes Brunch subagent definitions at startup.' - constants.ts: 'Defines shared Brunch filesystem constants.' + README.md: 'Documents this source subtree.' + brunch-tui.ts: 'Implements brunch tui.' + brunch.ts: 'Implements brunch.' + pi-extensions.ts: 'Implements pi extensions.' + pi-settings.ts: 'Implements pi settings.' + pi-subagents.ts: 'Implements pi subagents.' + constants.ts: 'Implements constants.' db: - README.md: 'Documents drizzle persistence substrate.' - _doc: 'Drizzle persistence substrate.' - connection.ts: 'Opens and migrates the Brunch SQLite database.' - row-schemas.ts: 'Derives runtime validation schemas from Drizzle tables.' - schema.ts: 'Defines canonical Drizzle database tables.' + README.md: 'Documents this source subtree.' + connection.ts: 'Implements connection.' + row-schemas.ts: 'Implements row schemas.' + schema.ts: 'Implements schema.' dev: - README.md: 'Documents local development harnesses and helpers.' - _doc: 'Local development harnesses and helpers.' - agent-messages.ts: 'Implements agent messages for dev.' - brunch-dev.ts: 'Implements brunch dev for dev.' - faux-harness.ts: 'Implements faux harness for dev.' - faux-launcher.ts: 'Implements faux launcher for dev.' + README.md: 'Documents this source subtree.' + agent-messages.ts: 'Implements agent messages.' + brunch-dev.ts: 'Implements brunch dev.' + faux-harness.ts: 'Implements faux harness.' + faux-launcher.ts: 'Implements faux launcher.' + generate-fan-out-witness.ts: 'Implements generate fan out witness.' index.ts: 'Exports the public module surface.' - introspection-launcher.ts: 'Implements introspection launcher for dev.' - pi-source-alias.ts: 'Implements pi source alias for dev.' - tier-2-harness.ts: 'Implements tier 2 harness for dev.' - workspace-rpc.ts: 'Implements workspace rpc for dev.' + introspection-launcher.ts: 'Implements introspection launcher.' + pi-source-alias.ts: 'Implements pi source alias.' + tier-2-harness.ts: 'Implements tier 2 harness.' + workspace-rpc.ts: 'Implements workspace rpc.' graph: - README.md: 'Documents graph domain layer for specifications.' - _doc: 'Graph domain layer for specifications.' - atoms.ts: 'Defines atoms for graph.' + README.md: 'Documents this source subtree.' + atoms.ts: 'Implements atoms.' command-executor: - _doc: 'Private CommandExecutor mutation planning modules.' - command-types.ts: 'Defines command types for command executor.' - command-validation.ts: 'Implements command validation for command executor.' - create-graph-batch.ts: 'Implements create graph batch for command executor.' - graph-mutation-planner.ts: 'Implements graph mutation planner for command executor.' - graph-mutation-types.ts: 'Defines graph mutation types for command executor.' - graph-mutation-writer.ts: 'Implements graph mutation writer for command executor.' - role-named-edge-draft.ts: 'Implements role named edge draft for command executor.' - command-executor.ts: 'Implements command executor for graph.' - elicitation-driver.ts: 'Implements elicitation driver for graph.' - export-fixtures.ts: 'Implements export fixtures for graph.' + command-types.ts: 'Implements command types.' + command-validation.ts: 'Implements command validation.' + create-graph-batch.ts: 'Implements create graph batch.' + graph-mutation-planner.ts: 'Implements graph mutation planner.' + graph-mutation-types.ts: 'Implements graph mutation types.' + graph-mutation-writer.ts: 'Implements graph mutation writer.' + role-named-edge-draft.ts: 'Implements role named edge draft.' + command-executor.ts: 'Implements command executor.' + elicitation-driver.ts: 'Implements elicitation driver.' + export-fixtures.ts: 'Implements export fixtures.' index.ts: 'Exports the public module surface.' policy: - _doc: 'Graph semantic policy tables.' - category-policy.ts: 'Defines category policy for policy.' + category-policy.ts: 'Implements category policy.' projection: - _doc: 'Graph read-label projection helpers.' - direction.ts: 'Implements direction for projection.' - labels.ts: 'Implements labels for projection.' - queries.ts: 'Queries queries for graph.' - review-set.ts: 'Implements review set for graph.' + direction.ts: 'Implements direction.' + labels.ts: 'Implements labels.' + queries.ts: 'Implements queries.' + review-set.ts: 'Implements review set.' schema: - _doc: 'Graph domain type definitions.' - edges.ts: 'Implements edges for schema.' - elicitation-gap-fixtures.ts: 'Implements elicitation gap fixtures for schema.' - elicitation-gaps.ts: 'Implements elicitation gaps for schema.' - kinds.ts: 'Defines kinds for schema.' - nodes.ts: 'Implements nodes for schema.' - reconciliation-need.ts: 'Implements reconciliation need for schema.' - seed-fixtures.ts: 'Implements seed fixtures for graph.' - validate-fixture.ts: 'Implements validate fixture for graph.' - workspace-store.ts: 'Implements workspace store for graph.' + _generated: + ontology.md: 'Markdown resource.' + edges.ts: 'Implements edges.' + elicitation-gap-fixtures.ts: 'Implements elicitation gap fixtures.' + elicitation-gaps.ts: 'Implements elicitation gaps.' + generate-ontology-ref.ts: 'Implements generate ontology ref.' + kinds.ts: 'Implements kinds.' + nodes.ts: 'Implements nodes.' + reconciliation-need.ts: 'Implements reconciliation need.' + seed-fixtures.ts: 'Implements seed fixtures.' + validate-fixture.ts: 'Implements validate fixture.' + workspace-store.ts: 'Implements workspace store.' probes: - _doc: 'Executable proof scripts and probe harnesses.' - capture-quality-loop.ts: 'Implements capture quality loop for probes.' - check-workspace-session-stores.ts: 'Implements check workspace session stores for probes.' - deterministic-exchange-script.ts: 'Implements deterministic exchange script for probes.' - faux-provider.ts: 'Implements faux provider for probes.' - fixture-curation-loop.ts: 'Implements fixture curation loop for probes.' - portable-report.ts: 'Implements portable report for probes.' - project-graph-review-cycle-proof.ts: 'Implements project graph review cycle proof for probes.' - propose-graph-commit-proof.ts: 'Implements propose graph commit proof for probes.' - public-rpc-parity-proof.ts: 'Implements public rpc parity proof for probes.' + capture-quality-loop.ts: 'Implements capture quality loop.' + check-workspace-session-stores.ts: 'Implements check workspace session stores.' + deterministic-exchange-script.ts: 'Implements deterministic exchange script.' + faux-provider.ts: 'Implements faux provider.' + fixture-curation-loop.ts: 'Implements fixture curation loop.' + portable-report.ts: 'Implements portable report.' + project-graph-review-cycle-proof.ts: 'Implements project graph review cycle proof.' + propose-graph-commit-proof.ts: 'Implements propose graph commit proof.' + public-rpc-parity-proof.ts: 'Implements public rpc parity proof.' scripts: - _doc: 'Shell wrappers for probe checks.' - run-ship-gate-composition.sh: 'Runs the run ship gate composition probe script.' - verify-startup-no-resume.sh: 'Runs the verify startup no resume probe script.' - ship-gate-composition-proof.ts: 'Implements ship gate composition proof for probes.' - ship-gate-rpc-client.ts: 'Implements ship gate rpc client for probes.' - structured-exchange-ordering-proof.ts: 'Implements structured exchange ordering proof for probes.' - structured-exchange-rpc-proof.ts: 'Implements structured exchange rpc proof for probes.' - test-helpers.ts: 'Implements test helpers for probes.' + run-ship-gate-composition.sh: 'Source asset.' + verify-startup-no-resume.sh: 'Source asset.' + ship-gate-composition-proof.ts: 'Implements ship gate composition proof.' + ship-gate-rpc-client.ts: 'Implements ship gate rpc client.' + structured-exchange-ordering-proof.ts: 'Implements structured exchange ordering proof.' + structured-exchange-rpc-proof.ts: 'Implements structured exchange rpc proof.' + test-helpers.ts: 'Implements test helpers.' projections: - README.md: 'Documents reusable DTO projections between domain and boundaries.' - _doc: 'Reusable DTO projections between domain and boundaries.' + README.md: 'Documents this source subtree.' exchanges: - _doc: 'Structured-exchange DTO projections.' - present-candidates.ts: 'Implements present candidates for exchanges.' - present-question.ts: 'Implements present question for exchanges.' - present-review-set.ts: 'Implements present review set for exchanges.' - request-answer.ts: 'Implements request answer for exchanges.' - request-choice.ts: 'Implements request choice for exchanges.' - request-choices.ts: 'Implements request choices for exchanges.' - request-review.ts: 'Implements request review for exchanges.' - review-set-payload.ts: 'Implements review set payload for exchanges.' + present-candidates.ts: 'Implements present candidates.' + present-question.ts: 'Implements present question.' + present-review-set.ts: 'Implements present review set.' + request-answer.ts: 'Implements request answer.' + request-choice.ts: 'Implements request choice.' + request-choices.ts: 'Implements request choices.' + request-review.ts: 'Implements request review.' + review-set-payload.ts: 'Implements review set payload.' graph: - _doc: 'Graph DTO projections.' - commit-result.ts: 'Implements commit result for graph.' - neighborhood.ts: 'Implements neighborhood for graph.' - overview.ts: 'Implements overview for graph.' - reconciliation-needs.ts: 'Implements reconciliation needs for graph.' + commit-result.ts: 'Implements commit result.' + neighborhood.ts: 'Implements neighborhood.' + overview.ts: 'Implements overview.' + reconciliation-needs.ts: 'Implements reconciliation needs.' session: - _doc: 'Session runtime and transcript DTO projections.' - affordances.ts: 'Implements affordances for session.' - assistant-visible-watermark.ts: 'Implements assistant visible watermark for session.' - capability-readiness.ts: 'Implements capability readiness for session.' - continuity-entry-classifier.ts: 'Implements continuity entry classifier for session.' - readiness-estimate.ts: 'Implements readiness estimate for session.' - runtime-policy.ts: 'Defines runtime policy for session.' - runtime-state.ts: 'Implements runtime state for session.' - sweep-watermark.test.ts: 'Tests sweep watermark test behavior.' - sweep-watermark.ts: 'Implements sweep watermark for session.' - transcript-context.ts: 'Implements transcript context for session.' + affordances.ts: 'Implements affordances.' + assistant-visible-watermark.ts: 'Implements assistant visible watermark.' + capability-readiness.ts: 'Implements capability readiness.' + continuity-entry-classifier.ts: 'Implements continuity entry classifier.' + readiness-estimate.ts: 'Implements readiness estimate.' + runtime-policy.ts: 'Implements runtime policy.' + runtime-state.ts: 'Implements runtime state.' + sweep-watermark.test.ts: 'Tests sweep watermark behavior.' + sweep-watermark.ts: 'Implements sweep watermark.' + transcript-context.ts: 'Implements transcript context.' workspace: - _doc: 'Workspace DTO projections.' - workspace-state.ts: 'Implements workspace state for workspace.' + workspace-state.ts: 'Implements workspace state.' renderers: - README.md: 'Documents lossy text renderers for model and debug surfaces.' - _doc: 'Lossy text renderers for model and debug surfaces.' - exchanges: - _doc: 'Structured-exchange markdown renderers.' - present-candidates.ts: 'Implements present candidates for exchanges.' - present-question.ts: 'Implements present question for exchanges.' - present-review-set.ts: 'Implements present review set for exchanges.' - request-answer.ts: 'Implements request answer for exchanges.' - request-choice.ts: 'Implements request choice for exchanges.' - request-choices.ts: 'Implements request choices for exchanges.' - request-review.ts: 'Implements request review for exchanges.' - graph: - __previews__: - _doc: 'Generated graph renderer previews.' - graph-overview-kind-band-spread.md: 'Preview output for graph overview kind band spread.' - neighborhood-brunch-self-MOD1-hops2.md: 'Preview output for neighborhood brunch self MOD1 hops2.' - neighborhood-brunch-self-REQ1.md: 'Preview output for neighborhood brunch self REQ1.' - neighborhood-code-health-REQ1.md: 'Preview output for neighborhood code health REQ1.' - neighborhood-hub-REQ1-compact.md: 'Preview output for neighborhood hub REQ1 compact.' - neighborhood-hub-REQ1-hops2.md: 'Preview output for neighborhood hub REQ1 hops2.' - neighborhood-hub-REQ1.md: 'Preview output for neighborhood hub REQ1.' - _doc: 'Graph context and result renderers.' - commit-result.ts: 'Implements commit result for graph.' - graph-slice.ts: 'Implements graph slice for graph.' - node-neighborhood.ts: 'Implements node neighborhood for graph.' - reconciliation-needs.ts: 'Implements reconciliation needs for graph.' - markdown.ts: 'Implements markdown for renderers.' - section.ts: 'Implements section for renderers.' + README.md: 'Documents this source subtree.' session: - __previews__: - _doc: 'Generated session renderer previews.' - runtime-frame-ready.md: 'Preview output for runtime frame ready.' - _doc: 'Session context renderers.' - readiness-estimate.ts: 'Implements readiness estimate for session.' - runtime-frame.ts: 'Implements runtime frame for session.' - transcript.ts: 'Implements transcript for session.' - specification: - __previews__: - _doc: 'Generated specification renderer previews.' - specification-context.md: 'Preview output for specification context.' - _doc: 'Specification context renderers.' - specification-context.ts: 'Implements specification context for specification.' - toon.ts: 'Implements toon for renderers.' - tree.ts: 'Implements tree for renderers.' + transcript.ts: 'Implements transcript.' workspace: - __previews__: - _doc: 'Generated workspace renderer previews.' - workspace-cwd-context.md: 'Preview output for workspace cwd context.' - workspace-overview-context.md: 'Preview output for workspace overview context.' - _doc: 'Workspace context renderers.' - workspace-context.ts: 'Implements workspace context for workspace.' - workspace-state.ts: 'Implements workspace state for workspace.' + workspace-state.ts: 'Implements workspace state.' rpc: - README.md: 'Documents public JSON-RPC boundary for Brunch clients.' - _doc: 'Public JSON-RPC boundary for Brunch clients.' - handlers.ts: 'Implements handlers for rpc.' + README.md: 'Documents this source subtree.' + handlers.ts: 'Implements handlers.' methods: - _doc: 'Individual JSON-RPC method implementations and schemas.' - dev-graph.ts: 'Implements dev graph for methods.' - graph.ts: 'Implements graph for methods.' - registry.ts: 'Implements registry for methods.' - schemas.ts: 'Defines schemas for methods.' - session-driver.ts: 'Implements session driver for methods.' - session-exchange-answer.ts: 'Implements session exchange answer for methods.' - session.ts: 'Implements session for methods.' - workspace.ts: 'Implements workspace for methods.' - product-updates.ts: 'Implements product updates for rpc.' - protocol.ts: 'Defines protocol for rpc.' - session-event-relay.test.ts: 'Tests session event relay test behavior.' - session-event-relay.ts: 'Implements session event relay for rpc.' - web-host.ts: 'Implements web host for rpc.' - websocket.ts: 'Implements websocket for rpc.' + dev-graph.ts: 'Implements dev graph.' + graph.ts: 'Implements graph.' + registry.ts: 'Implements registry.' + schemas.ts: 'Implements schemas.' + session-driver.ts: 'Implements session driver.' + session-exchange-answer.ts: 'Implements session exchange answer.' + session.ts: 'Implements session.' + workspace.ts: 'Implements workspace.' + product-updates.ts: 'Implements product updates.' + protocol.ts: 'Implements protocol.' + session-event-relay.test.ts: 'Tests session event relay behavior.' + session-event-relay.ts: 'Implements session event relay.' + web-host.ts: 'Implements web host.' + websocket.ts: 'Implements websocket.' scripts: - README.md: 'Documents source-owned script entry points.' - _doc: 'Source-owned script entry points.' + README.md: 'Documents this source subtree.' session: - README.md: 'Documents session domain layer and turn orchestration.' - _doc: 'Session domain layer and turn orchestration.' - agent-context-seed.ts: 'Implements agent context seed for session.' - brunch-session-envelope.ts: 'Implements brunch session envelope for session.' - context-seed.ts: 'Implements context seed for session.' - exchange-projection.ts: 'Projects exchange projection for session.' - flush-session-manager.ts: 'Implements flush session manager for session.' - live-exchange-broker.ts: 'Implements live exchange broker for session.' - mention-ledger.ts: 'Implements mention ledger for session.' - originate-assistant-turn.ts: 'Implements originate assistant turn for session.' - prepare-next-turn.ts: 'Implements prepare next turn for session.' - runtime-state.ts: 'Implements runtime state for session.' + README.md: 'Documents this source subtree.' + brunch-session-envelope.ts: 'Implements brunch session envelope.' + exchange-projection.ts: 'Implements exchange projection.' + flush-session-manager.ts: 'Implements flush session manager.' + live-exchange-broker.ts: 'Implements live exchange broker.' + mention-ledger.ts: 'Implements mention ledger.' + originate-assistant-turn.ts: 'Implements originate assistant turn.' + prepare-next-turn.ts: 'Implements prepare next turn.' + runtime-state.ts: 'Implements runtime state.' schema: - README.md: 'Documents session vocabulary definitions.' - _doc: 'Session vocabulary definitions.' - agent-manifest.ts: 'Implements agent manifest for schema.' - kinds.ts: 'Defines kinds for schema.' - session-binding.ts: 'Implements session binding for session.' - session-projection-reader.ts: 'Projects session projection reader for session.' - session-transcript.ts: 'Implements session transcript for session.' - specification-overview-context.ts: 'Implements specification overview context for session.' - start-assistant-turn.ts: 'Implements start assistant turn for session.' + README.md: 'Documents this source subtree.' + agent-manifest.ts: 'Implements agent manifest.' + kinds.ts: 'Implements kinds.' + tool-names.ts: 'Implements tool names.' + session-binding.ts: 'Implements session binding.' + session-projection-reader.ts: 'Implements session projection reader.' + session-transcript.ts: 'Implements session transcript.' + specification-overview-context.ts: 'Implements specification overview context.' + start-assistant-turn.ts: 'Implements start assistant turn.' structured-exchange-loop: - _doc: 'Private structured-exchange loop helpers.' - accepted-response.ts: 'Implements accepted response for structured exchange loop.' - pending-exchange.ts: 'Implements pending exchange for structured exchange loop.' - synthetic-tool-call.ts: 'Implements synthetic tool call for structured exchange loop.' - structured-exchange-loop.ts: 'Implements structured exchange loop for session.' - workspace-overview-context.ts: 'Implements workspace overview context for session.' + accepted-response.ts: 'Implements accepted response.' + pending-exchange.ts: 'Implements pending exchange.' + synthetic-tool-call.ts: 'Implements synthetic tool call.' + structured-exchange-loop.ts: 'Implements structured exchange loop.' + workspace-overview-context.ts: 'Implements workspace overview context.' workspace-session-coordinator: - _doc: 'Private workspace session file discovery helpers.' - canonical-session-files.ts: 'Implements canonical session files for workspace session coordinator.' - workspace-session-coordinator.ts: 'Implements workspace session coordinator for session.' + canonical-session-files.ts: 'Implements canonical session files.' + workspace-session-coordinator.ts: 'Implements workspace session coordinator.' + treedocs.yaml: 'Source asset.' utils: - _doc: 'Small shared utility helpers.' - strings.ts: 'Implements strings for utils.' + strings.ts: 'Implements strings.' web: - README.md: 'Documents react web client for Brunch.' - _doc: 'React web client for Brunch.' - app-meta.ts: 'Implements app meta for web.' - app.tsx: 'Implements app for web.' + README.md: 'Documents this source subtree.' + app-meta.ts: 'Implements app meta.' + app.tsx: 'Source asset.' assets: - _doc: 'Web client static assets.' - brunch.png: 'Brunch logo image asset.' + brunch.png: 'Image asset.' components: - _doc: 'Reusable React UI components.' - app-header.tsx: 'Implements app header for components.' - drawer-card.tsx: 'Implements drawer card for components.' - icons.tsx: 'Implements icons for components.' - node-card.tsx: 'Implements node card for components.' + app-header.tsx: 'Source asset.' + drawer-card.tsx: 'Source asset.' + icons.tsx: 'Source asset.' + node-card.tsx: 'Source asset.' features: - _doc: 'Feature-specific web UI modules.' graph: - _doc: 'Graph feature display helpers.' - kind-display.ts: 'Implements kind display for graph.' - structured-list-view.tsx: 'Implements structured list view for graph.' - main.tsx: 'Implements main for web.' + kind-display.ts: 'Implements kind display.' + structured-list-view.tsx: 'Source asset.' + main.tsx: 'Source asset.' queries: - _doc: 'TanStack Query definitions for RPC data.' - graph.ts: 'Implements graph for queries.' - session.ts: 'Implements session for queries.' - workspace.ts: 'Implements workspace for queries.' - query-client.ts: 'Implements query client for web.' - query-keys.ts: 'Implements query keys for web.' + graph.ts: 'Implements graph.' + session.ts: 'Implements session.' + workspace.ts: 'Implements workspace.' + query-client.ts: 'Implements query client.' + query-keys.ts: 'Implements query keys.' routes: - _doc: 'TanStack Router route components.' - root.tsx: 'Implements root for routes.' - spec.tsx: 'Implements spec for routes.' - rpc-client.ts: 'Implements rpc client for web.' - spec-id.ts: 'Implements spec id for web.' - styles.css: 'Defines global web client styles.' + root.tsx: 'Source asset.' + spec.tsx: 'Source asset.' + rpc-client.ts: 'Implements rpc client.' + spec-id.ts: 'Implements spec id.' + styles.css: 'Source asset.' subscriptions: - _doc: 'WebSocket-driven query invalidation hooks.' - brunch-updates.ts: 'Implements brunch updates for subscriptions.' - follow-workspace-spec.ts: 'Implements follow workspace spec for subscriptions.' - vite-env.d.ts: 'Declares Vite client types.' + brunch-updates.ts: 'Implements brunch updates.' + follow-workspace-spec.ts: 'Implements follow workspace spec.' + vite-env.d.ts: 'Implements vite env.d.' workspace: - README.md: 'Documents workspace identity, inventory, and state storage.' - _doc: 'Workspace identity, inventory, and state storage.' - cwd-inventory.ts: 'Implements cwd inventory for workspace.' - project-identity.ts: 'Implements project identity for workspace.' - workspace-state-store.ts: 'Implements workspace state store for workspace.' + README.md: 'Documents this source subtree.' + cwd-inventory.ts: 'Implements cwd inventory.' + project-identity.ts: 'Implements project identity.' + workspace-state-store.ts: 'Implements workspace state store.' From fe7da28b9d1420cf9edb10b8637ec13096e4a90a Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Fri, 26 Jun 2026 09:12:47 +0200 Subject: [PATCH 20/54] Characterize renderer moves at target owners --- memory/REFACTOR.md | 85 +++++++++++++++++++ .../__tests__/print-workspace-state.test.ts} | 4 +- .../__tests__/transcript-markdown.test.ts | 64 ++++++++++++++ 3 files changed, 151 insertions(+), 2 deletions(-) create mode 100644 memory/REFACTOR.md rename src/{renderers/workspace/__tests__/workspace-state.test.ts => app/__tests__/print-workspace-state.test.ts} (87%) create mode 100644 src/session/__tests__/transcript-markdown.test.ts diff --git a/memory/REFACTOR.md b/memory/REFACTOR.md new file mode 100644 index 000000000..409de2f37 --- /dev/null +++ b/memory/REFACTOR.md @@ -0,0 +1,85 @@ +## Problem Statement + +The LLM-context ingress refactor moved the bulk of prompts, skills, prompt runtime, and model-facing context text into `src/agents/`, but three topology problems remain. + +First, foreground agent runtime ownership still leaks through `projections/session/runtime-policy.ts`. That file owns the foreground roster, body path references, model/thinking choices, skill grants, tool policy, and delegatable-agent allowlist. Those are agent-runtime facts, not projection facts. The current `projections/session -> agents/registry` edge is documented as temporary; leaving it in place makes `src/agents/runtime/` look central while its most important source of truth is still outside it. + +Second, `read_graph related` was relocated into `src/agents/contexts/graph/related-nodes.ts`, but the actual wording shape still leaks structural internals (`-[category/direction]->`, `plane/kind`) and lacks a focused golden. This preserves the old problem under the new path. + +Third, `src/renderers/` is now an orphan topology: after model-facing renderers moved to `src/agents/contexts/`, it contains only two small human/product renderers. A top-level renderer layer for two isolated outputs adds navigation cost without hiding meaningful complexity. Those outputs now read better beside their real owners: print-mode state near `app/`, transcript debug markdown near `session/`. + +```pseudo tree +current remaining topology +src/ +├── agents/ +│ ├── runtime/ mostly central, but imports projection-owned runtime policy +│ └── contexts/graph/related moved, but still structurally leaky +├── projections/session/ +│ └── runtime-policy owns foreground roster + tool policy + body paths +└── renderers/ orphan layer + ├── workspace/workspace-state + └── session/transcript +``` + +## Solution + +Finish the topology by making `src/agents/runtime/` the actual owner of foreground runtime policy, repairing or collapsing the `related` graph render into the clean graph-context vocabulary, and deleting the now-shallow `src/renderers/` layer. + +```pseudo tree +desired topology +src/ +├── agents/ +│ ├── runtime/ +│ │ ├── foreground roster +│ │ ├── tool policy +│ │ ├── prompt-resource/tool legality +│ │ └── body path lookup +│ └── contexts/graph/ +│ ├── overview/neighborhood +│ └── related uses the same semantic relation vocabulary or disappears behind filtered neighborhood rendering +├── projections/session/ +│ └── runtime-state projection only; no agent roster ownership +├── app/ +│ └── print-mode workspace-state text beside the only product caller +└── session/ + └── debug transcript markdown beside transcript JSONL/projection utilities +``` + +The key judo move is deletion, not another rearrangement: remove the temporary projections→agents edge and remove the orphan `renderers/` topology once its two remaining functions have obvious homes. + +## Commits + +1. [x] Add or move characterization tests for the two remaining orphan human/product renderers at their target owners: print-mode workspace text under the app layer, and debug transcript markdown under the session layer. Keep expected bytes unchanged. +2. [ ] Move the foreground agent roster and tool-policy definitions into the agent runtime owner, then update projection and adapter callers to import runtime policy from `src/agents/runtime/`. Leave transcript-state projection in `projections/session`. +3. [ ] Delete the `projections/session -> agents/registry` temporary edge and update topology docs/tests so projections no longer own or import agent body locations, foreground roster, or tool policy. +4. [ ] Repair `read_graph related` by making it share the semantic relation vocabulary already used by neighborhood rendering, or by deleting the separate related formatter in favor of a filtered-neighborhood render. Add a focused golden/invariant for the related mode so raw category/direction arrows and raw fallback ids do not silently return. +5. [ ] Move print-mode workspace-state text out of `src/renderers/` to the app/print owner, update `brunch --mode print` imports, and delete the old workspace renderer file/directory. +6. [ ] Move debug transcript markdown out of `src/renderers/` to the session owner, update `session-transcript.ts` imports, and delete the old session renderer file/directory. +7. [ ] Delete `src/renderers/README.md` and the now-empty `src/renderers/` topology. Update root/topology READMEs, SPEC/PLAN references, and lint/import-boundary comments to remove `renderers/` as a live source layer. +8. [ ] Reconcile stale path fossils found during the move: old `.pi/extensions/system-prompts`, `.pi/extensions/graph`, `../web`, and renderers references in co-located READMEs and memory files. + +## Decisions + +- `src/agents/runtime/` owns foreground agent roster, agent body locations, skill grants, tool policy, delegatable set, and prompt-resource/tool legality. +- `projections/session/` owns transcript-backed runtime-state projection only; it may consume agent runtime definitions but must not own them. +- `src/renderers/` should be deleted if, after the move, it has no remaining multi-consumer human/product text seam. +- Print-mode workspace-state text belongs near the `app` print-mode path because it has one product caller and no model-facing role. +- Debug transcript markdown belongs near `session` because it is a transcript/debug artifact over Pi JSONL semantics, not a general renderer layer. +- `read_graph related` must not be considered fixed by relocation alone; it needs semantic vocabulary parity with graph neighborhood rendering or removal as a separate path. +- Topology READMEs touched: `src/README.md`, `src/agents/README.md`, `src/agents/runtime/README.md`, `src/agents/contexts/README.md`, `src/agents/contexts/graph/README.md`, `src/.pi/README.md`, `src/.pi/extensions/subagents/README.md`, `src/projections/README.md`, `src/session/README.md`, and any removed `src/renderers/README.md` references in `memory/SPEC.md` / `memory/PLAN.md`. + +## Testing Decisions + +- Existing `agents/runtime` tests cover prompt-resource/tool legality; extend or move them so the foreground roster move is witnessed at the new owner. +- Existing runtime-state projection tests should continue to prove transcript projection behavior, not agent roster ownership. +- Add a related-mode graph golden/invariant before or with the wording repair. The important behavior is semantic relation text and stable graph codes, not the internal helper used. +- Preserve existing print-mode output tests and transcript markdown tests through their move; these are characterization tests for deletion of the `renderers/` layer. +- Keep `npm run verify` as the gate for each commit-sized step; this refactor touches build asset paths, import boundaries, and topology docs. + +## Out of Scope + +- Changing graph ontology vocabulary, edge categories, node kinds, readiness bands, or detail schemas. +- Changing tool availability, Pi sealed-profile behavior, or subagent spawnability. +- Rewriting prompt bodies, skill bodies, or unrelated context wording. +- Moving product web/RPC rendering or introducing a new human-rendering framework. +- Solving the larger `elicitor-project` design question or adding new agent capabilities. diff --git a/src/renderers/workspace/__tests__/workspace-state.test.ts b/src/app/__tests__/print-workspace-state.test.ts similarity index 87% rename from src/renderers/workspace/__tests__/workspace-state.test.ts rename to src/app/__tests__/print-workspace-state.test.ts index 93fdbfa21..88fe3624a 100644 --- a/src/renderers/workspace/__tests__/workspace-state.test.ts +++ b/src/app/__tests__/print-workspace-state.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest'; -import type { WorkspaceState } from '../../../projections/workspace/workspace-state.js'; -import { renderWorkspaceState } from '../workspace-state.js'; +import type { WorkspaceState } from '../../projections/workspace/workspace-state.js'; +import { renderWorkspaceState } from '../../renderers/workspace/workspace-state.js'; const cwd = '/tmp/brunch-project'; diff --git a/src/session/__tests__/transcript-markdown.test.ts b/src/session/__tests__/transcript-markdown.test.ts new file mode 100644 index 000000000..2d53521c3 --- /dev/null +++ b/src/session/__tests__/transcript-markdown.test.ts @@ -0,0 +1,64 @@ +import { describe, expect, it } from 'vitest'; + +import type { ProjectedTranscriptContext } from '../../projections/session/transcript-context.js'; +import { formatTranscript } from '../../renderers/session/transcript.js'; + +describe('debug transcript markdown', () => { + it('renders projected transcript messages without non-text assistant blocks', () => { + const context: ProjectedTranscriptContext = { + messages: [ + { role: 'user', content: ' hello user ', timestamp: 1 }, + { + role: 'assistant', + api: 'openai-completions', + provider: 'openai', + model: 'test-model', + usage: { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, + totalTokens: 0, + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, + }, + stopReason: 'stop', + timestamp: 2, + content: [ + { type: 'text', text: 'First assistant paragraph.' }, + { type: 'thinking', thinking: 'private reasoning' }, + { type: 'text', text: 'Second assistant paragraph.' }, + ], + }, + { + role: 'toolResult', + toolName: 'request_response', + toolCallId: 'call-1', + content: [{ type: 'text', text: '### Response\n\nAccepted.' }], + isError: false, + timestamp: 3, + }, + ], + }; + + expect(formatTranscript(context, { title: 'debug.jsonl' })).toMatchInlineSnapshot(` + "# Transcript — debug.jsonl + + ## 1. User + + hello user + + ## 2. Assistant + + First assistant paragraph. + + Second assistant paragraph. + + ## 3. Tool result: request_response + + ### Response + + Accepted. + " + `); + }); +}); From 129a1d071e3142583b2b9f77ac25c2298d8c14c2 Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Fri, 26 Jun 2026 09:16:07 +0200 Subject: [PATCH 21/54] Move foreground runtime policy under agents --- memory/REFACTOR.md | 2 +- src/.pi/__tests__/architecture.test.ts | 2 +- .../runtime/authority-matrix.test.ts | 2 +- .../extensions/agent-runtime/runtime/index.ts | 8 +- src/.pi/extensions/commands/index.ts | 2 +- src/agents/runtime/README.md | 3 +- src/agents/runtime/__tests__/state.test.ts | 5 +- src/agents/runtime/policy.ts | 207 +++++++++++++++++ src/agents/runtime/state.ts | 14 +- .../session/__tests__/affordances.test.ts | 5 +- .../__tests__/readiness-estimate.test.ts | 2 +- src/projections/session/affordances.ts | 12 +- src/projections/session/runtime-policy.ts | 208 +----------------- src/projections/session/runtime-state.ts | 6 +- 14 files changed, 243 insertions(+), 235 deletions(-) create mode 100644 src/agents/runtime/policy.ts diff --git a/memory/REFACTOR.md b/memory/REFACTOR.md index 409de2f37..50161581c 100644 --- a/memory/REFACTOR.md +++ b/memory/REFACTOR.md @@ -50,7 +50,7 @@ The key judo move is deletion, not another rearrangement: remove the temporary p ## Commits 1. [x] Add or move characterization tests for the two remaining orphan human/product renderers at their target owners: print-mode workspace text under the app layer, and debug transcript markdown under the session layer. Keep expected bytes unchanged. -2. [ ] Move the foreground agent roster and tool-policy definitions into the agent runtime owner, then update projection and adapter callers to import runtime policy from `src/agents/runtime/`. Leave transcript-state projection in `projections/session`. +2. [x] Move the foreground agent roster and tool-policy definitions into the agent runtime owner, then update projection and adapter callers to import runtime policy from `src/agents/runtime/`. Leave transcript-state projection in `projections/session`. 3. [ ] Delete the `projections/session -> agents/registry` temporary edge and update topology docs/tests so projections no longer own or import agent body locations, foreground roster, or tool policy. 4. [ ] Repair `read_graph related` by making it share the semantic relation vocabulary already used by neighborhood rendering, or by deleting the separate related formatter in favor of a filtered-neighborhood render. Add a focused golden/invariant for the related mode so raw category/direction arrows and raw fallback ids do not silently return. 5. [ ] Move print-mode workspace-state text out of `src/renderers/` to the app/print owner, update `brunch --mode print` imports, and delete the old workspace renderer file/directory. diff --git a/src/.pi/__tests__/architecture.test.ts b/src/.pi/__tests__/architecture.test.ts index 7fe133fa7..ab555c53a 100644 --- a/src/.pi/__tests__/architecture.test.ts +++ b/src/.pi/__tests__/architecture.test.ts @@ -21,7 +21,7 @@ const runtimeRegistryExpectations = [ forbidden: ['reviewer', 'pi-coder'], }, { - file: 'src/projections/session/runtime-policy.ts', + file: 'src/agents/runtime/policy.ts', required: 'export const FOREGROUND_AGENT_ROSTER: Record = {', // `reviewer` is a non-write background agent that legitimately appears in diff --git a/src/.pi/extensions/agent-runtime/runtime/authority-matrix.test.ts b/src/.pi/extensions/agent-runtime/runtime/authority-matrix.test.ts index e191bff54..fed88bea5 100644 --- a/src/.pi/extensions/agent-runtime/runtime/authority-matrix.test.ts +++ b/src/.pi/extensions/agent-runtime/runtime/authority-matrix.test.ts @@ -1,9 +1,9 @@ import type { ExtensionAPI } from '@earendil-works/pi-coding-agent'; import { describe, expect, it } from 'vitest'; +import { isToolBlockedForRuntimeState } from '../../../../agents/runtime/policy.js'; import type { CommandResult } from '../../../../graph/command-executor.js'; import { groundingFloorGaps } from '../../../../graph/schema/elicitation-gap-fixtures.js'; -import { isToolBlockedForRuntimeState } from '../../../../projections/session/runtime-policy.js'; import { DEFAULT_BRUNCH_AGENT_STATE } from '../../../../session/runtime-state.js'; import { activeToolNamesForBrunchAgentState, projectBrunchAgentState } from './index.js'; diff --git a/src/.pi/extensions/agent-runtime/runtime/index.ts b/src/.pi/extensions/agent-runtime/runtime/index.ts index d9b8ee50b..61ac8b7af 100644 --- a/src/.pi/extensions/agent-runtime/runtime/index.ts +++ b/src/.pi/extensions/agent-runtime/runtime/index.ts @@ -17,13 +17,13 @@ import { } from '@earendil-works/pi-coding-agent'; import { Text } from '@earendil-works/pi-tui'; -import { activeToolNamesForPosture } from '../../../../agents/runtime/state.js'; -import { groundingFloorGaps } from '../../../../graph/schema/elicitation-gap-fixtures.js'; -import type { ElicitationGap } from '../../../../graph/schema/elicitation-gaps.js'; import { isToolBlockedForRuntimeState, toolPolicyForRuntimeState, -} from '../../../../projections/session/runtime-policy.js'; +} from '../../../../agents/runtime/policy.js'; +import { activeToolNamesForPosture } from '../../../../agents/runtime/state.js'; +import { groundingFloorGaps } from '../../../../graph/schema/elicitation-gap-fixtures.js'; +import type { ElicitationGap } from '../../../../graph/schema/elicitation-gaps.js'; export { agentBodyResourceLocation } from '../../../../agents/runtime/state.js'; diff --git a/src/.pi/extensions/commands/index.ts b/src/.pi/extensions/commands/index.ts index 58c21f669..41a73cec1 100644 --- a/src/.pi/extensions/commands/index.ts +++ b/src/.pi/extensions/commands/index.ts @@ -36,8 +36,8 @@ import type { ExtensionAPI, ExtensionCommandContext } from '@earendil-works/pi-coding-agent'; +import { pinnableAxisOptionsForRuntimeState } from '../../../agents/runtime/policy.js'; import type { ElicitationGap } from '../../../graph/schema/elicitation-gaps.js'; -import { pinnableAxisOptionsForRuntimeState } from '../../../projections/session/runtime-policy.js'; import { appendBrunchAgentRuntimeSwitch } from '../../../session/runtime-state.js'; import { AGENT_LENS_IDS, diff --git a/src/agents/runtime/README.md b/src/agents/runtime/README.md index 2f631fb5a..be0e94e6e 100644 --- a/src/agents/runtime/README.md +++ b/src/agents/runtime/README.md @@ -4,12 +4,13 @@ SPEC decisions: D40-L, D52-L, D58-L, D85-L, D90-L, D93-L ## Owns -Runtime prompt policy that is Pi-independent: foreground prompt composition, prompt-resource manifest rendering/loading, active method/tool derivation, and agent body location lookup. +Runtime prompt policy that is Pi-independent: foreground roster definitions, foreground prompt composition, prompt-resource manifest rendering/loading, active method/tool derivation, and agent body location lookup. ```text runtime/ ├── README.md ├── compose.ts pure prompt composer: agent body + runtime header + context + manifest +├── policy.ts foreground roster, tool policy, delegatable set, axis legality ├── prompt-skills.ts prompt-resource manifest loader/renderer ├── state.ts runtime-state-to-manifest/tool policy projection ├── __tests__/ prompt/runtime policy tests diff --git a/src/agents/runtime/__tests__/state.test.ts b/src/agents/runtime/__tests__/state.test.ts index fdc582055..0cf98f7fa 100644 --- a/src/agents/runtime/__tests__/state.test.ts +++ b/src/agents/runtime/__tests__/state.test.ts @@ -5,12 +5,9 @@ import { describe, expect, it } from 'vitest'; import { BRUNCH_ORCHESTRATOR_STUB_TOOL } from '../../../.pi/extensions/agent-runtime/orchestrator-stub/index.js'; import { groundingFloorGaps } from '../../../graph/schema/elicitation-gap-fixtures.js'; -import { - FOREGROUND_AGENT_ROSTER, - delegatableAgentsForRuntimeState, -} from '../../../projections/session/runtime-policy.js'; import { projectBrunchAgentState } from '../../../projections/session/runtime-state.js'; import { bundledAgentBodyLocation } from '../../registry.js'; +import { FOREGROUND_AGENT_ROSTER, delegatableAgentsForRuntimeState } from '../policy.js'; import { activeToolNamesForPosture, agentBodyResourceLocation, manifestsForState } from '../state.js'; const registeredToolNames = [ diff --git a/src/agents/runtime/policy.ts b/src/agents/runtime/policy.ts new file mode 100644 index 000000000..466cea4d6 --- /dev/null +++ b/src/agents/runtime/policy.ts @@ -0,0 +1,207 @@ +import type { ElicitationGap } from '../../graph/schema/elicitation-gaps.js'; +import { + evaluateCapabilityReadiness, + type CapabilityId, +} from '../../projections/session/capability-readiness.js'; +import type { BrunchAgentState, ToolPolicyId } from '../../session/runtime-state.js'; +import type { ForegroundAgentManifest } from '../../session/schema/agent-manifest.js'; +import type { + AgentLensId, + AgentLensSelection, + AgentStrategyId, + AgentStrategySelection, + OperationalModeId, +} from '../../session/schema/kinds.js'; +import { AGENT_METHOD_IDS } from '../../session/schema/kinds.js'; +import { BRUNCH_ORCHESTRATOR_STUB_TOOL } from '../../session/schema/tool-names.js'; +import { bundledAgentBodyRepoPath } from '../registry.js'; + +export interface ToolPolicyDefinition { + id: ToolPolicyId; + baseAllowedToolNames: readonly string[]; + blockedToolNames: readonly string[]; +} + +export interface OperationalModeDefinition { + id: OperationalModeId; + foregroundAgent: ForegroundAgentManifest; + toolPolicy: ToolPolicyDefinition; +} + +export type AgentRoleDefinition = ForegroundAgentManifest; + +export interface ResolvedBrunchAgentState extends BrunchAgentState { + agentRole: ForegroundAgentManifest['id']; + operationalModeDefinition: OperationalModeDefinition; + agentRoleDefinition: AgentRoleDefinition; +} + +const ELICIT_DELEGATABLE_AGENTS = ['explorer', 'researcher', 'projector', 'reviewer'] as const; + +export const FOREGROUND_AGENT_ROSTER: Record = { + elicit: { + id: 'elicit', + foregroundAgent: { + kind: 'foreground', + id: 'elicitor', + operationalMode: 'elicit', + description: + 'Foreground Brunch session agent that elicits, disambiguates, and captures selected-spec intent.', + model: 'default', + thinking: 'medium', + body: { + source: 'file', + location: bundledAgentBodyRepoPath('elicitor'), + }, + skills: { + strategies: ['freestyle', 'step-wise-decision-tree', 'step-wise-disambiguate'], + lenses: ['intent', 'design', 'oracle'], + methods: AGENT_METHOD_IDS, + }, + tools: ['read', 'grep', 'find', 'ls', 'web_fetch', 'web_search'], + canDelegate: ELICIT_DELEGATABLE_AGENTS, + defaultStrategy: 'auto', + defaultLens: 'auto', + toolAuthority: + 'elicit read-only; graph writes only through Brunch graph tools when legal methods allow them', + }, + toolPolicy: { + id: 'elicit-read-only', + baseAllowedToolNames: ['read', 'grep', 'find', 'ls', 'web_fetch', 'web_search'], + blockedToolNames: ['bash', 'edit', 'write'], + }, + }, + execute: { + id: 'execute', + foregroundAgent: { + kind: 'foreground', + id: 'orchestrator', + operationalMode: 'execute', + description: + 'Foreground Brunch execute-mode agent that coordinates task execution through code-owned tools.', + model: 'default', + thinking: 'medium', + body: { + source: 'file', + location: bundledAgentBodyRepoPath('orchestrator'), + }, + skills: { + strategies: [], + lenses: [], + methods: [], + }, + tools: ['read', 'grep', 'find', 'ls', 'web_fetch', 'web_search', BRUNCH_ORCHESTRATOR_STUB_TOOL], + canDelegate: [], + defaultStrategy: 'auto', + defaultLens: 'auto', + toolAuthority: + 'execute orchestrator read-only plus a code-owned stub tool; direct shell and file writes are blocked', + }, + toolPolicy: { + id: 'execute-orchestrator', + baseAllowedToolNames: [ + 'read', + 'grep', + 'find', + 'ls', + 'web_fetch', + 'web_search', + BRUNCH_ORCHESTRATOR_STUB_TOOL, + ], + blockedToolNames: ['bash', 'edit', 'write'], + }, + }, +}; + +export const AUTO_EXCLUDED_STRATEGIES = new Set(['freestyle']); + +const LENS_CAPABILITY: Partial> = { + design: 'generative-lens', + oracle: 'generative-lens', +}; + +export function isCapabilityLegalForGaps( + capability: CapabilityId | undefined, + gaps: readonly ElicitationGap[], +): boolean { + // Floor options carry no capability gate — always legal. + if (!capability) return true; + // A `negotiate` outcome omits the option (readiness, not a refusal — I31-L holds at the + // execution boundary). A missing-register-kind throw is a seeding/config bug and must + // fail loud (gaps-node-kind-reference: config bug ≠ uncovered) — do not swallow it. + return evaluateCapabilityReadiness(capability, gaps).status !== 'negotiate'; +} + +export type RuntimeAffordanceAxis = 'strategy' | 'lens'; + +export function axisOptionsForRuntimeState( + axis: 'strategy', + state: ResolvedBrunchAgentState, + gaps: readonly ElicitationGap[], +): readonly AgentStrategyId[]; +export function axisOptionsForRuntimeState( + axis: 'lens', + state: ResolvedBrunchAgentState, + gaps: readonly ElicitationGap[], +): readonly AgentLensId[]; +export function axisOptionsForRuntimeState( + axis: RuntimeAffordanceAxis, + state: ResolvedBrunchAgentState, + gaps: readonly ElicitationGap[], +): readonly (AgentStrategyId | AgentLensId)[] { + if (axis === 'strategy') { + const legal = pinnableAxisOptionsForRuntimeState('strategy', state, gaps); + return state.agentStrategy === 'auto' ? legal.filter((id) => !AUTO_EXCLUDED_STRATEGIES.has(id)) : legal; + } + return pinnableAxisOptionsForRuntimeState('lens', state, gaps); +} + +/** + * Options a user may explicitly pin on a user-mutable axis: role-allowed and + * capability-readiness-legal over the selected spec's gaps (D74-L). Unlike the + * AUTO-manifest view (`axisOptionsForRuntimeState`), the pin surface never + * applies the AUTO exclusion — `freestyle` is an explicit user pin (D66-L). + * `goal` is not user-mutable (D59-L) and has no pin surface. + */ +export function pinnableAxisOptionsForRuntimeState( + axis: 'strategy', + state: ResolvedBrunchAgentState, + gaps: readonly ElicitationGap[], +): readonly AgentStrategyId[]; +export function pinnableAxisOptionsForRuntimeState( + axis: 'lens', + state: ResolvedBrunchAgentState, + gaps: readonly ElicitationGap[], +): readonly AgentLensId[]; +export function pinnableAxisOptionsForRuntimeState( + axis: 'strategy' | 'lens', + state: ResolvedBrunchAgentState, + gaps: readonly ElicitationGap[], +): readonly (AgentStrategyId | AgentLensId)[] { + if (axis === 'strategy') { + return state.agentRoleDefinition.skills.strategies; + } + return state.agentRoleDefinition.skills.lenses.filter((id) => + isCapabilityLegalForGaps(LENS_CAPABILITY[id], gaps), + ); +} + +export function defaultStrategyForRuntimeState(state: ResolvedBrunchAgentState): AgentStrategySelection { + return state.agentRoleDefinition.defaultStrategy; +} + +export function defaultLensForRuntimeState(state: ResolvedBrunchAgentState): AgentLensSelection { + return state.agentRoleDefinition.defaultLens; +} + +export function toolPolicyForRuntimeState(state: ResolvedBrunchAgentState): ToolPolicyDefinition { + return state.operationalModeDefinition.toolPolicy; +} + +export function delegatableAgentsForRuntimeState(state: ResolvedBrunchAgentState): readonly string[] { + return state.agentRoleDefinition.canDelegate; +} + +export function isToolBlockedForRuntimeState(state: ResolvedBrunchAgentState, toolName: string): boolean { + return toolPolicyForRuntimeState(state).blockedToolNames.includes(toolName); +} diff --git a/src/agents/runtime/state.ts b/src/agents/runtime/state.ts index f0f67ea5b..3a8d79284 100644 --- a/src/agents/runtime/state.ts +++ b/src/agents/runtime/state.ts @@ -1,12 +1,5 @@ import type { ElicitationGap } from '../../graph/schema/elicitation-gaps.js'; import type { CapabilityId } from '../../projections/session/capability-readiness.js'; -import { - AUTO_EXCLUDED_STRATEGIES, - axisOptionsForRuntimeState, - isCapabilityLegalForGaps, - toolPolicyForRuntimeState, - type ResolvedBrunchAgentState, -} from '../../projections/session/runtime-policy.js'; import { AGENT_LENS_IDS, AGENT_METHOD_IDS, @@ -15,6 +8,13 @@ import { type AgentRoleId, } from '../../session/schema/kinds.js'; import { bundledAgentBodyLocation } from '../registry.js'; +import { + AUTO_EXCLUDED_STRATEGIES, + axisOptionsForRuntimeState, + isCapabilityLegalForGaps, + toolPolicyForRuntimeState, + type ResolvedBrunchAgentState, +} from './policy.js'; import { loadPromptResourceManifestEntries, type PromptManifests, diff --git a/src/projections/session/__tests__/affordances.test.ts b/src/projections/session/__tests__/affordances.test.ts index 5ab58f32e..afc68ca0c 100644 --- a/src/projections/session/__tests__/affordances.test.ts +++ b/src/projections/session/__tests__/affordances.test.ts @@ -3,10 +3,13 @@ import { fileURLToPath } from 'node:url'; import { describe, expect, it } from 'vitest'; +import { + axisOptionsForRuntimeState, + pinnableAxisOptionsForRuntimeState, +} from '../../../agents/runtime/policy.js'; import { groundingFloorGaps } from '../../../graph/schema/elicitation-gap-fixtures.js'; import { DEFAULT_BRUNCH_AGENT_STATE } from '../../../session/runtime-state.js'; import { affordances } from '../affordances.js'; -import { axisOptionsForRuntimeState, pinnableAxisOptionsForRuntimeState } from '../runtime-policy.js'; import { resolveBrunchAgentState } from '../runtime-state.js'; function resolved(overrides: Partial = {}) { diff --git a/src/projections/session/__tests__/readiness-estimate.test.ts b/src/projections/session/__tests__/readiness-estimate.test.ts index 5d4e6579c..797203930 100644 --- a/src/projections/session/__tests__/readiness-estimate.test.ts +++ b/src/projections/session/__tests__/readiness-estimate.test.ts @@ -69,7 +69,7 @@ describe('readiness estimate projection', () => { expect(driverSource).not.toMatch(/NODE_KIND_METADATA|bandsForKind|schema\/nodes/); for (const relativePath of [ - '../runtime-policy.ts', + '../../../agents/runtime/policy.ts', '../affordances.ts', '../../../agents/runtime/state.ts', ]) { diff --git a/src/projections/session/affordances.ts b/src/projections/session/affordances.ts index 95a13144e..dd7b31f4e 100644 --- a/src/projections/session/affordances.ts +++ b/src/projections/session/affordances.ts @@ -1,3 +1,9 @@ +import { + axisOptionsForRuntimeState, + defaultLensForRuntimeState, + defaultStrategyForRuntimeState, + type ResolvedBrunchAgentState, +} from '../../agents/runtime/policy.js'; import type { ElicitationGap } from '../../graph/schema/elicitation-gaps.js'; import type { AgentLensId, @@ -5,12 +11,6 @@ import type { AgentStrategyId, AgentStrategySelection, } from '../../session/schema/kinds.js'; -import { - axisOptionsForRuntimeState, - defaultLensForRuntimeState, - defaultStrategyForRuntimeState, - type ResolvedBrunchAgentState, -} from './runtime-policy.js'; interface AxisAffordance { readonly selection: TSelection; diff --git a/src/projections/session/runtime-policy.ts b/src/projections/session/runtime-policy.ts index 907c9ae3e..b03fdbe70 100644 --- a/src/projections/session/runtime-policy.ts +++ b/src/projections/session/runtime-policy.ts @@ -1,204 +1,4 @@ -import { bundledAgentBodyRepoPath } from '../../agents/registry.js'; -import type { ElicitationGap } from '../../graph/schema/elicitation-gaps.js'; -import type { BrunchAgentState, ToolPolicyId } from '../../session/runtime-state.js'; -import type { ForegroundAgentManifest } from '../../session/schema/agent-manifest.js'; -import type { - AgentLensId, - AgentLensSelection, - AgentStrategyId, - AgentStrategySelection, - OperationalModeId, -} from '../../session/schema/kinds.js'; -import { AGENT_METHOD_IDS } from '../../session/schema/kinds.js'; -import { BRUNCH_ORCHESTRATOR_STUB_TOOL } from '../../session/schema/tool-names.js'; -import { evaluateCapabilityReadiness, type CapabilityId } from './capability-readiness.js'; - -export interface ToolPolicyDefinition { - id: ToolPolicyId; - baseAllowedToolNames: readonly string[]; - blockedToolNames: readonly string[]; -} - -export interface OperationalModeDefinition { - id: OperationalModeId; - foregroundAgent: ForegroundAgentManifest; - toolPolicy: ToolPolicyDefinition; -} - -export type AgentRoleDefinition = ForegroundAgentManifest; - -export interface ResolvedBrunchAgentState extends BrunchAgentState { - agentRole: ForegroundAgentManifest['id']; - operationalModeDefinition: OperationalModeDefinition; - agentRoleDefinition: AgentRoleDefinition; -} - -const ELICIT_DELEGATABLE_AGENTS = ['explorer', 'researcher', 'projector', 'reviewer'] as const; - -export const FOREGROUND_AGENT_ROSTER: Record = { - elicit: { - id: 'elicit', - foregroundAgent: { - kind: 'foreground', - id: 'elicitor', - operationalMode: 'elicit', - description: - 'Foreground Brunch session agent that elicits, disambiguates, and captures selected-spec intent.', - model: 'default', - thinking: 'medium', - body: { - source: 'file', - location: bundledAgentBodyRepoPath('elicitor'), - }, - skills: { - strategies: ['freestyle', 'step-wise-decision-tree', 'step-wise-disambiguate'], - lenses: ['intent', 'design', 'oracle'], - methods: AGENT_METHOD_IDS, - }, - tools: ['read', 'grep', 'find', 'ls', 'web_fetch', 'web_search'], - canDelegate: ELICIT_DELEGATABLE_AGENTS, - defaultStrategy: 'auto', - defaultLens: 'auto', - toolAuthority: - 'elicit read-only; graph writes only through Brunch graph tools when legal methods allow them', - }, - toolPolicy: { - id: 'elicit-read-only', - baseAllowedToolNames: ['read', 'grep', 'find', 'ls', 'web_fetch', 'web_search'], - blockedToolNames: ['bash', 'edit', 'write'], - }, - }, - execute: { - id: 'execute', - foregroundAgent: { - kind: 'foreground', - id: 'orchestrator', - operationalMode: 'execute', - description: - 'Foreground Brunch execute-mode agent that coordinates task execution through code-owned tools.', - model: 'default', - thinking: 'medium', - body: { - source: 'file', - location: bundledAgentBodyRepoPath('orchestrator'), - }, - skills: { - strategies: [], - lenses: [], - methods: [], - }, - tools: ['read', 'grep', 'find', 'ls', 'web_fetch', 'web_search', BRUNCH_ORCHESTRATOR_STUB_TOOL], - canDelegate: [], - defaultStrategy: 'auto', - defaultLens: 'auto', - toolAuthority: - 'execute orchestrator read-only plus a code-owned stub tool; direct shell and file writes are blocked', - }, - toolPolicy: { - id: 'execute-orchestrator', - baseAllowedToolNames: [ - 'read', - 'grep', - 'find', - 'ls', - 'web_fetch', - 'web_search', - BRUNCH_ORCHESTRATOR_STUB_TOOL, - ], - blockedToolNames: ['bash', 'edit', 'write'], - }, - }, -}; - -export const AUTO_EXCLUDED_STRATEGIES = new Set(['freestyle']); - -const LENS_CAPABILITY: Partial> = { - design: 'generative-lens', - oracle: 'generative-lens', -}; - -export function isCapabilityLegalForGaps( - capability: CapabilityId | undefined, - gaps: readonly ElicitationGap[], -): boolean { - // Floor options carry no capability gate — always legal. - if (!capability) return true; - // A `negotiate` outcome omits the option (readiness, not a refusal — I31-L holds at the - // execution boundary). A missing-register-kind throw is a seeding/config bug and must - // fail loud (gaps-node-kind-reference: config bug ≠ uncovered) — do not swallow it. - return evaluateCapabilityReadiness(capability, gaps).status !== 'negotiate'; -} - -export type RuntimeAffordanceAxis = 'strategy' | 'lens'; - -export function axisOptionsForRuntimeState( - axis: 'strategy', - state: ResolvedBrunchAgentState, - gaps: readonly ElicitationGap[], -): readonly AgentStrategyId[]; -export function axisOptionsForRuntimeState( - axis: 'lens', - state: ResolvedBrunchAgentState, - gaps: readonly ElicitationGap[], -): readonly AgentLensId[]; -export function axisOptionsForRuntimeState( - axis: RuntimeAffordanceAxis, - state: ResolvedBrunchAgentState, - gaps: readonly ElicitationGap[], -): readonly (AgentStrategyId | AgentLensId)[] { - if (axis === 'strategy') { - const legal = pinnableAxisOptionsForRuntimeState('strategy', state, gaps); - return state.agentStrategy === 'auto' ? legal.filter((id) => !AUTO_EXCLUDED_STRATEGIES.has(id)) : legal; - } - return pinnableAxisOptionsForRuntimeState('lens', state, gaps); -} - -/** - * Options a user may explicitly pin on a user-mutable axis: role-allowed and - * capability-readiness-legal over the selected spec's gaps (D74-L). Unlike the - * AUTO-manifest view (`axisOptionsForRuntimeState`), the pin surface never - * applies the AUTO exclusion — `freestyle` is an explicit user pin (D66-L). - * `goal` is not user-mutable (D59-L) and has no pin surface. - */ -export function pinnableAxisOptionsForRuntimeState( - axis: 'strategy', - state: ResolvedBrunchAgentState, - gaps: readonly ElicitationGap[], -): readonly AgentStrategyId[]; -export function pinnableAxisOptionsForRuntimeState( - axis: 'lens', - state: ResolvedBrunchAgentState, - gaps: readonly ElicitationGap[], -): readonly AgentLensId[]; -export function pinnableAxisOptionsForRuntimeState( - axis: 'strategy' | 'lens', - state: ResolvedBrunchAgentState, - gaps: readonly ElicitationGap[], -): readonly (AgentStrategyId | AgentLensId)[] { - if (axis === 'strategy') { - return state.agentRoleDefinition.skills.strategies; - } - return state.agentRoleDefinition.skills.lenses.filter((id) => - isCapabilityLegalForGaps(LENS_CAPABILITY[id], gaps), - ); -} - -export function defaultStrategyForRuntimeState(state: ResolvedBrunchAgentState): AgentStrategySelection { - return state.agentRoleDefinition.defaultStrategy; -} - -export function defaultLensForRuntimeState(state: ResolvedBrunchAgentState): AgentLensSelection { - return state.agentRoleDefinition.defaultLens; -} - -export function toolPolicyForRuntimeState(state: ResolvedBrunchAgentState): ToolPolicyDefinition { - return state.operationalModeDefinition.toolPolicy; -} - -export function delegatableAgentsForRuntimeState(state: ResolvedBrunchAgentState): readonly string[] { - return state.agentRoleDefinition.canDelegate; -} - -export function isToolBlockedForRuntimeState(state: ResolvedBrunchAgentState, toolName: string): boolean { - return toolPolicyForRuntimeState(state).blockedToolNames.includes(toolName); -} +// Temporary migration bridge: foreground agent runtime policy now lives in +// `src/agents/runtime/policy.ts`. This file is removed by the next refactor +// item once topology docs/import guards stop naming the old projections owner. +export * from '../../agents/runtime/policy.js'; diff --git a/src/projections/session/runtime-state.ts b/src/projections/session/runtime-state.ts index 374b6060b..2b9704ebe 100644 --- a/src/projections/session/runtime-state.ts +++ b/src/projections/session/runtime-state.ts @@ -1,5 +1,6 @@ import type { FileEntry } from '@earendil-works/pi-coding-agent'; +import { FOREGROUND_AGENT_ROSTER, type ResolvedBrunchAgentState } from '../../agents/runtime/policy.js'; import { assertLinearBrunchSessionEnvelope, type BrunchSessionEnvelope, @@ -16,10 +17,9 @@ import type { AgentStrategySelection, OperationalModeId, } from '../../session/schema/kinds.js'; -import { FOREGROUND_AGENT_ROSTER, type ResolvedBrunchAgentState } from './runtime-policy.js'; -export type { ResolvedBrunchAgentState } from './runtime-policy.js'; -export { FOREGROUND_AGENT_ROSTER, delegatableAgentsForRuntimeState } from './runtime-policy.js'; +export type { ResolvedBrunchAgentState } from '../../agents/runtime/policy.js'; +export { FOREGROUND_AGENT_ROSTER, delegatableAgentsForRuntimeState } from '../../agents/runtime/policy.js'; export { DEFAULT_BRUNCH_AGENT_STATE } from '../../session/runtime-state.js'; export interface RuntimeStateProjection { From f09246ffc38792be5981e7a4dd3d75f5b977609b Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Fri, 26 Jun 2026 09:22:28 +0200 Subject: [PATCH 22/54] Delete projection runtime-policy bridge --- memory/REFACTOR.md | 2 +- memory/SPEC.md | 18 +++++++++--------- src/README.md | 6 +++--- src/agents/README.md | 7 ++++--- src/agents/prompts/README.md | 2 +- src/projections/README.md | 3 +-- .../__tests__/topology-boundaries.test.ts | 4 +--- .../session/__tests__/affordances.test.ts | 6 ++++-- src/projections/session/runtime-policy.ts | 4 ---- src/session/README.md | 2 +- src/treedocs.yaml | 2 +- 11 files changed, 26 insertions(+), 30 deletions(-) delete mode 100644 src/projections/session/runtime-policy.ts diff --git a/memory/REFACTOR.md b/memory/REFACTOR.md index 50161581c..9dd54e661 100644 --- a/memory/REFACTOR.md +++ b/memory/REFACTOR.md @@ -51,7 +51,7 @@ The key judo move is deletion, not another rearrangement: remove the temporary p 1. [x] Add or move characterization tests for the two remaining orphan human/product renderers at their target owners: print-mode workspace text under the app layer, and debug transcript markdown under the session layer. Keep expected bytes unchanged. 2. [x] Move the foreground agent roster and tool-policy definitions into the agent runtime owner, then update projection and adapter callers to import runtime policy from `src/agents/runtime/`. Leave transcript-state projection in `projections/session`. -3. [ ] Delete the `projections/session -> agents/registry` temporary edge and update topology docs/tests so projections no longer own or import agent body locations, foreground roster, or tool policy. +3. [x] Delete the `projections/session -> agents/registry` temporary edge and update topology docs/tests so projections no longer own or import agent body locations, foreground roster, or tool policy. 4. [ ] Repair `read_graph related` by making it share the semantic relation vocabulary already used by neighborhood rendering, or by deleting the separate related formatter in favor of a filtered-neighborhood render. Add a focused golden/invariant for the related mode so raw category/direction arrows and raw fallback ids do not silently return. 5. [ ] Move print-mode workspace-state text out of `src/renderers/` to the app/print owner, update `brunch --mode print` imports, and delete the old workspace renderer file/directory. 6. [ ] Move debug transcript markdown out of `src/renderers/` to the session owner, update `session-transcript.ts` imports, and delete the old session renderer file/directory. diff --git a/memory/SPEC.md b/memory/SPEC.md index dee7afb46..149defbc3 100644 --- a/memory/SPEC.md +++ b/memory/SPEC.md @@ -130,10 +130,10 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c - **D2-L — Brunch is an opinionated product, not a pi platform shell.** The POC hardcodes its toolset, system prompt, and policy doctrine; scopes state to `.brunch/`; and hides pi's generic extension surface from end users. Depends on: A1-L. Supersedes: —. - **D39-L — Brunch owns sealed Pi settings plus an explicit Brunch extension bundle around the embedded harness.** Product behavior must come from Brunch-owned programmatic policy, not ambient Pi discovery. The settings layer must stay sealed (no ambient global/project `.pi` behavior shaping, no ambient resource discovery, offline-by-default for Brunch-launched Pi), and the product extension bundle must remain an explicit static registrar list rather than any filesystem discovery or runtime `import()` path. Current sealed-profile policy and bundle topology live in [`src/.pi/README.md`](src/.pi/README.md), [`src/.pi/extensions/README.md`](src/.pi/extensions/README.md), [`src/app/pi-settings.ts`](src/app/pi-settings.ts), [`src/app/pi-extensions.ts`](src/app/pi-extensions.ts), and [`src/dev/README.md`](src/dev/README.md). Depends on: D1-L, D2-L, A19-L. Supersedes: treating `noSkills: true` as full profile isolation, relying on user/project `.pi/` defaults to be harmless, nesting Brunch's product extension modules under `src/.pi/extensions/brunch/`, or replacing the explicit static extension list with a Brunch-internal filesystem-discovery / `brunchExtensionMeta` / `loadOrder` mechanism as the product runtime load path. - Tooling exception: the worktree helper extension now lives outside this repository under the user Pi agent tree (`~/.pi/agent/extensions/worktree/index.ts`) for direct Pi sessions only. It is not a Brunch product extension, is not imported by `src/.pi/brunch-pi-extensions.ts`, and does not weaken the sealed Brunch Pi settings/extensions boundary; Brunch-launched product sessions continue to disable ambient `.pi/` discovery unless deliberately imported. The extension may register direct-Pi `/worktree:switch` / `switch_worktree` and `/worktree:create` / `create_worktree` affordances, but Brunch does not test, package, or document it as a product extension. -- **D40-L — Runtime state is transcript-backed Brunch session-agent state, not hidden extension memory.** The architectural commitment is that Brunch session-agent posture remains transcript-backed Pi JSONL state rather than hidden extension memory: posture switches are user/system authority, the foreground session agent is derived from `op_mode`, child axes default back to `AUTO` when invalidated, and tool authority remains `op_mode`-gated rather than prompt-composition-owned. Runtime-state entries are Pi JSONL state-change facts, not assistant/user chat content: init and switch entries should render, when visible, as dim non-chat state rows analogous to Pi thinking/model-change rows, and must not enter LLM context as ordinary conversation. Current materialized state lives in [`src/session/README.md`](src/session/README.md), [`src/projections/README.md`](src/projections/README.md), [`src/.pi/README.md`](src/.pi/README.md), [`src/agents/README.md`](src/agents/README.md), [`src/agents/prompts/README.md`](src/agents/prompts/README.md), and [`src/projections/session/runtime-policy.ts`](src/projections/session/runtime-policy.ts). Depends on: D17-L, D23-L, D25-L, D39-L, D58-L, D59-L. Refined by: D85-L (the AUTO-able objective axes reduce to `strategy` + `lens`; `goal` is dropped from the runtime-state axis set and inlined into the agent role prompt). Supersedes: mode-only vocabulary, extension-local mutable state as authority, storing the foreground role as independent session state, the "runtime bundle / role preset" as one knob deriving model/thinking/resources, and binding prompt-resource location to `src/.pi/context/`. +- **D40-L — Runtime state is transcript-backed Brunch session-agent state, not hidden extension memory.** The architectural commitment is that Brunch session-agent posture remains transcript-backed Pi JSONL state rather than hidden extension memory: posture switches are user/system authority, the foreground session agent is derived from `op_mode`, child axes default back to `AUTO` when invalidated, and tool authority remains `op_mode`-gated rather than prompt-composition-owned. Runtime-state entries are Pi JSONL state-change facts, not assistant/user chat content: init and switch entries should render, when visible, as dim non-chat state rows analogous to Pi thinking/model-change rows, and must not enter LLM context as ordinary conversation. Current materialized state lives in [`src/session/README.md`](src/session/README.md), [`src/projections/README.md`](src/projections/README.md), [`src/.pi/README.md`](src/.pi/README.md), [`src/agents/README.md`](src/agents/README.md), [`src/agents/prompts/README.md`](src/agents/prompts/README.md), and [`src/agents/runtime/README.md`](src/agents/runtime/README.md). Depends on: D17-L, D23-L, D25-L, D39-L, D58-L, D59-L. Refined by: D85-L (the AUTO-able objective axes reduce to `strategy` + `lens`; `goal` is dropped from the runtime-state axis set and inlined into the agent role prompt). Supersedes: mode-only vocabulary, extension-local mutable state as authority, storing the foreground role as independent session state, the "runtime bundle / role preset" as one knob deriving model/thinking/resources, and binding prompt-resource location to `src/.pi/context/`. - **D34-L — Command containment separates visibility suppression from effect blocking.** Current Pi extension seams can hide unsupported slash suggestions with autocomplete wrapping and can cancel branch/session effects through lifecycle hooks, but they cannot strictly suppress exact interactive built-in commands before `InteractiveMode` dispatches them. Brunch-owned commands must use product-specific names and route writes through Brunch handlers/`CommandExecutor`; extension command collisions are not an override mechanism. Strict built-in command/keybinding policy is a Pi upstream/API ask, while POC safety relies on hiding generic affordances, blocking dangerous effects (`/fork`, `/clone`, raw session replacement), allowing native `/tree` as inspection/navigation, and failing fast on branched transcripts. Brunch's command-policy code should live in `src/.pi/extensions/commands/policy.ts`, merging branch/session-effect blocking with any product command allow/deny behavior instead of preserving a branch-only module. Depends on: D2-L, D24-L, A18-L. Supersedes: treating extension `input` handlers or command-name collisions as built-in command allowlisting. - **D35-L — Dynamic TUI chrome is a Brunch projection wrapper over Pi UI primitives.** The architectural commitment is that downstream TUI affordances call one Brunch-owned renderer (`renderBrunchChrome` or its successor) with a single activated product-state value rather than scattering raw `ctx.ui.setHeader` / `setFooter` / `setWidget` / title / working-indicator calls; the wrapper is stateless projection over canonical workspace/session/graph facts, never its own mutable state. Chrome is a project-first shell surface with selected-spec context — project name labels the cwd container, spec title labels the selected graph, session label distinguishes transcript instances — and a session label must never replace spec identity or graph truth. Chrome must not consume the status-key namespace for its own summary (`ctx.ui.setStatus` stays a lateral channel for other extensions), must not advertise unwired affordances, and RPC clients must rely only on surfaces Pi actually emits (header/footer/working-indicator are TUI-only in current Pi RPC mode). Current chrome state shape, render surfaces, telemetry/refresh, startup-header behavior, and status-key filtering live in [`src/.pi/extensions/chrome/README.md`](src/.pi/extensions/chrome/README.md); launch/activation wiring lives in [`src/.pi/extensions/README.md`](src/.pi/extensions/README.md). Depends on: D2-L, D21-L, D34-L, A18-L. Supersedes: treating Pi UI methods as direct downstream affordance APIs, rendering placeholder session state such as `unbound` after a session is activated, consuming the status-key namespace for chrome's own static summary, using spec title as the default session label, or allowing two unchanged Brunch-created default names to collide in one cwd, and the earlier resume/open-launches-stay-quiet clause (superseded 2026-06-11: the shipped, test-locked behavior headers every non-cancel activation). -- **D52-L — Source topology targets `src/{app, workspace, scripts, agents, .pi, db, graph, session, projections, renderers, rpc, web}` with directed layer dependencies.** Reusable projection and rendering modules live in top-level `src/projections/` and `src/renderers/` rather than whichever domain or adapter first needed them; `src/agents/` is the Pi-independent owner for Brunch-authored LLM context ingress (currently bundled agent prompt bodies, prompt-resource skills, prompt composition, prompt-resource/tool legality, seed context composition, reusable agent-visible renderers, adapter-local tool/session text promoted into contexts, and the central registry for prompt/skill file paths; the `projections/session/runtime-policy.ts` registry import is a temporary migration edge until the foreground roster moves under `agents/runtime/`); domain layers (`graph/`, `session/`) and the reusable `projections` / `renderers` / `agents` layers must not import adapters, transports, app entrypoints, or web code; `graph/` is the only layer that imports `db/`, plus the single sanctioned `db/`→`graph/schema/kinds.ts` taxonomy edge (D73-L). The concrete per-directory ownership, layout sketch, and full import matrix are owned by [`src/README.md`](src/README.md). Depends on: D2-L, D4-L, D39-L, D40-L. Refined by: D73-L. Supersedes: scattering session domain files at `src/` root; treating Pi-only agents as a host-independent top-level `src/.pi/` layer; nesting prompt composition under `src/.pi/context/`; treating reusable `project` / `format` helpers as owned by whichever adapter first needed them; treating retired `.pi/agents` / `.pi/skills` locations as the long-term conceptual owner for Brunch-authored model-facing content. +- **D52-L — Source topology targets `src/{app, workspace, scripts, agents, .pi, db, graph, session, projections, renderers, rpc, web}` with directed layer dependencies.** Reusable projection and rendering modules live in top-level `src/projections/` and `src/renderers/` rather than whichever domain or adapter first needed them; `src/agents/` is the Pi-independent owner for Brunch-authored LLM context ingress and foreground runtime policy (currently bundled agent prompt bodies, prompt-resource skills, foreground roster/tool policy, prompt composition, prompt-resource/tool legality, seed context composition, reusable agent-visible renderers, adapter-local tool/session text promoted into contexts, and the central registry for prompt/skill file paths); domain layers (`graph/`, `session/`) and the reusable `projections` / `renderers` / `agents` layers must not import adapters, transports, app entrypoints, or web code; `graph/` is the only layer that imports `db/`, plus the single sanctioned `db/`→`graph/schema/kinds.ts` taxonomy edge (D73-L). The concrete per-directory ownership, layout sketch, and full import matrix are owned by [`src/README.md`](src/README.md). Depends on: D2-L, D4-L, D39-L, D40-L. Refined by: D73-L. Supersedes: scattering session domain files at `src/` root; treating Pi-only agents as a host-independent top-level `src/.pi/` layer; nesting prompt composition under `src/.pi/context/`; treating reusable `project` / `format` helpers as owned by whichever adapter first needed them; treating retired `.pi/agents` / `.pi/skills` locations as the long-term conceptual owner for Brunch-authored model-facing content. - **D73-L — Domain enum taxonomy is owned by drizzle-free schema leaves; persistence and adapters are consumers, not the source.** The closed enum `const` arrays that define graph vocabulary — node kinds (`INTENT_KINDS`, `ORACLE_KINDS`, `DESIGN_KINDS`, `PLAN_KINDS`), `NODE_PLANES` (`intent`/`oracle`/`design`/`plan`), `NODE_BASES`, `EDGE_CATEGORIES`, `EDGE_STANCES`, `READINESS_BANDS`, `LENS_AFFINITIES`, `GAP_DISPOSITIONS`, and `GAP_PREDICATE_KINDS` — live in `graph/schema/kinds.ts`, a pure constants leaf that imports nothing (no drizzle, no `graph/atoms`). Both `db/schema.ts` (for `text({ enum })` column constraints, including the previously-inlined `plane` columns) and `graph/` domain modules import the arrays from this leaf; `graph/index.ts` re-exports them from the leaf so non-graph layers still avoid importing `db/` directly (I26-L). Session runtime axis vocabulary mirrors the same ownership direction in `session/schema/kinds.ts`: that leaf imports nothing and owns the `op_mode`, agent-role, `strategy`, `lens`, `auto`, and display-only planned mode choices consumed by `session/runtime-state.ts`, `projections/session/*`, and `agents/runtime/state.ts`; it deliberately contains no `goal` axis and no retired `READINESS_GRADES`. Derivations stay where they are read: `NODE_KIND_METADATA`, `formatGraphNodeCode`, `parseGraphNodeCode`, and `intentKindCategory` remain in `graph/schema/nodes.ts` (D62-L). The motivating defect: because `db/schema.ts` eagerly evaluates `sqliteTable(...)` and `verbatimModuleSyntax` emits even type-only imports at runtime, any value-import path from `web/` into the old taxonomy location pulled Drizzle into the browser bundle. Locating taxonomy in a drizzle-free leaf makes the `web/` build target structurally Drizzle-free (I44-L) and corrects the ownership direction so the domain, not the persistence layer, owns its vocabulary. Vocabulary migration status: `READINESS_GRADES` is retired (readiness is no longer a stored grade, D45-L), `ELICITATION_BACKLOG_STATUSES` is replaced by the `elicitation_gaps` disposition + predicate-shape enums (D65-L), and `READINESS_BANDS` stays. Depends on: D16-L, D52-L, D54-L, D62-L, D63-L, D64-L; I26-L. Supersedes: `db/schema.ts` owning the shared enum `const` arrays and the "enum literals flow outward from `db/schema.ts`" posture; the triplicated inline `['intent','oracle','design','plan']` plane literals. #### Data model & vocabulary @@ -152,9 +152,9 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c - **D63-L — Graph `basis` records item-level approval strength, not the mutation pathway.** Accepted nodes and edges use `basis ∈ explicit | implicit`. `explicit` means the user directly stated the graph item or approved the exact node/edge in a review set; `implicit` means the user accepted a concept/proposal and the agent materialized specific graph items to match it without per-item review (the `propose-graph` direct-commit path). The mutation pathway lives in `change_log.operation` and payload (`mutate_graph`, `accept_review_set`, post-exchange capture, etc.), while epistemic attribution lives in `Node.source` and proposal UI metadata may still carry `epistemic_status`. Low-confidence inferred material is still not graph truth; it remains in preface/capture analysis/review drafts/reconciliation needs until clarified or accepted. More abstractly, `basis` is a *provenance-directness* marker — directly from the user (`explicit`) versus agent-materialized from user input (`implicit`) — of which item-level approval strength is the claim-flavored reading; this lets the same `explicit | implicit` distinction apply to non-claim registers such as `elicitation_gaps` (user-raised vs agent-inferred, D65-L). Depends on: D26-L, D27-L, D53-L, D54-L, D55-L. Supersedes: `basis = accepted_review_set` as a persisted graph enum value and any interpretation of `basis` as a provenance/path field. - **D64-L — Readiness bands are the coarse advisory coverage axis; D94-L materializes the current four-band derived model.** Bands are `grounding`, `elicitation`, `projection`, and `commitment`; they are non-exclusive node-kind groupings derived by `bandsForKind(kind)` in `src/graph/schema/nodes.ts`, not stored per-kind metadata and not structural legality gates. The derivation is `design` + `oracle` → `projection`, `plan` → `commitment`, and intent-plane kinds via a hand-maintained bisection: `goal`/`thesis` → `grounding`, `story`/`unknown`/`assumption`/`invariant`/`decision` → `elicitation`, `requirement`/`criterion` → `commitment`, `context` + `constraint` → dual `grounding` + `elicitation`, with `example`/`sketch`/`term` explicitly band-less. Two carriers must stay separate (I50-L): the asking agenda and soft readiness estimate read `gap.band`, while graph filters/rendering/thresholds read derived node band membership. Bands guide what the elicitor is trying to complete, what graph filters and rendered context show, the per-band **readiness estimate** rollup (D45-L), and which gaps a capability-readiness judgment weighs (D74-L). The `CommandExecutor` must not reject a clear later-band kind merely because of band; readiness controls objectives and capability judgment, not what graph truth may contain (I31-L). Depends on: D45-L, D56-L, D57-L, D59-L, D60-L, D65-L. Refined by: D94-L (four bands with two super-types; node band membership is derived from `plane` rather than declared per-kind; the asking-agenda reader reads `gap.band`, not the node-kind table). Supersedes: treating the intent `basic | structural | reasoning` category as the readiness taxonomy, treating readiness as a per-kind creation whitelist, treating bands as a grade rubric for a stored grade, or treating the earlier design doc's duplicated readiness table as the source of truth. - **D65-L — `elicitation_gaps` are typed coverage *obligations* (typologies) — the elicitor's prospective-memory agenda and the substrate of capability-readiness judgment; they guide and modulate, they never hard-gate.** Renamed and reconceived from `elicitation_backlog`. A gap is an obligation register entry, not domain content: the anti-shadowing line remains that the table holds obligation / disposition / meta only, never graph truth. Importance remains pre-answer weight and coverage remains post-answer derived strength; they must not collapse into one ambiguous field. `basis` still follows provenance-directness (D63-L), and `not_applicable` / `irrelevant` / `reopened` remain legitimate disposition semantics. The process-vs-domain split also remains: these are elicitation-process gaps, not domain-gap graph nodes, and an open grounding gap must never wall the candidate-proposal / disambiguation UX that fills it. Current materialized register shape, ownership, seeding, and predicate/disposition mechanics live in [`src/graph/README.md`](src/graph/README.md) and [`src/graph/schema/elicitation-gaps.ts`](src/graph/schema/elicitation-gaps.ts). Still open: whether the register eventually thins the `goal` axis (D59-L); capture-reflection writeback is now *designed* (D81-L: low-confidence noticings spawn gaps; the sweep closes answered gaps) with implementation pending in FE-861. Depends on: D8-L, D30-L, D45-L, D57-L, D59-L, D60-L, D63-L, D64-L, D74-L. Refined by: D75-L (gaps reference graph node kinds via `refersTo: NodeKind`; the parallel grounding-typology catalog and the closed gap-`name` enum are retired — substrate, predicate union, disposition, and anti-shadowing line are unchanged); D81-L (noticings-spawn-gaps is the committed capture-reflection writeback); D82-L (seeding gains the situating gap — orientation anchors routing acquisition modes). Supersedes: the `elicitation_backlog` name and its question-instance / `open | closed`-status model, treating `unknown` as a graph node kind, and any readiness-grade-projection-over-open-counts as authority. -- **D74-L — Capability-readiness is a just-in-time, capability-relative judgment over relevant gaps — it replaces the standing grade gate.** When a capability is requested (a generative lens, `propose-graph`, `project-graph`, commitment review, eventual export), the agent evaluates readiness *for that capability* against the `elicitation_gaps` (D65-L) declared relevant to it. The `capability → relevant gaps` map is **explicit** and subsumes the retired `STRATEGY_MIN_GRADE` / `GOAL_MIN_GRADE` / `LENS_MIN_GRADE` thresholds in `runtime-policy.ts` plus the retired prompt-manifest/tool `METHOD_MIN_GRADE` thresholds in `agents/runtime/state.ts`, which were lossy grade-proxies for "enough grounding". Structurally-obvious relevant gaps (`presence` / `field` / `coverage`) are checked **mechanically** (cheap, no LLM); non-obvious (`manual`) ones consume an **LLM satisficiency judgment** (D57-L). The outcome is one of **proceed**, **proceed at low epistemic status** (density-scaled, D30-L), or **negotiate** — surface an `establishment_offer` ("I can, but answer X and Y first", D32-L). Explicit user/system pins stay legible when they are role/mode-legal; readiness negotiation narrows AUTO choices and gated methods/tools rather than erasing the pin or crashing prompt composition. Capability-readiness fires **on request, reactive-primary** (proactive nudges are a separate later concern) and is the **only readiness gate**: it never bars attempting work, it scales/negotiates. This resolves the prior "lens is never gated" (`ELICITATION_LENSES.md`) vs `LENS_MIN_GRADE` contradiction (lenses are not grade-gated; readiness is JIT-judged) and dissolves the grade-ratchet / two-value problem (the soft `readiness estimate`, D45-L, gates nothing and may regress honestly). A future structural milestone gate for export/plan/execute op-modes is deferred (D45-L) until such an op-mode exists. Depends on: D25-L, D26-L, D30-L, D32-L, D45-L, D57-L, D59-L, D65-L. Refined by: D75-L (the `capability → relevant gaps` map references node kinds, not a closed typology-name enum); D86-L (the "narrows … gated methods/tools" clause no longer applies to graph-write tools — `mutate_graph` and review-set tools are floor; readiness is advisory for them). Supersedes: `GRADE_RANK`-based `MIN_GRADE` hard gating of goal/strategy/lens/method prompt resources and method-coupled tools, and a standing readiness scalar as the authority for capability availability. +- **D74-L — Capability-readiness is a just-in-time, capability-relative judgment over relevant gaps — it replaces the standing grade gate.** When a capability is requested (a generative lens, `propose-graph`, `project-graph`, commitment review, eventual export), the agent evaluates readiness *for that capability* against the `elicitation_gaps` (D65-L) declared relevant to it. The `capability → relevant gaps` map is **explicit** and subsumes the retired `STRATEGY_MIN_GRADE` / `GOAL_MIN_GRADE` / `LENS_MIN_GRADE` thresholds in `agents/runtime/policy.ts` plus the retired prompt-manifest/tool `METHOD_MIN_GRADE` thresholds in `agents/runtime/state.ts`, which were lossy grade-proxies for "enough grounding". Structurally-obvious relevant gaps (`presence` / `field` / `coverage`) are checked **mechanically** (cheap, no LLM); non-obvious (`manual`) ones consume an **LLM satisficiency judgment** (D57-L). The outcome is one of **proceed**, **proceed at low epistemic status** (density-scaled, D30-L), or **negotiate** — surface an `establishment_offer` ("I can, but answer X and Y first", D32-L). Explicit user/system pins stay legible when they are role/mode-legal; readiness negotiation narrows AUTO choices and gated methods/tools rather than erasing the pin or crashing prompt composition. Capability-readiness fires **on request, reactive-primary** (proactive nudges are a separate later concern) and is the **only readiness gate**: it never bars attempting work, it scales/negotiates. This resolves the prior "lens is never gated" (`ELICITATION_LENSES.md`) vs `LENS_MIN_GRADE` contradiction (lenses are not grade-gated; readiness is JIT-judged) and dissolves the grade-ratchet / two-value problem (the soft `readiness estimate`, D45-L, gates nothing and may regress honestly). A future structural milestone gate for export/plan/execute op-modes is deferred (D45-L) until such an op-mode exists. Depends on: D25-L, D26-L, D30-L, D32-L, D45-L, D57-L, D59-L, D65-L. Refined by: D75-L (the `capability → relevant gaps` map references node kinds, not a closed typology-name enum); D86-L (the "narrows … gated methods/tools" clause no longer applies to graph-write tools — `mutate_graph` and review-set tools are floor; readiness is advisory for them). Supersedes: `GRADE_RANK`-based `MIN_GRADE` hard gating of goal/strategy/lens/method prompt resources and method-coupled tools, and a standing readiness scalar as the authority for capability availability. - **D75-L — `elicitation_gaps` reference graph node kinds; the parallel grounding-typology vocabulary is retired.** The commitment is architectural: Brunch has one closed ontology here (`NodeKind`), not a second closed grounding-typology vocabulary; gap naming must stay on the kind layer, while question phrasing remains open and situated. This retires the denormalized grounding catalog and the closed gap-name vocabulary, preserves the anti-shadowing line from D65-L, and keeps example question phrasing as priming rather than schema. Current node-kind-keyed gap shape, grounding-floor seeding, capability-readiness mapping, and priming catalogs live in [`src/graph/README.md`](src/graph/README.md), [`src/graph/schema/elicitation-gaps.ts`](src/graph/schema/elicitation-gaps.ts), [`src/projections/README.md`](src/projections/README.md), [`src/db/README.md`](src/db/README.md), and [`docs/design/ELICITATION_QUESTIONS.md`](docs/design/ELICITATION_QUESTIONS.md). Depends on: D54-L, D56-L, D57-L, D64-L, D65-L, D73-L, D74-L; A27-L (and validated A24-L). Refines: D30-L, D65-L, D74-L. Supersedes: the grounding typology catalog as a parallel closed gap vocabulary; the closed gap-`name` typology enum and the `RelevantGapName` union; and the retired refactor plan to enshrine `GROUNDING_GAP_TYPOLOGIES` as a canonical const. -- **D86-L — Capability-readiness never withholds a graph-write tool; `negotiate` is advisory, not a tool gate.** Readiness modulates: it scales epistemic status (D30-L), narrows AUTO *axis* menus (`strategy`/`lens`), and surfaces the `establishment_offer` — but it must never remove a graph-write tool from the active set. `mutate_graph` (direct commit) and the review-set tools (`present_review_set` / `request_response`) are **floor** capabilities in `elicit` mode whenever gaps exist; the agent always retains the means to commit graph truth and may proceed at low epistemic status when grounding is thin. This re-asserts I31-L ("readiness never bars graph truth or work") against the contrary reading. Motivating reductio: gating `mutate_graph` behind `propose-graph` readiness created a **bootstrap deadlock** — a fresh or foundation-light spec can never establish its `context`/`thesis`/`goal`/`constraint` frame, because the only tool that can write those nodes was gated on those nodes already existing (a developed but foundation-light spec such as `beta-commitments` was likewise unwritable). The `establishment_offer` is the correct *soft* mechanism; hard tool-withholding was over-anticipation (the same over-gating smell as a method withholding its own answer surface). Structural legality at the `CommandExecutor` (D64-L) is unchanged — illegal writes still fail loud; only the readiness-based *tool* withholding is removed. Implementation decouples the graph-write methods (`commit-graph`, `generate-proposal`) from the `propose-graph` / `project-graph` capability gate; current tool-legality and capability-readiness policy live in [`src/projections/README.md`](src/projections/README.md) (`session/capability-readiness`, `session/runtime-policy`) and [`src/agents/runtime/state.ts`](src/agents/runtime/state.ts) (`activeToolNamesForPosture`, `METHOD_CAPABILITY`, `METHOD_TOOL_NAMES`). Depends on: D30-L, D32-L, D74-L, D81-L, D85-L; I31-L. Refines: D74-L, D85-L. Supersedes: D85-L move 2's "the graph-write readiness gate lives on those method ids via capability-readiness"; the D74-L clause "readiness negotiation narrows … gated methods/tools" insofar as it withholds *graph-write* tools (AUTO axis-menu narrowing for `strategy`/`lens` is unaffected). +- **D86-L — Capability-readiness never withholds a graph-write tool; `negotiate` is advisory, not a tool gate.** Readiness modulates: it scales epistemic status (D30-L), narrows AUTO *axis* menus (`strategy`/`lens`), and surfaces the `establishment_offer` — but it must never remove a graph-write tool from the active set. `mutate_graph` (direct commit) and the review-set tools (`present_review_set` / `request_response`) are **floor** capabilities in `elicit` mode whenever gaps exist; the agent always retains the means to commit graph truth and may proceed at low epistemic status when grounding is thin. This re-asserts I31-L ("readiness never bars graph truth or work") against the contrary reading. Motivating reductio: gating `mutate_graph` behind `propose-graph` readiness created a **bootstrap deadlock** — a fresh or foundation-light spec can never establish its `context`/`thesis`/`goal`/`constraint` frame, because the only tool that can write those nodes was gated on those nodes already existing (a developed but foundation-light spec such as `beta-commitments` was likewise unwritable). The `establishment_offer` is the correct *soft* mechanism; hard tool-withholding was over-anticipation (the same over-gating smell as a method withholding its own answer surface). Structural legality at the `CommandExecutor` (D64-L) is unchanged — illegal writes still fail loud; only the readiness-based *tool* withholding is removed. Implementation decouples the graph-write methods (`commit-graph`, `generate-proposal`) from the `propose-graph` / `project-graph` capability gate; current tool-legality and capability-readiness policy live in [`src/projections/README.md`](src/projections/README.md) (`session/capability-readiness`) and [`src/agents/runtime/README.md`](src/agents/runtime/README.md) (`policy.ts`, `state.ts`, `activeToolNamesForPosture`, `METHOD_CAPABILITY`, `METHOD_TOOL_NAMES`). Depends on: D30-L, D32-L, D74-L, D81-L, D85-L; I31-L. Refines: D74-L, D85-L. Supersedes: D85-L move 2's "the graph-write readiness gate lives on those method ids via capability-readiness"; the D74-L clause "readiness negotiation narrows … gated methods/tools" insofar as it withholds *graph-write* tools (AUTO axis-menu narrowing for `strategy`/`lens` is unaffected). - **D87-L — Multi-method ontology revision: methods are validation lenses, not sources of kinds; the locked kind set reopens once for a small batch.** The ontology must host BDD, EDD, and formal-spec/verification flows on one model, cheapest to establish now before change costs rise. The governing result — validated against BDD/Gherkin and formal verification in [`docs/design/ONTOLOGY_REVIEW_PROTOCOL.md`](docs/design/ONTOLOGY_REVIEW_PROTOCOL.md) — is the **closure rule**: a method = `spec.kind` (D89-L) + `detail.form` (D88-L) + a renderer + a heuristic-set; no method earns its own node/edge kind, and a method term with no clean mapping is a *finding about our model*, not a licence to add a kind. This reopens the D54-L/D56-L node lock and the D51-L edge set once, deliberately, for one batch (implemented in the FE-1052 frontier; the schema enums changed during that build and `GRAPH_MODEL.md` was retired): - **Edges 8 → 9** (renames preserve behavior incl. stance): `proof → witness`, `support → rationale`, `boundary → exclusion`, `association → cross_reference`; **add `refinement`** (generality → specificity; present reader is formal refinement, abstract model ⊑ concrete implementation, distinct from `realization`). `stance ∈ for | against` stays valid only on the renamed `witness`/`rationale`; a counterexample is `witness:against`. The *edge* `proof` becomes `witness` while the *node* `evidence` is unchanged (renaming the edge to `evidence` would collide with the node; the relation reads as a verb). @@ -273,8 +273,8 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c This division mirrors the batch-proposal flow in D26-L. Worker-style write-capable subagents and nesting remain deferred beyond the initial `execute`/`orchestrator` standup. Cross-extension agent registration (Amos's `globalThis.__pi_subagents` bridge), raw `pi` subprocesses, and ambient `~/.pi` discovery are rejected for the POC because they conflict with profile sealing. Subagents remain an optional enhancement to candidate-proposal diversity and future delegated acquisition, not a load-bearing M0–M9 substrate. Depends on: D2-L, D26-L, D27-L, D30-L, D31-L, D39-L, D40-L, D41-L. Distinct from: D15-L Side task (non-blocking, status-via-custom-message), the deferred Side chat (user-invoked overlay; see Future Direction Register). Supersedes: subprocess/argv-shaped subagents and the `globalThis.__pi_subagents` bridge. Refined by: D90-L (shared foreground/background manifest + code-owned background discovery), D91-L (semi-permeable seal + assembled prompt + injected world), D92-L (sovereign tool grants + op_mode delegatable-set gate). - **D90-L — Foreground and background agents share one manifest model; background discovery is code-owned (frontmatter is authoring DX, not a second agent model).** Agent definitions project into one `AgentManifest` (`id`, `kind`, `description`, `model`, `thinking`, body at the canonical `src/agents/prompts//SYSTEM.md` convention, a skills grant, a tools grant, and a `canDelegate` set naming the background agents it may spawn — D92-L/D93-L) discriminated by `kind: "foreground" | "background"` — the execution **lifecycle/host**, not a noun: a foreground agent is a live op_mode-derived Pi session; a background agent is a spawned-to-completion sealed child. The kinds keep **distinct authority sources**: a foreground agent's identity is derived from `op_mode` (D40-L) and its tool/skill legality is dynamic (op_mode policy + live gaps); a background agent's identity is caller-chosen (`{agent, task}`) and its skills/tools come from its authored manifest. DX-vs-strictness is reconciled by keeping **frontmatter as the authoring surface** for background agents while making **discovery code-owned**: the `readdir` scan over `agents/*.md` is retired for an explicit registry id list (mirroring how `state.ts` loads foreground bodies/skills through `loadSkills({ skillPaths, includeDefaults: false })`), so D39-L "no filesystem discovery" holds and frontmatter authoring survives. "subagent" stays the tool/UX noun (the main-agent tool call), not the kind name. Depends on: D39-L, D40-L, D44-L, D58-L. Refines: D44-L (the parallel frontmatter-discovered format collapses into the shared manifest; background agent bodies migrated from `src/.pi/extensions/subagents/agents/*.md` onto the canonical `src/agents/prompts//SYSTEM.md` convention, so SPEC carries one agent-body layout — D44-L and the `src/.pi/extensions/subagents/README.md` topology notes reconcile to that path). Establishing frontier: `subagent-reconciliation`. Supersedes: `readdir` filesystem discovery of subagent definitions; the standalone subagent frontmatter format as a second, separate agent model. - **D91-L — Background subagents run a semi-permeable seal: explicitly-injected parent world handles plus an assembled (not verbatim) prompt; ambient leakage stays closed.** This deliberately reopens the D44-L/I29-L "no graph access, no Brunch RPC, no inherited context" clause. The seal stays closed against **ambient** leakage (in-memory auth/settings/session, no `~/.pi` discovery — D39-L intact) but opens to **explicitly injected** parent world handles the app root (`src/app/pi-subagents.ts`) supplies at spawn: the same `GraphReaders` the foreground uses scoped to the parent's `specId`, the spec/workspace context seed, and a bounded **session digest** (the parent branch flattened via `sessionManager.getBranch()`, the pattern in pi's `summarize.ts` example). The child's system prompt becomes **assembled, not verbatim**: body + a background control header (sealed child, delegated task, snapshot view) + world snapshot + a `` manifest built from the manifest's skills grant + router rules — reusing the foreground composer's extracted prompt-skill core (`renderBrunchSkills`, the skill-manifest loader) plus the selected workspace/spec seed renderer from `src/agents/contexts/seeds/turn-context.ts`, minus the foreground-only elicitation-recommendation block. World binding is **snapshot-at-spawn** (the child runs to completion against a fixed view) where the foreground is live-per-turn. Read access is asymmetric **by design**: the **session digest** is a snapshot block baked into the prompt (expensive, rarely re-pulled), while the **graph** is exposed as Brunch read tools (`read_graph` now; `read_session_context`, `read_elicitation_gaps`, … remain future grants) the child calls on demand (a recon agent iterates on graph). Return to the main agent is the ordinary tool-call result: findings re-enter main-agent context as the tool-result `content`; the structured `details` payload (`{ agent, status, text, … }`) is render-only via custom `renderCall`/`renderResult`, never model context. Write-capable children stay deferred (gated by D92-L); when they land, a `mutate_graph` against the parent's `specId` is a real side effect crossing back *outside* the tool result, and is named here so the write slice does not surprise. Depends on: D39-L, D43-L, D44-L, D58-L, D60-L, D82-L. Establishing frontier: `subagent-reconciliation`. Supersedes: the D44-L/I29-L clause that subagents have no graph access, no Brunch RPC/graph reads, no inherited world context, and a verbatim-body system prompt. -- **D92-L — Background tool grants are sovereign per-agent ceilings gated by a code-owned, op_mode-keyed delegatable-set allowlist — not parent-subset containment.** The earlier containment invariant (child tools ⊆ the parent's current legal set) is rejected: delegation is **capability-inverting on purpose** — a low-privilege orchestrator (the `execute`-mode foreground agent holding only data-layer tools) may spawn a narrow high-privilege child (e.g. a file-writing worker) so writes are quarantined in a child that does one job and exits. Each background agent's tool grant is therefore **sovereign** (authored in its manifest; may exceed the parent's). The surviving safety boundary is not a tool subset but **which background agents an op_mode may spawn**: a **code-owned, op_mode-keyed delegatable-set allowlist** living beside `AGENT_PROMPT_DEFINITIONS` / the op_mode policy, *not* authored in frontmatter (otherwise a manifest could self-advertise into a read-only mode). This lifts D40-L's registration ≠ advertisement from tools to agents: every background agent is registered; op_mode decides which are advertised as spawnable. A read-only `elicit` session is write-safe because elicit's delegatable set **excludes** write-capable agents, not because children are subset-bounded. Enabling write tools later = author the write-capable worker manifest + add it to the relevant op_mode's delegatable set (an advertisement change), not a re-derivation of parent authority. Depends on: D39-L, D40-L, D44-L. Establishing frontier: `subagent-reconciliation`. Refined by: D93-L (the delegatable-set allowlist becomes a per-agent `AgentManifest` `canDelegate` field; for a foreground mode it is that mode's code-owned delegatable set, and it generalizes to background→background nesting). Supersedes: the parent-subset tool-containment model for subagents; D44-L's "read-only/no-tool allowlist" as the only background tool posture; the framing that write-capable subagents wait on an execute mode raising both parent and child ceilings together. -- **D93-L — Operational mode and foreground agent collapse to one op-mode-keyed source of truth; the foreground roster is `elicit` / `execute` / `code`.** A foreground agent and its operational mode are 1:1 (D40-L: the foreground agent is derived from `op_mode`), so the prior **three-record fragmentation** — id enums in `src/session/schema/kinds.ts`, `OPERATIONAL_MODE_DEFINITIONS` + `AGENT_ROLE_DEFINITIONS` + `TOOL_POLICY_DEFINITIONS` in `src/projections/session/runtime-policy.ts`, and `AGENT_PROMPT_DEFINITIONS` in `src/agents/runtime/state.ts` (which duplicated `model`/`thinking`/`allowedStrategies`/`allowedLenses` across two of them) — collapses to a **single op-mode-keyed record**. An operational mode IS `{ foreground AgentManifest (D90-L), tool policy, canDelegate set }`; background agents live in a sibling `AgentManifest` registry, and the per-agent **`canDelegate`** field (D92-L generalized from op_mode-keyed to a manifest field) links a foreground mode to the background agents it may spawn — **code-owned for foreground modes** so the write-safety boundary (I49-L) holds; it also generalizes to background→background nesting. The foreground roster is three modes: **`elicit` → `elicitor`** (current: read-only + graph-writes-via-tools; delegates to read-only gatherers); **`execute` → `orchestrator`** (live initial standup: direct shell/file writes blocked, no delegated workers yet, and one code-owned `orchestrator_stub` grant proves the non-dev runnable path; future worker delegation quarantines writes in spawned high-privilege workers per D92-L); and **`code` → `pi-coder`** (planned direct-coding assistant with direct `read`/`edit`/`write`/`bash`, **augmented to be Brunch-tooling-aware** — it also sees the graph read/capture tools). `execute` and `code` contrast deliberately: the orchestrator does not write directly, while the pi-coder will write directly (and does not delegate by default). `pi-coder` **instantiates** the augment case D58-L anticipated — its `SYSTEM.md` augments Pi's base coding-agent prompt rather than replacing it; whether other foreground roles should instead suppress/replace the base stays open per D58-L. (`code` is provisional naming for the pi-coder mode; rename is a one-token change. `code` remains in `PLANNED_OPERATIONAL_MODE_IDS` until built; `execute` is live.) Depends on: D23-L, D40-L, D58-L, D90-L, D92-L; I49-L. Establishing frontier: `subagent-reconciliation` (model + collapse land here; `execute` stood up here with the stub grant, while `code` remains declarative build-out — see PLAN). Supersedes: the three-record foreground-agent fragmentation as separate sources of truth; `defaultRole`/`allowedRoles` as a flexible many-roles-per-mode model (it is 1:1); the `executor` name for the execute-mode agent (now `orchestrator`). +- **D92-L — Background tool grants are sovereign per-agent ceilings gated by a code-owned, op_mode-keyed delegatable-set allowlist — not parent-subset containment.** The earlier containment invariant (child tools ⊆ the parent's current legal set) is rejected: delegation is **capability-inverting on purpose** — a low-privilege orchestrator (the `execute`-mode foreground agent holding only data-layer tools) may spawn a narrow high-privilege child (e.g. a file-writing worker) so writes are quarantined in a child that does one job and exits. Each background agent's tool grant is therefore **sovereign** (authored in its manifest; may exceed the parent's). The surviving safety boundary is not a tool subset but **which background agents an op_mode may spawn**: a **code-owned, op_mode-keyed delegatable-set allowlist** living beside the op_mode policy, *not* authored in frontmatter (otherwise a manifest could self-advertise into a read-only mode). This lifts D40-L's registration ≠ advertisement from tools to agents: every background agent is registered; op_mode decides which are advertised as spawnable. A read-only `elicit` session is write-safe because elicit's delegatable set **excludes** write-capable agents, not because children are subset-bounded. Enabling write tools later = author the write-capable worker manifest + add it to the relevant op_mode's delegatable set (an advertisement change), not a re-derivation of parent authority. Depends on: D39-L, D40-L, D44-L. Establishing frontier: `subagent-reconciliation`. Refined by: D93-L (the delegatable-set allowlist becomes a per-agent `AgentManifest` `canDelegate` field; for a foreground mode it is that mode's code-owned delegatable set, and it generalizes to background→background nesting). Supersedes: the parent-subset tool-containment model for subagents; D44-L's "read-only/no-tool allowlist" as the only background tool posture; the framing that write-capable subagents wait on an execute mode raising both parent and child ceilings together. +- **D93-L — Operational mode and foreground agent collapse to one op-mode-keyed source of truth; the foreground roster is `elicit` / `execute` / `code`.** A foreground agent and its operational mode are 1:1 (D40-L: the foreground agent is derived from `op_mode`), so the prior **three-record fragmentation** — id enums in `src/session/schema/kinds.ts`, `OPERATIONAL_MODE_DEFINITIONS` + `AGENT_ROLE_DEFINITIONS` + `TOOL_POLICY_DEFINITIONS` in the former projections runtime-policy module, and `AGENT_PROMPT_DEFINITIONS` in `src/agents/runtime/state.ts` (which duplicated `model`/`thinking`/`allowedStrategies`/`allowedLenses` across two of them) — collapses to a **single op-mode-keyed record**. An operational mode IS `{ foreground AgentManifest (D90-L), tool policy, canDelegate set }`; background agents live in a sibling `AgentManifest` registry, and the per-agent **`canDelegate`** field (D92-L generalized from op_mode-keyed to a manifest field) links a foreground mode to the background agents it may spawn — **code-owned for foreground modes** so the write-safety boundary (I49-L) holds; it also generalizes to background→background nesting. The foreground roster is three modes: **`elicit` → `elicitor`** (current: read-only + graph-writes-via-tools; delegates to read-only gatherers); **`execute` → `orchestrator`** (live initial standup: direct shell/file writes blocked, no delegated workers yet, and one code-owned `orchestrator_stub` grant proves the non-dev runnable path; future worker delegation quarantines writes in spawned high-privilege workers per D92-L); and **`code` → `pi-coder`** (planned direct-coding assistant with direct `read`/`edit`/`write`/`bash`, **augmented to be Brunch-tooling-aware** — it also sees the graph read/capture tools). `execute` and `code` contrast deliberately: the orchestrator does not write directly, while the pi-coder will write directly (and does not delegate by default). `pi-coder` **instantiates** the augment case D58-L anticipated — its `SYSTEM.md` augments Pi's base coding-agent prompt rather than replacing it; whether other foreground roles should instead suppress/replace the base stays open per D58-L. (`code` is provisional naming for the pi-coder mode; rename is a one-token change. `code` remains in `PLANNED_OPERATIONAL_MODE_IDS` until built; `execute` is live.) Depends on: D23-L, D40-L, D58-L, D90-L, D92-L; I49-L. Establishing frontier: `subagent-reconciliation` (model + collapse land here; `execute` stood up here with the stub grant, while `code` remains declarative build-out — see PLAN). Supersedes: the three-record foreground-agent fragmentation as separate sources of truth; `defaultRole`/`allowedRoles` as a flexible many-roles-per-mode model (it is 1:1); the `executor` name for the execute-mode agent (now `orchestrator`). - **D36-L — Spec/session selection is a reusable hierarchical decision model with transport-specific presentations.** Brunch owns a pure spec/session selection model that renders cwd-scoped inventory under the discovered project name without calling the user-created object a “workspace”. In TUI mode, the model may present a fast “continue last session” affordance when `.brunch/workspace.json` points to a valid spec+session; otherwise, or after “other spec/session”, the durable tree is: `create new spec → provide spec name → session created automatically`; `resume existing spec → choose existing spec → create a new session OR resume existing session → choose existing session`. The UI should not list every spec as a top-level action label; “resume existing spec” is the top-level intent, and the spec list is the next screen/scrollable selector. The model returns a product decision (`new spec`, `new session for spec`, `open session`, `continue selected session`, `cancel/quit`) without opening Pi sessions or mutating `.brunch/workspace.json` itself. The `WorkspaceSessionCoordinator` activates that decision and owns all persistence/session-binding effects. TUI startup and in-session paths share branded `pi-tui` components and colocated logo assets under `src/.pi/components/workspace-dialog`; adapters differ only in terminal lifecycle and Pi session-replacement mechanics (`ProcessTerminal`/`TUI.showOverlay` before Pi starts, `ctx.ui.custom(..., { overlay: true })` inside Pi), not in product semantics. RPC/headless transports must not invoke the TUI picker; they expose the same initial-selection requirement and activation decisions as JSON-RPC/product results so CLI JSON-RPC clients can select or create spec/session correctly. Depends on: D11-L, D21-L, D24-L, D33-L. Supersedes: implicit resume of `.brunch/workspace.json` on TUI launch, Pi `/resume`/`/new` as Brunch's product session chooser, one-off startup-only picker implementations, a flat action list that says “workspace” for specs, top-level `resume spec X` labels, and a separate intermediate action chooser for switching. - **D42-L — Session naming is Pi `session_info` presentation metadata, not spec identity.** Brunch-created sessions should be named at creation with neutral workspace-global defaults (`Untitled Session 1`, `Untitled Session 2`, …) so pickers/chrome never show an unnamed Brunch session and unchanged defaults do not collide across specs in the same cwd. These defaults are immediate lifecycle metadata, not LLM-generated summaries and not derived from the selected spec title. Brunch may later use Pi session lifecycle hooks to opportunistically replace a default with a short human-readable name that characterizes what happened in the transcript. The preferred generation trigger is `session_shutdown` for `quit`, `new`, and `resume` replacements because it sees the just-finished transcript and can name it before later picker lists need to distinguish sessions; `session_before_compact` or post-compaction (`session_compact`) may be used to refresh names after major summarization, and a manual/user rename command can force or override naming. The generation call should mirror the model-selection pattern in the local `summarize.ts` extension example: choose a cheap/fast authorized model, extract user/assistant text plus salient tool calls from the current branch, ask for a concise title, and append a Pi `session_info` entry through `SessionManager.appendSessionInfo`. Naming must be best-effort and non-blocking with a tight budget: failures, missing auth, empty transcripts, or shutdown aborts preserve the existing default/user label rather than blocking session replacement or exit. Session display names label sessions in pickers and chrome, but do not affect spec ids, session bindings, graph truth, or replay semantics. Depends on: D6-L, D17-L, D21-L, D35-L. Supersedes: using spec title or session UUID alone as the only durable display label once transcripts have meaningful content, leaving Brunch-created sessions unnamed, spec-local default numbering, or treating generated session names as canonical spec identity. - **D58-L — Brunch prompt composition is a thin runtime header plus a gated prompt-resource manifest, not eager selection of every objective pack.** The architectural commitment is: composition stays a projection layer, not a behavioral state machine; detailed strategy/lens/method guidance lives in read-on-demand prompt resources rather than eager prompt-pack concatenation; runtime-state-gated availability is Brunch's sealed manifest, not ambient Pi discovery; pinned axes remain visible when role/mode-legal while readiness constrains AUTO menus and gated methods/tools; and the pushed-context slice stays compact, with deeper access governed by D60-L. Current prompt-resource topology, manifest emission, file-owned skill metadata, seed context composition, and ownership split across `agents/prompts/`, `agents/skills/`, `agents/runtime/`, `agents/contexts/`, and `.pi/extensions/agent-runtime/` live in [`src/agents/README.md`](src/agents/README.md), [`src/agents/prompts/README.md`](src/agents/prompts/README.md), [`src/agents/skills/README.md`](src/agents/skills/README.md), [`src/agents/runtime/README.md`](src/agents/runtime/README.md), [`src/agents/contexts/README.md`](src/agents/contexts/README.md), [`src/.pi/README.md`](src/.pi/README.md), [`src/.pi/extensions/README.md`](src/.pi/extensions/README.md), [`src/agents/runtime/compose.ts`](src/agents/runtime/compose.ts), [`src/agents/runtime/state.ts`](src/agents/runtime/state.ts), and [`src/agents/contexts/seeds/turn-context.ts`](src/agents/contexts/seeds/turn-context.ts). **Base-prompt relationship (validated 2026-06-18, slice 1):** the `before_agent_start` handler **appends** Brunch's composed block (now led by the agent `SYSTEM.md` body, then runtime header + manifests) to Pi's base system prompt (`${basePrompt}\n\n${composed}`), so a foreground agent currently *augments* Pi's base coding-agent prompt rather than replacing it. Whether a foreground role's `SYSTEM.md` body should suppress or replace that base is **open** and tied to the future `pi-coder` op-mode (which deliberately augments Pi's coding agent); the `elicitor` augmenting a coding base is a known follow-on question, not a settled choice. Refined by: D93-L (the `code`→`pi-coder` foreground mode instantiates the augment case; the replace option for other roles stays open). Composition is projection, not a behavioral state machine. Depends on: D23-L, D25-L, D39-L, D40-L, D52-L, D59-L, D60-L. Refined by: D85-L (implemented 2026-06-18/19: the manifest drops `` — two axes `strategy` + `lens` — and the `goal` body inlines into the `elicitor` role prompt) and by the 2026-06-22 prompt-skill-topology slice (all prompt resources adopt Agent Skills `SKILL.md` topology; `description` becomes file-owned frontmatter; the emitted wrapper becomes `` with per-skill ``). Supersedes: the flat "base + mode + role + strategy + lens + grade + …" layering; the fixed all-packs concatenation in `compose-brunch-prompt.ts`; "role preset / runtime bundle" as the composition unit; direct Layer-2 eager prompt-pack injection as the default mechanism; treating top-level `src/agents/` as Pi-only rather than Brunch LLM-context ingress; and `capability` as a parallel name for `method`. @@ -294,7 +294,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c - **D71-L — One `BRUNCH_DEV` switch gates all dev affordances; the main CLI accepts `--cwd`; introspection is present-but-dead in prod.** The over-specific `BRUNCH_DEV_RPC` env var is generalized to a single `BRUNCH_DEV` switch that, when set, enables dev affordances together: dev RPC methods (`dev.*`), registration of the read-only introspection extension (D69-L), and routing of dev-loop artifacts to `.fixtures/scratch/` (D70-L). `runBrunchCli` parses a `--cwd ` flag (defaulting to `process.cwd()`) so a dev session can target a `.fixtures/workbenches/` workspace without `cd`. Two independent prod-safety gates hold: (1) `src/dev/**` is build-excluded by `tsconfig.build.json`, so launchers/harness/alias never ship; (2) the introspection extension, though compiled into `dist` under `src/.pi/`, only *registers* when `createBrunchPiExtensions(..., { introspection: { enabled } })` opts in — and the TUI call site sets `enabled` from `BRUNCH_DEV` only, so absent the switch it is present-but-dead, never wired, honoring D39-L explicit-opt-in sealing (no ambient discovery). Brunch-launched TUI sessions keep Pi startup update suppression on in both product and `BRUNCH_DEV` runs by scoping `PI_OFFLINE=1` through `InteractiveMode.run()` unless the user already set a value; prior `PI_OFFLINE` / `PI_SKIP_VERSION_CHECK` state is restored in `finally`, never as a leaked global `process.env` mutation. Depends on: D39-L, D67-L, D68-L, D69-L, D70-L. Supersedes: the `BRUNCH_DEV_RPC`-only dev gate; relying on the operating cwd to choose the dev workspace; the assumption that the introspection extension needs build-exclusion (runtime opt-in suffices); lifting Pi offline mode in `BRUNCH_DEV` TUI sessions merely to enable live-provider behavior. - **D79-L — Dev DB seeding is explicit, selected, and target-workspace-scoped; `npm run dev` never implies a seed.** A Brunch workspace DB is local runtime state under that launch cwd's `.brunch/`; running `npm run dev` against the repo root or a workbench may create/open that workspace, but it must not silently load reusable seed fixtures. Reusable graph seeds under `.fixtures/seeds//.json` are loaded only by an explicit seed command that names the target workspace and the seed set/slug (or an explicitly requested all-seeds batch); the loader remains a graph-domain utility over `seedFixture`/`CommandExecutor`, so seeded specs get normal `create_spec`/`mutate_graph` change-log entries, spec-local LSNs, elicitation-gap seeding, and structural validation. Workbenches under `.fixtures/workbenches//` are launchable cwd containers, not seed truth: their `.brunch/` may be reset or re-seeded locally, but tracked files must document which seed(s) a human or script should apply. Captured or newly-authored seed JSON is parked until it has at least one named consumer disposition (`test`, `preview`, `manual workbench`, `probe input`, or `parked`); existence under `seeds/` alone does not make it part of the default dev database. Depends on: D16-L, D20-L, D52-L, D70-L, D71-L. Supersedes: the catch-all `npm run seed` mental model that loads every seed into the current shell cwd; treating the repo-root `.brunch/` as canonical dev fixture state; auto-seeding because a dev host starts. - **D59-L — `goal` is a readiness-derived, AUTO-able objective axis, distinct from strategy.** A *goal* is what the session agent currently pursues; a *strategy* is the reusable interaction shape used to pursue it — a goal is pursued *via* a strategy *through* a lens (three orthogonal axes). The goal set is derived from readiness-band coverage (D64-L) rather than a stored grade: `grounding-advance` (fill grounding gaps and raise grounding coverage), `elicit-expand` (expand the elicited specification graph while ambiguity remains productive), `commit-converge` (reduce / lock down reviewable commitments), plus an always-on `capture-posture` (capture or confirm dev `posture`, D45-L). `goal` defaults to the readiness-derived objective (e.g. while grounding coverage is thin, `grounding-advance`), may be pinned, or left `AUTO`; in either case D58-L manifests advertise the legal resource(s) rather than injecting the whole goal body. For now `goal` is **internal/readiness-derived and not part of the user posture-change surface** (it is too contingent to expose as a user-mutable axis); the pin affordance is reserved for system/internal logic, and unlike `strategy`/`lens` the user does not switch it (D40-L, Q4). `elicit-expand` and `commit-converge` intentionally form the diverge/converge pair for the elicitation diamond; `elicit-I` / `elicit-II` are retired because they were phase-like labels, not objectives. "Advance grounding" is a goal, not a strategy — though the `grounding-advance` goal may carry a dedicated default interaction pattern. Depends on: D45-L, D57-L, D58-L, D64-L. Refined by: D85-L (`goal` is dropped as a manifest/runtime axis; the four postures inline into the `elicitor` role prompt, agent-selected by band — goal was internal/readiness-derived anyway). Supersedes: conflating the elicit-lifecycle objective with strategy selection, and deriving the goal set from a stored readiness grade. -- **D66-L — `freestyle` is a structure-optional elicitation strategy; it and generalized free-text capture are one slice.** The architectural commitment is that `freestyle` is an interaction-style strategy, not a new `op_mode` or authority posture: ordinary user-driven turns become allowed without banning structured exchanges, and AUTO must not select `freestyle` — it remains an explicit user/system pin so offer-first does not disappear silently. Current materialized state lives in [`src/agents/skills/strategies/README.md`](src/agents/skills/strategies/README.md), [`src/agents/skills/strategies/freestyle/SKILL.md`](src/agents/skills/strategies/freestyle/SKILL.md), [`src/agents/skills/methods/capture/SKILL.md`](src/agents/skills/methods/capture/SKILL.md), [`src/projections/session/runtime-policy.ts`](src/projections/session/runtime-policy.ts), [`src/.pi/extensions/commands/index.ts`](src/.pi/extensions/commands/index.ts), and [`src/session/README.md`](src/session/README.md). Depends on: D18-L, D25-L, D26-L, D40-L, D45-L, D49-L, D50-L, D59-L, D63-L, D65-L. Refines: R16. Refined by: D80-L, D81-L (2026-06-12 FE-861 grill: the capture half — submit-time capture wiring and the "directly-stated only" commitment line — is superseded by the banded capture sweep and the commitment gradient; capture runs on every elicitor turn over the un-swept tail, resolving the every-turn-vs-on-demand open question). Supersedes: treating offer-first (R16) as a universal per-turn session invariant; treating freestyle as a new operational mode or authority posture. +- **D66-L — `freestyle` is a structure-optional elicitation strategy; it and generalized free-text capture are one slice.** The architectural commitment is that `freestyle` is an interaction-style strategy, not a new `op_mode` or authority posture: ordinary user-driven turns become allowed without banning structured exchanges, and AUTO must not select `freestyle` — it remains an explicit user/system pin so offer-first does not disappear silently. Current materialized state lives in [`src/agents/skills/strategies/README.md`](src/agents/skills/strategies/README.md), [`src/agents/skills/strategies/freestyle/SKILL.md`](src/agents/skills/strategies/freestyle/SKILL.md), [`src/agents/skills/methods/capture/SKILL.md`](src/agents/skills/methods/capture/SKILL.md), [`src/agents/runtime/policy.ts`](src/agents/runtime/policy.ts), [`src/.pi/extensions/commands/index.ts`](src/.pi/extensions/commands/index.ts), and [`src/session/README.md`](src/session/README.md). Depends on: D18-L, D25-L, D26-L, D40-L, D45-L, D49-L, D50-L, D59-L, D63-L, D65-L. Refines: R16. Refined by: D80-L, D81-L (2026-06-12 FE-861 grill: the capture half — submit-time capture wiring and the "directly-stated only" commitment line — is superseded by the banded capture sweep and the commitment gradient; capture runs on every elicitor turn over the un-swept tail, resolving the every-turn-vs-on-demand open question). Supersedes: treating offer-first (R16) as a universal per-turn session invariant; treating freestyle as a new operational mode or authority posture. - **D80-L — Generalized capture is the elicitor's banded capture sweep: in-turn, synchronous, over the un-swept transcript tail.** Capture is conduct of the foreground elicitor, not product wiring: there is no observer/auditor queue on the primary path (D18-L, reaffirmed — the v1 observer failed on structure-dependence and context starvation), no product-side LLM extraction pass on the submit paths, no gateway/translation/judgment layer between the agent and graph truth, and no capture subagent in the current block. The **banded capture sweep** is one band-ordered pass that walks intent-kind groups (the same typology the `elicitation_gaps` register references via `refersTo: NodeKind`, D65-L/D75-L), committing through the real role-named `mutateGraph` grammar (D53-L/A14-L) and moving gap dispositions through `update_elicitation_gaps`. Its input window is the **un-swept transcript tail** — all conversational and digest content since the last sweep, tracked by a **sweep watermark** (prior art: the I45-L assistant-visible watermark and the own-mutation stamp) — so capture is robust to RPC-submitted messages, interruptions, and multi-message bursts, and probes get a crisp invariant: after any elicitor turn, nothing conversational remains behind the watermark. Default is a single pass; bulk material (pastes, document reads, exploration digests) may engage an **escalation valve** — chunked/iterated sweeping within the same turn — without changing window or watermark semantics. Choreography is **capture-then-ask**: the sweep commits facts and moves gaps *before* the elicitor composes its next question, so the question provably benefits from what was just captured. Consequence: the deterministic labeled-prefix capture core (`graph/capture/structured-response.ts`), its `session.submitMessage`/`session.submitExchangeResponse` wiring, and the `capture-response-to-graph` proof are retired fossils — capture becomes turn-coupled (same agent for RPC transport clients, different moment; no coverage loss). Depends on: A14-L, A22-L, D18-L, D49-L, D53-L, D63-L, D65-L, D66-L; I45-L. Supersedes: submit-time product-side capture (the D66-L "exactly as the structured-response capture tracer does" wiring), the labeled-prefix extraction core, and the capture-quality spike's product-side extraction-pass shape. - **D81-L — Capture commitment gradient: confidence, not directness; low-confidence noticings spawn elicitation gaps.** What the sweep commits is governed by confidence in grounding, not by whether the user uttered the exact words: directly-stated facts commit with `basis: explicit`; confidently-materialized items — including implied edges and structure soundly inferred from stated content — commit with `basis: implicit`, which D63-L already licenses (agent-materialized-from-user-input); low-confidence **noticings** are never committed — the sweep's prompt directs the elicitor to spawn an `elicitation_gap` instead (`basis: implicit`, rationale citing the noticing), so the false-commit guard's positive output *is* the capture-reflection behavior: one prompted discipline discharges both the guard and the gap-writeback obligation, the agenda durably carries what was noticed, and the anti-shadowing line (D65-L) holds because the gap carries question/rationale, never domain content as truth. There is **no structural gate**: the guard is commitment rules in the sweep prompting plus the false-commit scenario matrix re-aimed at the low-confidence line and run at probe tier (some spike implication rows become legitimate implicit commits under the gradient; expected gap-spawns become assertable probe outcomes); CI guards structural legality only at the `CommandExecutor` boundary. Refines: D18-L (low-confidence material now spawns gaps rather than only "folding into later questions"; preface, D47-L, remains the orientation carrier). Depends on: A22-L, D18-L, D47-L, D63-L, D65-L. Supersedes: "implications never become graph truth" as a *directness* rule, and the spike matrix's `shouldCommit` expectations as written. - **D82-L — Ground-material acquisition is a skill-structured layer in front of the sweep; bulk modes interpose a digest; a seeded situating gap routes modes.** Questions and answers are not the only way the graph gains ground material: the elicitor must also accept arbitrary pasted content, read user-referenced workspace documents, and explore-and-characterize a brownfield codebase. These are **acquisition modes** — elicit-by-question, ingest-paste, read-referenced-documents, explore-and-characterize — structured as Brunch prompt-resource skills (D58-L manifest world), each a distinct competence the elicitor reaches for; `read-referenced-documents` and `explore-and-characterize` may use Brunch-owned static `web_fetch`/`web_search` tools registered in the sealed Pi profile (D39-L/D40-L). Acquisition varies, capture stays uniform (`acquire → digest → sweep`): everything acquired lands in the transcript behind the sweep watermark. Bulk modes (exploration, research, large document reads) interpose a **digest** — an assistant-authored characterization of what was read/found (prior art: the v1 preface-of-exchange-tuple, which proved capture should run over the summary, not the raw bulk; D47-L) — and the sweep captures from digests plus conversational content while raw tool results pass behind the watermark as background. A **situating gap** is seeded at spec creation (orientation anchors: new-from-scratch / brownfield codebase / continuation of a prior thread — the grounding-advance anchors promoted from skill prose to agenda), so the opening elicitation itself routes the session into the right acquisition mode; the gap's discharge is what licenses, e.g., explore-and-characterize. Near-future direction (not current block): exploration/research acquisition delegated to **subagents** with the digest as the handback artifact — clean main-elicitor context without observer starvation, because the subagent owns its exploration context and returns only the digest. Depends on: D47-L, D57-L, D58-L, D65-L, D80-L. Supersedes: treating conversational answers as the only capture source. @@ -312,7 +312,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c - **Rollout** — incremental: ``, ``, graph, session runtime-frame, and structured-exchange result renders now live under `src/agents/contexts/`; transcript debug/report rendering remains in `src/renderers/session/transcript.ts` until it becomes deliberate agent context. - **Closed audit** — per-session `turnCount` is derived once while inspecting canonical session files and counts only current Pi v3 JSONL message entries (`type: "message"` with `message.role: "user" | "assistant"`); tool/custom entries are excluded, and downstream workspace/specification overview renders reuse that inspected count rather than reparsing the file. - **D85-L — Brunch prompt-resource axis model: two AUTO axes (`strategy`, `lens`); `goal` inlined into the agent role; graph-write mechanism is method-routed, not a strategy.** A 2026-06-18 grill consolidation of the `agents/skills/` topology and the D58-L manifest axes, implemented across FE-893, FE-861, and FE-898. Four locked moves: - 1. **Two AUTO objective axes, not three.** The runtime manifest advertises only `strategy` and `lens`; **`goal` is dropped as a manifest/runtime axis**. The four goal postures (`grounding-advance`, `elicit-expand`, `commit-converge`, always-on `capture-posture`) **inline into the `elicitor` agent role prompt** (`src/agents/prompts/elicitor/SYSTEM.md`), selected inline by the agent from the pushed readiness-band/posture context. Rationale: `goal` was already internal/readiness-derived and not user-mutable (D59-L), so advertising it as an AUTO-selectable axis was indirection over what is agent-directed-by-band anyway. Consequences for the build: `compose.ts` drops the `` family, `manifestsForState` drops `goals`, `runtime-state.ts` / `runtime-policy.ts` / `affordances.ts` drop the `goal` axis slot, and the runtime header drops the goal line. Capability-readiness (D74-L) is unaffected — it keys on gaps, not goal. + 1. **Two AUTO objective axes, not three.** The runtime manifest advertises only `strategy` and `lens`; **`goal` is dropped as a manifest/runtime axis**. The four goal postures (`grounding-advance`, `elicit-expand`, `commit-converge`, always-on `capture-posture`) **inline into the `elicitor` agent role prompt** (`src/agents/prompts/elicitor/SYSTEM.md`), selected inline by the agent from the pushed readiness-band/posture context. Rationale: `goal` was already internal/readiness-derived and not user-mutable (D59-L), so advertising it as an AUTO-selectable axis was indirection over what is agent-directed-by-band anyway. Consequences for the build: `compose.ts` drops the `` family, `manifestsForState` drops `goals`, `runtime-state.ts` / `agents/runtime/policy.ts` / `affordances.ts` drop the `goal` axis slot, and the runtime header drops the goal line. Capability-readiness (D74-L) is unaffected — it keys on gaps, not goal. 2. **Graph-write mechanism is method-routed, not a strategy-axis member.** `propose-graph` (direct-commit) and `project-graph` (review-set) describe the **graph-write capability ids** (the D26-L commitment mechanisms), not interaction shape; their strategy names are retired rather than rehomed. The existing methods absorb the mechanics: `commit-graph` carries direct-commit mechanics, and `generate-proposal` carries review-set mechanics. The offer→accept / derive→review choreography lives in the inlined `commit-converge` posture, not in method bodies. The graph-write readiness gate was originally placed on those method ids via capability-readiness (**removed by D86-L**: the graph-write methods are floor — readiness is advisory for them, never a tool gate), while the `strategy` axis keeps only genuine interaction shapes: `step-wise-decision-tree`, `step-wise-disambiguate`, and `freestyle` (AUTO-excluded, D66-L). 3. **Gap-reflection conduct belongs to the capture skill, not `review-for-gaps`.** D81-L spawn-on-noticing + close-on-answered is **always-on capture-sweep conduct** (every elicitor turn), so it lives with the D80-L capture skill, not an optionally-selected method. `review-for-gaps` is demoted to the **deliberate-audit sense only** (missing support, contradictions, verification debt). Read/interpret-gap semantics stay on the `read_elicitation_gaps` tool description (tool-local), not duplicated into a skill; the D81-L commitment gradient lives once, in the capture skill, with gap-spawn as its third outlet. 4. **The prompt-content rewrite is design work entangled with live/stubbed seams — not a keyword fossil sweep.** The strategy/method bodies drift and overlap, but audit (2026-06-18) found their suspect tokens are mostly *not* dead history: `tool_meta` is live across every exchange projection; `capture_*` is a live `tool_meta.next` sequencing marker (`present_* → request_* → capture_*`), distinct from the D80-L-retired labeled-prefix capture core; and `present_candidates` + `user_rubric` / `meta_rubric` / `graph_refs` are the **anticipated payload of the live candidate topology stub** (`projections/exchanges/present-candidates.ts`, PLAN-confirmed stubbed), not removable fossils. Only `renderCall` is genuinely unreferenced (confirm against the Pi display API before removal). Rewriting prompt content must reconcile against the candidate stub, the exchange `tool_meta` model, and the D80-L sweep model rather than strip by keyword. Lexicon sweep in the same pass: `elicitation backlog` → `elicitation gaps` / `coverage obligation` (the D65-L rename; the inlined `elicit-expand` posture in `elicitor/SYSTEM.md` still carries the old term after the goal-axis drop relocated it). @@ -358,7 +358,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c | I28-L | Auto-compaction output preserves the configured anchor set byte-stable: every entry kind listed in [src/.pi/extensions/compaction/index.ts](src/.pi/extensions/compaction/index.ts) is reconstructable post-compaction according to its `select` rule (`first | latest | active-leaves | all-unresolved`); LLM-generated narrative summary never replaces or rephrases preserved-anchor content; extension failure falls through to Pi default compaction rather than dropping anchors silently. | planned (compaction round-trip property tests at M9 plus inner-loop anchor-rendering unit tests and TypeBox schema validation of the anchor contract) | D43-L; R15, R13; I3-L, I4-L, I8-L, I12-L | | I29-L | Subagent SDK child sessions inherit Brunch Pi Profile sealing while allowing explicitly injected parent-world reads: every `subagent` tool invocation builds an in-process `AgentSession` from explicit sealed services (in-memory auth/settings/session managers, no ambient resources, assembled background system prompt, parent model registry, explicit tool allowlist); subagents never load ambient user/project `.pi/` skills, prompts, themes, extensions, context files, or behavior-shaping settings; subagents never gain direct access to the parent's `CommandExecutor`, Brunch RPC handlers, or graph persistence; parent world access is injected by the app root as a snapshot prompt block plus selected-spec read tools such as `read_graph`; parent aborts prevent prompt execution before/during setup and abort live child sessions; subagent results return to the main agent only as tool result content (no side-effect transcript writes). | covered for the implemented SDK seam by `src/.pi/extensions/subagents/subagents.test.ts`: frontmatter/config validation (including duplicate keys), explicit registry loading from `src/.pi/agents//SYSTEM.md` while ignoring unlisted planted bodies, tool allowlist conformance for `explorer`/`projector`/`researcher`, sealed faux-provider child sessions with no inherited base prompt or conversation, assembled prompt snapshot coverage (selected spec/workspace/session digest, no foreground elicitation recommendation), unknown-tool failure, `read_graph` availability only with injected parent graph readers, parent-spec-only graph read content with sibling-spec negative assertion, bounded concurrency including waiter/new-arrival race, invalid invocation shape rejection before runner call, and parent-abort setup/live-session behavior. Startup advertisement remains dev-gated by whether a launch path supplies subagent deps to `createBrunchPiExtensions(...)`. | D2-L, D39-L, D40-L, D44-L, D91-L; I1-L, I2-L, I11-L, I24-L | | I30-L | Elicitor capture commits only high-confidence graph truth; under the D81-L gradient, directly-stated facts commit `explicit`, confidently-materialized facts/edges commit `implicit`, low-confidence noticings never become graph truth — they map to existing-or-new `elicitation_gaps` as agenda — and contradictions with existing graph truth route to `reconciliation_need` rather than gap or overwrite. | covered for deterministic routing (`src/graph/__tests__/capture-commitment-gradient-gate.test.ts` proves the FE-861 routing gate through the real `mutate_graph`, `update_elicitation_gaps`, and `update_reconciliation_needs` adapters: explicit→commit, implicit→commit, low→one gap, contradiction→one semantic-conflict recon need, structural answered derivation, manual gap close on the graph clock, illegal capture batches failing loud, and the closed capture-quality-spike scenario family re-aimed from binary `shouldCommit` to gradient `expectedOutcome` rows across free prose, file refs, implication-heavy, and contradiction classes. `src/probes/capture-quality-loop.ts` keeps the LLM-in-loop probe as fitness by scoring gradient-routing accuracy, not gating classification quality. `src/.pi/extensions/brunch-data/reconciliation/index.test.ts` proves the recon-need tool pair over `CommandExecutor`/`getOpenReconciliationNeeds` plus elicit-posture legality. `src/projections/session/sweep-watermark.test.ts` plus `src/.pi/__tests__/extension-registry.test.ts` prove the D80-L transcript-position sweep watermark: conversational/digest tail classification, raw background exclusion, idempotent marker advance, graph-LSN watermark separation, and live `before_agent_start` wiring. The submit-time labeled-prefix capture module, its `session.*` wiring, and the `capture-response-to-graph` / `submit-message-capture` proofs were deleted 2026-06-19 (D80-L fossil retirement); `session.submitMessage` / `session.submitExchangeResponse` results no longer carry a `capture` field. Confidence/dedup quality remains fitness.) | D8-L, D18-L, D47-L, D65-L, D80-L, D81-L; A22-L | -| I31-L | Readiness never bars graph truth or work; it is just-in-time capability-readiness over relevant gaps, not a stored grade or kind whitelist. There is no `readiness_grade` scalar; capability availability is judged on request against the relevant `elicitation_gaps` (D74-L) and may proceed, proceed at low epistemic status, or negotiate — it never refuses outright. The `CommandExecutor` must not reject a graph node solely because its kind belongs to a later readiness band (D64-L). The soft `readiness estimate` (D45-L) is UI-only and gates nothing. Capability-readiness never *withholds a graph-write tool*: `mutate_graph` and the review-set tools stay in the active tool set regardless of readiness; `negotiate` is advisory (establishment offer + epistemic scaling), never a tool gate (D86-L). | partially covered (`src/projections/session/capability-readiness.test.ts` covers the D74-L tracer gate, including proceed / proceed_low_epistemic / negotiate, no-refusal, no grade-symbol import, and a live `presence` coverage flip; `src/projections/session/affordances.test.ts` covers the first consumer rewire: menu legality omits gated options while relevant gaps negotiate and includes them when coverage rises, with no grade symbols in `runtime-policy.ts` / `affordances.ts`, and a required `NodeKind` absent from the gap register fails loud (config bug ≠ uncovered — readiness omission never masks a seeding error); `src/projections/session/readiness-estimate.test.ts` covers the soft D45-L estimate shape, empty-band zero, importance-weighted per-band coverage, honest regression, and no legality-path imports; `src/.pi/extensions/agent-runtime/runtime/state.test.ts`, `src/agents/runtime/__tests__/compose.test.ts`, `src/agents/contexts/seeds/__tests__/turn-context.test.ts`, and `src/.pi/__tests__/prompting.test.ts` cover the prompt consumer path: selected-spec gaps render as the soft per-band estimate, readiness-thin pinned axes remain visible, gated methods stay withheld, `readiness_grade=` is absent from prompt display, and the turn boundary threads the same gaps into cwd context without prompt-assembly failure; `src/session/workspace-session-coordinator.test.ts`, `src/renderers/workspace/__tests__/workspace-state.test.ts`, `src/session/workspace-overview-context.test.ts`, `src/.pi/__tests__/context-tools.test.ts`, `src/rpc/handlers.test.ts`, and `src/web/app.test.tsx` cover the workspace/chrome display retirement: `chrome.phase` / `chrome.chatMode` no longer project through coordinator/RPC/web/chrome fixtures, and workspace overview session inventory no longer carries or renders `readinessGrade`; `createSpec` / `getSpec` persistence, seed/export fixture contracts, probes, and selected-spec prompt carriers no longer persist or transport a readiness grade; the D86-L graph-write-tool-floor sub-claim is covered — `state.test.ts` proves `mutate_graph` + review-set tools stay floor while `propose-graph`/`project-graph` readiness `negotiate`s and only the non-graph-write `review-for-gaps` is withheld, and `dev/__tests__/tier-2-harness.test.ts` proves the same through a real `runBrunchTui` boot at thin vs covered grounding) | D20-L, D45-L, D64-L, D74-L, D86-L | +| I31-L | Readiness never bars graph truth or work; it is just-in-time capability-readiness over relevant gaps, not a stored grade or kind whitelist. There is no `readiness_grade` scalar; capability availability is judged on request against the relevant `elicitation_gaps` (D74-L) and may proceed, proceed at low epistemic status, or negotiate — it never refuses outright. The `CommandExecutor` must not reject a graph node solely because its kind belongs to a later readiness band (D64-L). The soft `readiness estimate` (D45-L) is UI-only and gates nothing. Capability-readiness never *withholds a graph-write tool*: `mutate_graph` and the review-set tools stay in the active tool set regardless of readiness; `negotiate` is advisory (establishment offer + epistemic scaling), never a tool gate (D86-L). | partially covered (`src/projections/session/capability-readiness.test.ts` covers the D74-L tracer gate, including proceed / proceed_low_epistemic / negotiate, no-refusal, no grade-symbol import, and a live `presence` coverage flip; `src/projections/session/affordances.test.ts` covers the first consumer rewire: menu legality omits gated options while relevant gaps negotiate and includes them when coverage rises, with no grade symbols in `agents/runtime/policy.ts` / `affordances.ts`, and a required `NodeKind` absent from the gap register fails loud (config bug ≠ uncovered — readiness omission never masks a seeding error); `src/projections/session/readiness-estimate.test.ts` covers the soft D45-L estimate shape, empty-band zero, importance-weighted per-band coverage, honest regression, and no legality-path imports; `src/.pi/extensions/agent-runtime/runtime/state.test.ts`, `src/agents/runtime/__tests__/compose.test.ts`, `src/agents/contexts/seeds/__tests__/turn-context.test.ts`, and `src/.pi/__tests__/prompting.test.ts` cover the prompt consumer path: selected-spec gaps render as the soft per-band estimate, readiness-thin pinned axes remain visible, gated methods stay withheld, `readiness_grade=` is absent from prompt display, and the turn boundary threads the same gaps into cwd context without prompt-assembly failure; `src/session/workspace-session-coordinator.test.ts`, `src/renderers/workspace/__tests__/workspace-state.test.ts`, `src/session/workspace-overview-context.test.ts`, `src/.pi/__tests__/context-tools.test.ts`, `src/rpc/handlers.test.ts`, and `src/web/app.test.tsx` cover the workspace/chrome display retirement: `chrome.phase` / `chrome.chatMode` no longer project through coordinator/RPC/web/chrome fixtures, and workspace overview session inventory no longer carries or renders `readinessGrade`; `createSpec` / `getSpec` persistence, seed/export fixture contracts, probes, and selected-spec prompt carriers no longer persist or transport a readiness grade; the D86-L graph-write-tool-floor sub-claim is covered — `state.test.ts` proves `mutate_graph` + review-set tools stay floor while `propose-graph`/`project-graph` readiness `negotiate`s and only the non-graph-write `review-for-gaps` is withheld, and `dev/__tests__/tier-2-harness.test.ts` proves the same through a real `runBrunchTui` boot at thin vs covered grounding) | D20-L, D45-L, D64-L, D74-L, D86-L | | I32-L | Public RPC structured-exchange driving never requires a client to speak raw Pi RPC: after Brunch method discovery and workspace/spec/session activation, each pending assistant-originated exchange is answered exactly once through `session.submitExchangeResponse`, and the deterministic permutation run produces linear Pi JSONL whose structured exchange projection preserves the same prompt/answer/status/comment artifacts as the equivalent TUI structured-exchange path. | covered for deterministic FE-744 parity under canonical session method names (`session.triggerExchange`, `session.pendingExchange`, `session.submitExchangeResponse`, `session.exchanges`): `rpc.discover` contract tests, pending/respond lifecycle tests, current public-RPC structured-exchange permutations, terminal non-answered status handling, option content/rationale parity, no repeated deterministic prompts, and transcript/exchange parity assertions. | D5-L, D48-L, D49-L; I10-L, I13-L, I21-L, I23-L | | I33-L | `capture_*` analysis entries are transcript evidence only: they persist as Brunch structured-exchange `toolResult` rows, are included by Brunch-semantic transcript renderers, are hidden or collapsed in TUI display, and never mutate graph truth or bypass `CommandExecutor`. | partially covered (minimum capture details schemas parse/export and reject graph payload fields; future runtime capture-analysis schema/rendering tests plus transcript renderer fixtures still need to prove persisted result rendering and TUI hide/collapse behavior; later graph-capture fixtures compare analysis candidates against committed graph mutations) | D17-L, D18-L, D37-L, D47-L, D50-L; I2-L, I11-L, I23-L, I30-L | | I34-L | `mutateGraph` batch validation is all-or-nothing: if any node or edge in the batch is structurally illegal, the entire batch is rejected and no partial state is persisted; the agent receives diagnostics sufficient for bounded self-correction retry. | covered (`command-executor/commit-graph-batch.test.ts` and graph-tool adapter tests cover dry-run/commit diagnostic parity for invalid basis, missing refs/codes, invalid category/stance, self-loop, invalid node kind/detail shape, rollback of nodes/edges/change_log/counters, transaction-local planning before LSN allocation/writes, and structured adapter diagnostics without thrown projected-code errors or fake endpoint refs) | D53-L; I1-L, I11-L | diff --git a/src/README.md b/src/README.md index 5591a07ab..26572bc41 100644 --- a/src/README.md +++ b/src/README.md @@ -45,7 +45,7 @@ src/ rules: graph/ -> db/ [allowed] workspace/ -> constants/ or workspace-local files only - projections/* -> agents/, graph/, session/, workspace/ [read/domain imports allowed; agents/ is temporary registry edge] + projections/* -> graph/, session/, workspace/ [read/domain imports allowed] renderers/* -> projections/, session/, workspace/ as needed for human/product input types agents/ -> graph/, projections/, session/, workspace/ [agent-visible text over already-read facts] .pi/ -> agents/, graph/, session/, projections/ [Pi runtime adapters/resources] @@ -61,7 +61,7 @@ Rules: - `workspace/` owns cwd-scoped identity, inventory, and workspace default-state persistence. It must not import Pi, session, graph, DB, projection, renderer, adapter, transport, app, or web modules. - `graph/` imports from `db/`. No other layer imports `db/` directly. -- `agents/` owns the Brunch-authored LLM-context ingress seam. Today it hosts agent prompt bodies, prompt-resource skills, prompt composition, prompt-resource/tool legality, context seed composition, reusable agent-visible context renderers, and the central file registry. The current `projections/session/runtime-policy.ts` import of this registry is a migration edge only: once the foreground roster moves under `agents/runtime/`, projections should stop depending on `agents/`. +- `agents/` owns the Brunch-authored LLM-context ingress seam. Today it hosts agent prompt bodies, prompt-resource skills, foreground roster policy, prompt composition, prompt-resource/tool legality, context seed composition, reusable agent-visible context renderers, and the central file registry. - `.pi/` owns Pi-harness extensions/components and no longer hosts Brunch-authored prompt bodies, prompt-resource skills, prompt composition, or provider-visible tool/session text. - `.pi/extensions/` registers Pi tools/hooks/UI affordances and delegates product semantics outward. - `projections/` owns reusable structured output; `agents/contexts/` owns reusable model-facing text; `renderers/` owns human/product-only lossy text output. @@ -75,6 +75,6 @@ The old domain-local `src/{graph,session,structured-exchange}/project/` folders The old domain-local `src/{graph,session,structured-exchange}/format/` folders and `src/render/` first moved under `renderers/`; reusable model-facing renderers now live under `agents/contexts/`, while `renderers/` retains human/product-only text. -Runtime-state transcript entry facts live in `session/runtime-state.ts`; reusable flattened runtime-state projection/policy now lives in `projections/session/runtime-state.ts` and `projections/session/runtime-policy.ts`. +Runtime-state transcript entry facts live in `session/runtime-state.ts`; reusable flattened runtime-state projection lives in `projections/session/runtime-state.ts`, while foreground roster/tool policy lives in `agents/runtime/policy.ts`. The earlier `src/agents/` top-level prompt subtree had moved under `src/.pi/{agents,skills}/`; the new `src/agents/` seam reclaims the name for Pi-independent LLM context ingress. Agent bodies have moved to `src/agents/prompts/`; prompt-resource skills have moved to `src/agents/skills/`. The old `src/.pi/context/` prompt-pack subtree remains retired. diff --git a/src/agents/README.md b/src/agents/README.md index 7f578806a..4f5e649c5 100644 --- a/src/agents/README.md +++ b/src/agents/README.md @@ -4,7 +4,7 @@ SPEC decisions: D39-L, D40-L, D52-L, D60-L, D85-L, D90-L, D91-L, D93-L ## Owns -`src/agents/` is the Pi-independent home for Brunch-authored model-facing context. It now owns bundled agent prompt bodies, Brunch prompt-resource skills, prompt composition/runtime legality, seed context composition, reusable agent-visible context renderers, and the central registry for prompt/skill paths. +`src/agents/` is the Pi-independent home for Brunch-authored model-facing context and runtime policy. It now owns bundled agent prompt bodies, Brunch prompt-resource skills, foreground roster policy, prompt composition/runtime legality, seed context composition, reusable agent-visible context renderers, and the central registry for prompt/skill paths. ```text agents/ @@ -24,12 +24,13 @@ rules: agents/registry.ts -> agents/prompts/*/SYSTEM.md [body file locations] agents/registry.ts -> agents/skills/*/*/SKILL.md [prompt-resource locations] agents/contexts/ -> graph/, projections/, session/, workspace/ [agent-visible text over already-read facts] + agents/runtime/ -> agents/registry, projections/session/capability-readiness, session/schema .pi/extensions/* -> agents/ [adapters ask for Brunch-authored context] session/ -> agents/contexts/seeds/ [origination asks for seed payload text] - projections/session/runtime-policy.ts -> agents/registry.ts [temporary roster-location edge] + projections/session/runtime-state.ts -> agents/runtime/policy.ts [consume code-owned roster] agents/ x> Pi extension hooks [no registration side effects] ``` ## Migration note -Agent prompt bodies, prompt-resource skills, prompt composition, prompt-resource/tool legality, seed context composition, reusable agent-visible context renderers, and formerly adapter-local model-facing text live here. Pi extensions remain runtime adapters that register hooks/tools, gather data, and call this layer for Brunch-authored text. +Agent prompt bodies, prompt-resource skills, foreground roster/tool policy, prompt composition, prompt-resource/tool legality, seed context composition, reusable agent-visible context renderers, and formerly adapter-local model-facing text live here. Pi extensions remain runtime adapters that register hooks/tools, gather data, and call this layer for Brunch-authored text. diff --git a/src/agents/prompts/README.md b/src/agents/prompts/README.md index cbf615e2d..a15371b7e 100644 --- a/src/agents/prompts/README.md +++ b/src/agents/prompts/README.md @@ -18,7 +18,7 @@ prompts/ └── pi-coder/SYSTEM.md future unwired coding-agent augmentation baseline ``` -This directory is markdown-only. It carries no TypeScript and registers no Pi hooks. Foreground metadata is code-owned in the op-mode-keyed foreground roster (`src/projections/session/runtime-policy.ts`), while body file locations are centralized in `src/agents/registry.ts`. Background metadata is authored as frontmatter but discovered only through the explicit `BACKGROUND_SUBAGENT_IDS` registry in `src/.pi/extensions/subagents/agents.ts`. +This directory is markdown-only. It carries no TypeScript and registers no Pi hooks. Foreground metadata is code-owned in the op-mode-keyed foreground roster (`src/agents/runtime/policy.ts`), while body file locations are centralized in `src/agents/registry.ts`. Background metadata is authored as frontmatter but discovered only through the explicit `BACKGROUND_SUBAGENT_IDS` registry in `src/.pi/extensions/subagents/agents.ts`. ## Prompt-shape decisions diff --git a/src/projections/README.md b/src/projections/README.md index 309f3c5ee..bd3dc77f4 100644 --- a/src/projections/README.md +++ b/src/projections/README.md @@ -30,7 +30,6 @@ Disposition: `✓` resolved (direct lock or accepted transitive proof) · `●` | `session/affordances` | 1 | ✓ | `affordances.test.ts` — gap-driven legality + default-on-switch derivation tested directly. Legal options are a menu projection over capability-readiness; omitted options are not capability refusals (I31-L). | | `session/capability-readiness` | 1 | ✓ | D74-L/D75-L tracer gate, not a reusable DTO. `capability-readiness.test.ts` locks the explicit capability→node-kind map, proceed / low-epistemic / negotiate outcomes, no-refusal invariant, loud failure when the gap register lacks a required kind, same-kind discrimination through `question`, and live presence-coverage flip. `session/affordances` now consumes it for axis-option legality. **D86-L: capability-readiness gates AUTO axis menus (`strategy`/`lens`) and the non-graph-write `review-for-gaps` method only — it never withholds a graph-write tool. `mutate_graph` and the review-set tools (`present_review_set`/`request_response`) are floor in elicit mode (their `commit-graph`/`generate-proposal` methods are absent from `METHOD_CAPABILITY` in `agents/runtime/state.ts`); `negotiate` is advisory (establishment offer + epistemic scaling), proven by `state.test.ts` + the tier-2 live-boot legality test.** | | `session/readiness-estimate` | — | ✓ | D45-L soft per-band coverage rollup over `ElicitationGap[]`; UI-only and gates nothing. `readiness-estimate.test.ts` locks every-band shape, empty-band zero, importance-weighted mean, honest regression, no grade imports, and no legality-path imports. | -| `session/runtime-policy` | 4 | ○ | Policy/definitions data, not a DTO transform. Gap-driven legality is guarded via `affordances.test.ts`; no runtime grade table remains. | | `session/assistant-visible-watermark` | 2 | ✓ | Carrier projection over the authoritative `continuity-entry-classifier` watermark set. Unit tests guard seed/overview/own-mutation/`worldUpdate` carriers, narrow-read exclusion, and cross-spec failure. | | `session/continuity-entry-classifier` | 2 | ✓ | Shared FE-847 taxonomy for watermark-carrier vs continuity-only-non-debt vs debt-bearing entries; consumed by watermark projection and origination tail classification. | | `session/sweep-watermark` | 1 | ✓ | FE-861 D80-L sweep-window projection. `sweep-watermark.test.ts` locks the transcript-backed marker, conversational/digest tail classification, raw-background exclusion, monotonic idempotent advance, and graph-LSN watermark separation. | @@ -69,4 +68,4 @@ projections/ x> .pi/, rpc/, app/, web/ Current migration notes: - `projections/exchanges/*` imports Zod schemas from `.pi/extensions/exchanges/schemas/` because D37-L/D41-L currently place the structured-exchange schema lock at that Pi transcript seam. That is an explicit temporary exception, not a general adapter dependency permission. -- `projections/session/runtime-state.ts` owns flattened runtime-state DTO projection while `session/runtime-state.ts` owns transcript entry facts and append helpers. +- `projections/session/runtime-state.ts` owns flattened runtime-state DTO projection while `session/runtime-state.ts` owns transcript entry facts and append helpers. It consumes the code-owned foreground roster/tool policy from `agents/runtime/policy.ts`; projections do not own agent body locations, foreground roster definitions, or tool policy. diff --git a/src/projections/__tests__/topology-boundaries.test.ts b/src/projections/__tests__/topology-boundaries.test.ts index 52d8ed11e..bc857bfcb 100644 --- a/src/projections/__tests__/topology-boundaries.test.ts +++ b/src/projections/__tests__/topology-boundaries.test.ts @@ -90,8 +90,6 @@ describe('projection topology boundaries', () => { expect(importedSourcePaths('src/session/runtime-state.ts')).not.toContain( 'src/projections/session/runtime-state.ts', ); - expect(importedSourcePaths('src/session/runtime-state.ts')).not.toContain( - 'src/projections/session/runtime-policy.ts', - ); + expect(importedSourcePaths('src/session/runtime-state.ts')).not.toContain('src/agents/runtime/policy.ts'); }); }); diff --git a/src/projections/session/__tests__/affordances.test.ts b/src/projections/session/__tests__/affordances.test.ts index afc68ca0c..496d73cfd 100644 --- a/src/projections/session/__tests__/affordances.test.ts +++ b/src/projections/session/__tests__/affordances.test.ts @@ -113,8 +113,10 @@ describe('runtime affordances derivation', () => { axisOptionsForRuntimeState('lens', resolved(), groundingFloorGaps({ coverage: { thesis: 0 } })), ).toEqual(['intent']); - for (const fileName of ['affordances.ts', 'runtime-policy.ts']) { - const sourcePath = fileURLToPath(new URL(`../${fileName}`, import.meta.url)); + for (const sourcePath of [ + fileURLToPath(new URL('../affordances.ts', import.meta.url)), + fileURLToPath(new URL('../../../agents/runtime/policy.ts', import.meta.url)), + ]) { const source = readFileSync(sourcePath, 'utf8'); expect(source).not.toMatch(/ReadinessGrade|GRADE_RANK|MIN_GRADE/); } diff --git a/src/projections/session/runtime-policy.ts b/src/projections/session/runtime-policy.ts deleted file mode 100644 index b03fdbe70..000000000 --- a/src/projections/session/runtime-policy.ts +++ /dev/null @@ -1,4 +0,0 @@ -// Temporary migration bridge: foreground agent runtime policy now lives in -// `src/agents/runtime/policy.ts`. This file is removed by the next refactor -// item once topology docs/import guards stop naming the old projections owner. -export * from '../../agents/runtime/policy.js'; diff --git a/src/session/README.md b/src/session/README.md index c711a5e67..57d17f54d 100644 --- a/src/session/README.md +++ b/src/session/README.md @@ -106,7 +106,7 @@ directly instead of growing a wrapper. | `cwd_inventory` | `workspace/cwd-inventory.ts` (`inspectWorkspaceCwdInventory`) | `read_workspace_context`, `agents/contexts/workspace/workspace-context.ts` | Workspace-owned direct PULL read. The typed inventory already matches the tool/renderer seam, so no `projections/workspace/workspace-context` wrapper survives. | | `workspace_overview` | `workspace-overview-context.ts` (`inspectWorkspaceOverview`) | `read_workspace_context`, origination seed context, `agents/contexts/workspace/workspace-context.ts` | Session-side composition over graph specs and canonical session files. Same no-wrapper rationale as `cwd_inventory`: the source shape is already the consumer shape. | | `workspace_session_state` | `WorkspaceSessionCoordinator` (`WorkspaceSessionState`) | `projections/workspace/workspace-state.ts`, `chromeStateForWorkspace`, app/rpc/web workspace flows | Source union owned by the coordinator. Downstream code may flatten it, but the coordinator remains the authority for the narrow chrome snapshot and status-variant field set. | -| `agent_runtime_vocab` | `schema/kinds.ts`, `schema/tool-names.ts` | `runtime-state.ts`, `projections/session/runtime-policy.ts`, `projections/session/affordances.ts`, `agents/runtime/state.ts`, `.pi/extensions/agent-runtime/orchestrator-stub/` | Pure vocabulary leaf for runtime axes, agent-role ids, and shared Brunch tool-name constants; imports nothing and mirrors D73-L's graph taxonomy direction on the session side. | +| `agent_runtime_vocab` | `schema/kinds.ts`, `schema/tool-names.ts` | `runtime-state.ts`, `agents/runtime/policy.ts`, `projections/session/affordances.ts`, `agents/runtime/state.ts`, `.pi/extensions/agent-runtime/orchestrator-stub/` | Pure vocabulary leaf for runtime axes, agent-role ids, and shared Brunch tool-name constants; imports nothing and mirrors D73-L's graph taxonomy direction on the session side. | | `agent_runtime_state` | `latestValidBrunchAgentStateEntryData` and transcript-backed runtime-state facts in `session/runtime-state.ts` | `projections/session/runtime-state.ts`, `projections/session/affordances.ts`, `.pi/extensions/agent-runtime/runtime/` | Transcript-backed source read. Projection/policy layers derive from these facts rather than storing parallel hidden runtime memory. | ## Runtime affordance coverage ledger diff --git a/src/treedocs.yaml b/src/treedocs.yaml index 79daf45a0..30d850810 100644 --- a/src/treedocs.yaml +++ b/src/treedocs.yaml @@ -234,6 +234,7 @@ tree: elicitor--pinned-strategy-lens.md: 'Markdown resource.' elicitor--pushed-context.md: 'Markdown resource.' compose.ts: 'Implements compose.' + policy.ts: 'Implements runtime policy.' prompt-skills.ts: 'Implements prompt skills.' state.ts: 'Implements state.' skills: @@ -383,7 +384,6 @@ tree: capability-readiness.ts: 'Implements capability readiness.' continuity-entry-classifier.ts: 'Implements continuity entry classifier.' readiness-estimate.ts: 'Implements readiness estimate.' - runtime-policy.ts: 'Implements runtime policy.' runtime-state.ts: 'Implements runtime state.' sweep-watermark.test.ts: 'Tests sweep watermark behavior.' sweep-watermark.ts: 'Implements sweep watermark.' From 9a6ce8435baa80db89b01407f7fa1785585a6ae0 Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Fri, 26 Jun 2026 09:24:02 +0200 Subject: [PATCH 23/54] Render related graph nodes semantically --- memory/REFACTOR.md | 2 +- .../graph/__snapshots__/related-hub-REQ1.md | 21 +++ .../graph/__tests__/related-nodes.test.ts | 36 +++++ src/agents/contexts/graph/related-nodes.ts | 125 +++++++++++++----- 4 files changed, 153 insertions(+), 31 deletions(-) create mode 100644 src/agents/contexts/graph/__snapshots__/related-hub-REQ1.md create mode 100644 src/agents/contexts/graph/__tests__/related-nodes.test.ts diff --git a/memory/REFACTOR.md b/memory/REFACTOR.md index 9dd54e661..aa179686b 100644 --- a/memory/REFACTOR.md +++ b/memory/REFACTOR.md @@ -52,7 +52,7 @@ The key judo move is deletion, not another rearrangement: remove the temporary p 1. [x] Add or move characterization tests for the two remaining orphan human/product renderers at their target owners: print-mode workspace text under the app layer, and debug transcript markdown under the session layer. Keep expected bytes unchanged. 2. [x] Move the foreground agent roster and tool-policy definitions into the agent runtime owner, then update projection and adapter callers to import runtime policy from `src/agents/runtime/`. Leave transcript-state projection in `projections/session`. 3. [x] Delete the `projections/session -> agents/registry` temporary edge and update topology docs/tests so projections no longer own or import agent body locations, foreground roster, or tool policy. -4. [ ] Repair `read_graph related` by making it share the semantic relation vocabulary already used by neighborhood rendering, or by deleting the separate related formatter in favor of a filtered-neighborhood render. Add a focused golden/invariant for the related mode so raw category/direction arrows and raw fallback ids do not silently return. +4. [x] Repair `read_graph related` by making it share the semantic relation vocabulary already used by neighborhood rendering, or by deleting the separate related formatter in favor of a filtered-neighborhood render. Add a focused golden/invariant for the related mode so raw category/direction arrows and raw fallback ids do not silently return. 5. [ ] Move print-mode workspace-state text out of `src/renderers/` to the app/print owner, update `brunch --mode print` imports, and delete the old workspace renderer file/directory. 6. [ ] Move debug transcript markdown out of `src/renderers/` to the session owner, update `session-transcript.ts` imports, and delete the old session renderer file/directory. 7. [ ] Delete `src/renderers/README.md` and the now-empty `src/renderers/` topology. Update root/topology READMEs, SPEC/PLAN references, and lint/import-boundary comments to remove `renderers/` as a live source layer. diff --git a/src/agents/contexts/graph/__snapshots__/related-hub-REQ1.md b/src/agents/contexts/graph/__snapshots__/related-hub-REQ1.md new file mode 100644 index 000000000..14135f210 --- /dev/null +++ b/src/agents/contexts/graph/__snapshots__/related-hub-REQ1.md @@ -0,0 +1,21 @@ +Related nodes: 13 node(s), 13 relation(s). +Anchors: REQ1 Stage 2 configuration-space requirement (hub anchor) + +upstream nodes (3) — review anchors if these change +- depends on A1: Local-only execution assumption +- expresses INV1: No network call invariant +- bounded by CON1: No cloud dependencies constraint + +downstream nodes (9) — reconcile these if anchors change +- required by D1: Two-stage split decision {hard} +- implemented by MOD1: SQLite configuration store module +- established by S1: Persist configuration spaces slice +- witnessed by AC1: Airplane-mode acceptance criterion +- challenged by EX1: Network-outage counterexample +- motivated by CTX1: Stakeholder offline-first preference +- opposed by CTX2: Conflicting always-connected note +- part of F1: Configuration-space data frontier +- superseded by REQ2: Revised configuration-space requirement (successor) + +lateral nodes (1) — cross-check with anchors if either changes +- related to G1: Offline-first product goal \ No newline at end of file diff --git a/src/agents/contexts/graph/__tests__/related-nodes.test.ts b/src/agents/contexts/graph/__tests__/related-nodes.test.ts new file mode 100644 index 000000000..50fed0b40 --- /dev/null +++ b/src/agents/contexts/graph/__tests__/related-nodes.test.ts @@ -0,0 +1,36 @@ +import { expect, test } from 'vitest'; + +import { readNodeNeighborhoodFixture } from '../../../../graph/__tests__/support/fixture-reads.js'; +import { formatRelatedNodesResult } from '../related-nodes.js'; + +function expectNoStructuralLeak(rendered: string): void { + expect(rendered).not.toContain('-['); + expect(rendered).not.toMatch(/\bdependency\b|\bwitness\b|\brationale\b|\brealization\b/); + expect(rendered).not.toMatch(/\bincoming\b|\boutgoing\b/); + expect(rendered).not.toContain('#'); + expect(rendered).not.toContain('intent/'); + expect(rendered).not.toContain('oracle/'); + expect(rendered).not.toContain('design/'); + expect(rendered).not.toContain('plan/'); +} + +test('related nodes uses semantic relation labels instead of raw graph internals', async () => { + const rendered = formatRelatedNodesResult({ + status: 'success', + anchors: [ + readNodeNeighborhoodFixture({ set: 'edge-spread', fixture: 'hub-neighborhood', anchorCode: 'REQ1' }), + ], + }); + + await expect(rendered).toMatchFileSnapshot('../__snapshots__/related-hub-REQ1.md'); + expect(rendered).toContain('upstream nodes'); + expect(rendered).toContain('downstream nodes'); + expect(rendered).toContain('cross-check'); + expectNoStructuralLeak(rendered); +}); + +test('related nodes missing anchors renders a clear miss', () => { + expect(formatRelatedNodesResult({ status: 'not_found' })).toBe( + 'One or more anchor nodes were not found in the selected spec.', + ); +}); diff --git a/src/agents/contexts/graph/related-nodes.ts b/src/agents/contexts/graph/related-nodes.ts index e6a2d9835..2a5e8c81e 100644 --- a/src/agents/contexts/graph/related-nodes.ts +++ b/src/agents/contexts/graph/related-nodes.ts @@ -1,11 +1,36 @@ -import type { NodeNeighborhood } from '../../../graph/queries.js'; -import { formatGraphNodeCode } from '../../../graph/schema/nodes.js'; +import type { GraphEdge, NodeNeighborhood } from '../../../graph/index.js'; +import type { EdgeEndpoint } from '../../../graph/policy/category-policy.js'; +import { relationFromAnchor, type EdgeRelation } from '../../../graph/projection/direction.js'; +import { edgeLabel } from '../../../graph/projection/labels.js'; +import { formatGraphNodeCode, type GraphNode } from '../../../graph/schema/nodes.js'; export interface RelatedNodesResult { readonly status: 'success' | 'not_found'; readonly anchors?: readonly NodeNeighborhood[]; } +const SECTION_ORDER: readonly EdgeRelation[] = ['upstream', 'downstream', 'lateral']; + +const SECTION_HEADING: Record = { + upstream: 'upstream nodes', + downstream: 'downstream nodes', + lateral: 'lateral nodes', +}; + +const SECTION_GLOSS: Record = { + upstream: 'review anchors if these change', + downstream: 'reconcile these if anchors change', + lateral: 'cross-check with anchors if either changes', +}; + +interface ProjectedRelatedEdge { + readonly relation: EdgeRelation; + readonly label: string; + readonly code: string; + readonly title: string; + readonly hard: boolean; +} + export function formatRelatedNodesResult(result: RelatedNodesResult): string { if (result.status === 'not_found') return 'One or more anchor nodes were not found in the selected spec.'; @@ -15,41 +40,81 @@ export function formatRelatedNodesResult(result: RelatedNodesResult): string { ); const related = new Map(found.flatMap((anchor) => anchor.related.map((node) => [node.id, node] as const))); const edges = found.flatMap((anchor) => anchor.edges); - const nodesById = new Map([...found.map((anchor) => [anchor.node.id, anchor.node] as const), ...related]); + const nodesById = new Map([ + ...found.map((anchor) => [anchor.node.id, anchor.node] as const), + ...related, + ]); + const projected = projectRelatedEdges(found, edges, nodesById); const lines = [ - `Related nodes: ${related.size} node(s), ${edges.length} edge(s).`, - `Anchors: ${found.map((anchor) => `[${formatGraphNodeCode(anchor.node.kind, anchor.node.kindOrdinal)}] ${anchor.node.title}`).join(', ')}`, + `Related nodes: ${related.size} node(s), ${projected.length} relation(s).`, + `Anchors: ${found.map((anchor) => `${formatNode(anchor.node)} ${anchor.node.title}`).join(', ')}`, ]; - if (related.size === 0) { - lines.push('Related: none'); + if (projected.length === 0) { + lines.push('', 'No relations.'); } else { - lines.push('Related:'); - for (const node of related.values()) { - lines.push( - ` - [${formatGraphNodeCode(node.kind, node.kindOrdinal)}] ${node.plane}/${node.kind}: "${node.title}"`, - ); + for (const relation of SECTION_ORDER) { + const inSection = projected.filter((edge) => edge.relation === relation); + if (inSection.length === 0) continue; + lines.push('', `${SECTION_HEADING[relation]} (${inSection.length}) — ${SECTION_GLOSS[relation]}`); + lines.push(...inSection.map(formatEdge)); } } - if (edges.length === 0) { - lines.push('Edges: none'); - } else { - lines.push('Edges:'); - const anchorIds = new Set(found.map((anchor) => anchor.node.id)); - for (const edge of edges) { - const source = nodesById.get(edge.sourceId); - const target = nodesById.get(edge.targetId); - const sourceCode = source ? formatGraphNodeCode(source.kind, source.kindOrdinal) : `#${edge.sourceId}`; - const targetCode = target ? formatGraphNodeCode(target.kind, target.kindOrdinal) : `#${edge.targetId}`; - const direction = anchorIds.has(edge.sourceId) - ? 'outgoing' - : anchorIds.has(edge.targetId) - ? 'incoming' - : 'lateral'; - lines.push(` - ${sourceCode} -[${edge.category}/${direction}]-> ${targetCode}`); - } + return lines.join('\n'); +} + +function projectRelatedEdges( + anchors: readonly Extract[], + edges: readonly GraphEdge[], + nodesById: ReadonlyMap, +): readonly ProjectedRelatedEdge[] { + const anchorIds = new Set(anchors.map((anchor) => anchor.node.id)); + const projected: ProjectedRelatedEdge[] = []; + const seen = new Set(); + + for (const edge of edges) { + const anchorRole = anchorRoleForEdge(edge, anchorIds); + if (!anchorRole) continue; + const otherId = anchorRole === 'source' ? edge.targetId : edge.sourceId; + if (anchorIds.has(otherId)) continue; + + const dedupeKey = `${edge.category}|${edge.stance ?? 'none'}|${anchorRole}|${otherId}`; + if (seen.has(dedupeKey)) continue; + seen.add(dedupeKey); + + const { relation, strength } = relationFromAnchor(edge.category, anchorRole); + const source = nodesById.get(edge.sourceId); + const target = nodesById.get(edge.targetId); + const other = nodesById.get(otherId); + projected.push({ + relation, + label: edgeLabel({ + category: edge.category, + anchorRole, + stance: edge.stance, + sourceKind: source?.kind, + targetKind: target?.kind, + }), + code: other ? formatNode(other) : 'missing endpoint', + title: other?.title ?? 'missing endpoint', + hard: relation === 'downstream' && strength === 'cascade', + }); } - return lines.join('\n'); + return projected; +} + +function anchorRoleForEdge(edge: GraphEdge, anchorIds: ReadonlySet): EdgeEndpoint | undefined { + if (anchorIds.has(edge.sourceId)) return 'source'; + if (anchorIds.has(edge.targetId)) return 'target'; + return undefined; +} + +function formatEdge(edge: ProjectedRelatedEdge): string { + return `- ${edge.label} ${edge.code}: ${edge.title}${edge.hard ? ' {hard}' : ''}`; +} + +function formatNode(node: GraphNode): string { + return formatGraphNodeCode(node.kind, node.kindOrdinal); } From b8ba992a7b603fe07a0975613960573717d50bcf Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Fri, 26 Jun 2026 09:25:50 +0200 Subject: [PATCH 24/54] Move print workspace rendering into app --- memory/REFACTOR.md | 2 +- memory/SPEC.md | 2 +- src/README.md | 2 +- src/agents/contexts/workspace/README.md | 2 +- src/app/README.md | 2 ++ src/app/__tests__/print-workspace-state.test.ts | 2 +- src/app/brunch.ts | 2 +- .../workspace-state.ts => app/print-workspace-state.ts} | 2 +- src/renderers/README.md | 3 +-- src/scripts/README.md | 2 +- src/treedocs.yaml | 3 +-- 11 files changed, 12 insertions(+), 12 deletions(-) rename src/{renderers/workspace/workspace-state.ts => app/print-workspace-state.ts} (84%) diff --git a/memory/REFACTOR.md b/memory/REFACTOR.md index aa179686b..a5bbcdc40 100644 --- a/memory/REFACTOR.md +++ b/memory/REFACTOR.md @@ -53,7 +53,7 @@ The key judo move is deletion, not another rearrangement: remove the temporary p 2. [x] Move the foreground agent roster and tool-policy definitions into the agent runtime owner, then update projection and adapter callers to import runtime policy from `src/agents/runtime/`. Leave transcript-state projection in `projections/session`. 3. [x] Delete the `projections/session -> agents/registry` temporary edge and update topology docs/tests so projections no longer own or import agent body locations, foreground roster, or tool policy. 4. [x] Repair `read_graph related` by making it share the semantic relation vocabulary already used by neighborhood rendering, or by deleting the separate related formatter in favor of a filtered-neighborhood render. Add a focused golden/invariant for the related mode so raw category/direction arrows and raw fallback ids do not silently return. -5. [ ] Move print-mode workspace-state text out of `src/renderers/` to the app/print owner, update `brunch --mode print` imports, and delete the old workspace renderer file/directory. +5. [x] Move print-mode workspace-state text out of `src/renderers/` to the app/print owner, update `brunch --mode print` imports, and delete the old workspace renderer file/directory. 6. [ ] Move debug transcript markdown out of `src/renderers/` to the session owner, update `session-transcript.ts` imports, and delete the old session renderer file/directory. 7. [ ] Delete `src/renderers/README.md` and the now-empty `src/renderers/` topology. Update root/topology READMEs, SPEC/PLAN references, and lint/import-boundary comments to remove `renderers/` as a live source layer. 8. [ ] Reconcile stale path fossils found during the move: old `.pi/extensions/system-prompts`, `.pi/extensions/graph`, `../web`, and renderers references in co-located READMEs and memory files. diff --git a/memory/SPEC.md b/memory/SPEC.md index 149defbc3..49a34b86b 100644 --- a/memory/SPEC.md +++ b/memory/SPEC.md @@ -358,7 +358,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c | I28-L | Auto-compaction output preserves the configured anchor set byte-stable: every entry kind listed in [src/.pi/extensions/compaction/index.ts](src/.pi/extensions/compaction/index.ts) is reconstructable post-compaction according to its `select` rule (`first | latest | active-leaves | all-unresolved`); LLM-generated narrative summary never replaces or rephrases preserved-anchor content; extension failure falls through to Pi default compaction rather than dropping anchors silently. | planned (compaction round-trip property tests at M9 plus inner-loop anchor-rendering unit tests and TypeBox schema validation of the anchor contract) | D43-L; R15, R13; I3-L, I4-L, I8-L, I12-L | | I29-L | Subagent SDK child sessions inherit Brunch Pi Profile sealing while allowing explicitly injected parent-world reads: every `subagent` tool invocation builds an in-process `AgentSession` from explicit sealed services (in-memory auth/settings/session managers, no ambient resources, assembled background system prompt, parent model registry, explicit tool allowlist); subagents never load ambient user/project `.pi/` skills, prompts, themes, extensions, context files, or behavior-shaping settings; subagents never gain direct access to the parent's `CommandExecutor`, Brunch RPC handlers, or graph persistence; parent world access is injected by the app root as a snapshot prompt block plus selected-spec read tools such as `read_graph`; parent aborts prevent prompt execution before/during setup and abort live child sessions; subagent results return to the main agent only as tool result content (no side-effect transcript writes). | covered for the implemented SDK seam by `src/.pi/extensions/subagents/subagents.test.ts`: frontmatter/config validation (including duplicate keys), explicit registry loading from `src/.pi/agents//SYSTEM.md` while ignoring unlisted planted bodies, tool allowlist conformance for `explorer`/`projector`/`researcher`, sealed faux-provider child sessions with no inherited base prompt or conversation, assembled prompt snapshot coverage (selected spec/workspace/session digest, no foreground elicitation recommendation), unknown-tool failure, `read_graph` availability only with injected parent graph readers, parent-spec-only graph read content with sibling-spec negative assertion, bounded concurrency including waiter/new-arrival race, invalid invocation shape rejection before runner call, and parent-abort setup/live-session behavior. Startup advertisement remains dev-gated by whether a launch path supplies subagent deps to `createBrunchPiExtensions(...)`. | D2-L, D39-L, D40-L, D44-L, D91-L; I1-L, I2-L, I11-L, I24-L | | I30-L | Elicitor capture commits only high-confidence graph truth; under the D81-L gradient, directly-stated facts commit `explicit`, confidently-materialized facts/edges commit `implicit`, low-confidence noticings never become graph truth — they map to existing-or-new `elicitation_gaps` as agenda — and contradictions with existing graph truth route to `reconciliation_need` rather than gap or overwrite. | covered for deterministic routing (`src/graph/__tests__/capture-commitment-gradient-gate.test.ts` proves the FE-861 routing gate through the real `mutate_graph`, `update_elicitation_gaps`, and `update_reconciliation_needs` adapters: explicit→commit, implicit→commit, low→one gap, contradiction→one semantic-conflict recon need, structural answered derivation, manual gap close on the graph clock, illegal capture batches failing loud, and the closed capture-quality-spike scenario family re-aimed from binary `shouldCommit` to gradient `expectedOutcome` rows across free prose, file refs, implication-heavy, and contradiction classes. `src/probes/capture-quality-loop.ts` keeps the LLM-in-loop probe as fitness by scoring gradient-routing accuracy, not gating classification quality. `src/.pi/extensions/brunch-data/reconciliation/index.test.ts` proves the recon-need tool pair over `CommandExecutor`/`getOpenReconciliationNeeds` plus elicit-posture legality. `src/projections/session/sweep-watermark.test.ts` plus `src/.pi/__tests__/extension-registry.test.ts` prove the D80-L transcript-position sweep watermark: conversational/digest tail classification, raw background exclusion, idempotent marker advance, graph-LSN watermark separation, and live `before_agent_start` wiring. The submit-time labeled-prefix capture module, its `session.*` wiring, and the `capture-response-to-graph` / `submit-message-capture` proofs were deleted 2026-06-19 (D80-L fossil retirement); `session.submitMessage` / `session.submitExchangeResponse` results no longer carry a `capture` field. Confidence/dedup quality remains fitness.) | D8-L, D18-L, D47-L, D65-L, D80-L, D81-L; A22-L | -| I31-L | Readiness never bars graph truth or work; it is just-in-time capability-readiness over relevant gaps, not a stored grade or kind whitelist. There is no `readiness_grade` scalar; capability availability is judged on request against the relevant `elicitation_gaps` (D74-L) and may proceed, proceed at low epistemic status, or negotiate — it never refuses outright. The `CommandExecutor` must not reject a graph node solely because its kind belongs to a later readiness band (D64-L). The soft `readiness estimate` (D45-L) is UI-only and gates nothing. Capability-readiness never *withholds a graph-write tool*: `mutate_graph` and the review-set tools stay in the active tool set regardless of readiness; `negotiate` is advisory (establishment offer + epistemic scaling), never a tool gate (D86-L). | partially covered (`src/projections/session/capability-readiness.test.ts` covers the D74-L tracer gate, including proceed / proceed_low_epistemic / negotiate, no-refusal, no grade-symbol import, and a live `presence` coverage flip; `src/projections/session/affordances.test.ts` covers the first consumer rewire: menu legality omits gated options while relevant gaps negotiate and includes them when coverage rises, with no grade symbols in `agents/runtime/policy.ts` / `affordances.ts`, and a required `NodeKind` absent from the gap register fails loud (config bug ≠ uncovered — readiness omission never masks a seeding error); `src/projections/session/readiness-estimate.test.ts` covers the soft D45-L estimate shape, empty-band zero, importance-weighted per-band coverage, honest regression, and no legality-path imports; `src/.pi/extensions/agent-runtime/runtime/state.test.ts`, `src/agents/runtime/__tests__/compose.test.ts`, `src/agents/contexts/seeds/__tests__/turn-context.test.ts`, and `src/.pi/__tests__/prompting.test.ts` cover the prompt consumer path: selected-spec gaps render as the soft per-band estimate, readiness-thin pinned axes remain visible, gated methods stay withheld, `readiness_grade=` is absent from prompt display, and the turn boundary threads the same gaps into cwd context without prompt-assembly failure; `src/session/workspace-session-coordinator.test.ts`, `src/renderers/workspace/__tests__/workspace-state.test.ts`, `src/session/workspace-overview-context.test.ts`, `src/.pi/__tests__/context-tools.test.ts`, `src/rpc/handlers.test.ts`, and `src/web/app.test.tsx` cover the workspace/chrome display retirement: `chrome.phase` / `chrome.chatMode` no longer project through coordinator/RPC/web/chrome fixtures, and workspace overview session inventory no longer carries or renders `readinessGrade`; `createSpec` / `getSpec` persistence, seed/export fixture contracts, probes, and selected-spec prompt carriers no longer persist or transport a readiness grade; the D86-L graph-write-tool-floor sub-claim is covered — `state.test.ts` proves `mutate_graph` + review-set tools stay floor while `propose-graph`/`project-graph` readiness `negotiate`s and only the non-graph-write `review-for-gaps` is withheld, and `dev/__tests__/tier-2-harness.test.ts` proves the same through a real `runBrunchTui` boot at thin vs covered grounding) | D20-L, D45-L, D64-L, D74-L, D86-L | +| I31-L | Readiness never bars graph truth or work; it is just-in-time capability-readiness over relevant gaps, not a stored grade or kind whitelist. There is no `readiness_grade` scalar; capability availability is judged on request against the relevant `elicitation_gaps` (D74-L) and may proceed, proceed at low epistemic status, or negotiate — it never refuses outright. The `CommandExecutor` must not reject a graph node solely because its kind belongs to a later readiness band (D64-L). The soft `readiness estimate` (D45-L) is UI-only and gates nothing. Capability-readiness never *withholds a graph-write tool*: `mutate_graph` and the review-set tools stay in the active tool set regardless of readiness; `negotiate` is advisory (establishment offer + epistemic scaling), never a tool gate (D86-L). | partially covered (`src/projections/session/capability-readiness.test.ts` covers the D74-L tracer gate, including proceed / proceed_low_epistemic / negotiate, no-refusal, no grade-symbol import, and a live `presence` coverage flip; `src/projections/session/affordances.test.ts` covers the first consumer rewire: menu legality omits gated options while relevant gaps negotiate and includes them when coverage rises, with no grade symbols in `agents/runtime/policy.ts` / `affordances.ts`, and a required `NodeKind` absent from the gap register fails loud (config bug ≠ uncovered — readiness omission never masks a seeding error); `src/projections/session/readiness-estimate.test.ts` covers the soft D45-L estimate shape, empty-band zero, importance-weighted per-band coverage, honest regression, and no legality-path imports; `src/.pi/extensions/agent-runtime/runtime/state.test.ts`, `src/agents/runtime/__tests__/compose.test.ts`, `src/agents/contexts/seeds/__tests__/turn-context.test.ts`, and `src/.pi/__tests__/prompting.test.ts` cover the prompt consumer path: selected-spec gaps render as the soft per-band estimate, readiness-thin pinned axes remain visible, gated methods stay withheld, `readiness_grade=` is absent from prompt display, and the turn boundary threads the same gaps into cwd context without prompt-assembly failure; `src/session/workspace-session-coordinator.test.ts`, `src/app/__tests__/print-workspace-state.test.ts`, `src/session/workspace-overview-context.test.ts`, `src/.pi/__tests__/context-tools.test.ts`, `src/rpc/handlers.test.ts`, and `src/web/app.test.tsx` cover the workspace/chrome display retirement: `chrome.phase` / `chrome.chatMode` no longer project through coordinator/RPC/web/chrome fixtures, and workspace overview session inventory no longer carries or renders `readinessGrade`; `createSpec` / `getSpec` persistence, seed/export fixture contracts, probes, and selected-spec prompt carriers no longer persist or transport a readiness grade; the D86-L graph-write-tool-floor sub-claim is covered — `state.test.ts` proves `mutate_graph` + review-set tools stay floor while `propose-graph`/`project-graph` readiness `negotiate`s and only the non-graph-write `review-for-gaps` is withheld, and `dev/__tests__/tier-2-harness.test.ts` proves the same through a real `runBrunchTui` boot at thin vs covered grounding) | D20-L, D45-L, D64-L, D74-L, D86-L | | I32-L | Public RPC structured-exchange driving never requires a client to speak raw Pi RPC: after Brunch method discovery and workspace/spec/session activation, each pending assistant-originated exchange is answered exactly once through `session.submitExchangeResponse`, and the deterministic permutation run produces linear Pi JSONL whose structured exchange projection preserves the same prompt/answer/status/comment artifacts as the equivalent TUI structured-exchange path. | covered for deterministic FE-744 parity under canonical session method names (`session.triggerExchange`, `session.pendingExchange`, `session.submitExchangeResponse`, `session.exchanges`): `rpc.discover` contract tests, pending/respond lifecycle tests, current public-RPC structured-exchange permutations, terminal non-answered status handling, option content/rationale parity, no repeated deterministic prompts, and transcript/exchange parity assertions. | D5-L, D48-L, D49-L; I10-L, I13-L, I21-L, I23-L | | I33-L | `capture_*` analysis entries are transcript evidence only: they persist as Brunch structured-exchange `toolResult` rows, are included by Brunch-semantic transcript renderers, are hidden or collapsed in TUI display, and never mutate graph truth or bypass `CommandExecutor`. | partially covered (minimum capture details schemas parse/export and reject graph payload fields; future runtime capture-analysis schema/rendering tests plus transcript renderer fixtures still need to prove persisted result rendering and TUI hide/collapse behavior; later graph-capture fixtures compare analysis candidates against committed graph mutations) | D17-L, D18-L, D37-L, D47-L, D50-L; I2-L, I11-L, I23-L, I30-L | | I34-L | `mutateGraph` batch validation is all-or-nothing: if any node or edge in the batch is structurally illegal, the entire batch is rejected and no partial state is persisted; the agent receives diagnostics sufficient for bounded self-correction retry. | covered (`command-executor/commit-graph-batch.test.ts` and graph-tool adapter tests cover dry-run/commit diagnostic parity for invalid basis, missing refs/codes, invalid category/stance, self-loop, invalid node kind/detail shape, rollback of nodes/edges/change_log/counters, transaction-local planning before LSN allocation/writes, and structured adapter diagnostics without thrown projected-code errors or fake endpoint refs) | D53-L; I1-L, I11-L | diff --git a/src/README.md b/src/README.md index 26572bc41..f59874600 100644 --- a/src/README.md +++ b/src/README.md @@ -69,7 +69,7 @@ Rules: ## Migration notes -Product entrypoints now live in `app/`; package/project identity helpers and `.brunch/workspace.json` default-state persistence live in `workspace/`; reusable workspace state DTOs live in `projections/workspace/`; and reusable print-mode workspace-state text lives in `renderers/workspace/`. No compatibility root files remain for the old root-level Brunch entrypoint, print helper, or package-identity paths. +Product entrypoints now live in `app/`; package/project identity helpers and `.brunch/workspace.json` default-state persistence live in `workspace/`; reusable workspace state DTOs live in `projections/workspace/`; and print-mode workspace-state text lives beside the print entrypoint in `app/print-workspace-state.ts`. No compatibility root files remain for the old root-level Brunch entrypoint, print helper, or package-identity paths. The old domain-local `src/{graph,session,structured-exchange}/project/` folders now live under `projections/{graph,session,exchanges}/`. diff --git a/src/agents/contexts/workspace/README.md b/src/agents/contexts/workspace/README.md index 9affdf041..51dd745f9 100644 --- a/src/agents/contexts/workspace/README.md +++ b/src/agents/contexts/workspace/README.md @@ -2,4 +2,4 @@ SPEC decisions: D19-L, D60-L, D83-L -Owns the `` context render for cwd/project/topology/spec-roster facts. It is agent context, not `workspace.state`; human print-mode workspace state stays in `src/renderers/workspace/workspace-state.ts`. +Owns the `` context render for cwd/project/topology/spec-roster facts. It is agent context, not `workspace.state`; human print-mode workspace state stays with the print-mode app owner in `src/app/print-workspace-state.ts`. diff --git a/src/app/README.md b/src/app/README.md index 9d704fb78..a8aa44654 100644 --- a/src/app/README.md +++ b/src/app/README.md @@ -11,6 +11,8 @@ Current entrypoints: - `brunch.ts` — CLI mode dispatch for TUI, RPC, and print. `--mode web` is reserved but deferred: the browser client is served only as the TUI sidecar (a standalone headless web host is a future feature). +- `print-workspace-state.ts` — terse human/product print-mode rendering for + `brunch --mode print`. - `brunch-tui.ts` — TUI launch path, embedded Pi session runtime wiring, and the web sidecar (`startWebHost`; browser launch is opt-in via `--open-web`). diff --git a/src/app/__tests__/print-workspace-state.test.ts b/src/app/__tests__/print-workspace-state.test.ts index 88fe3624a..a24a7f8da 100644 --- a/src/app/__tests__/print-workspace-state.test.ts +++ b/src/app/__tests__/print-workspace-state.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest'; import type { WorkspaceState } from '../../projections/workspace/workspace-state.js'; -import { renderWorkspaceState } from '../../renderers/workspace/workspace-state.js'; +import { renderWorkspaceState } from '../print-workspace-state.js'; const cwd = '/tmp/brunch-project'; diff --git a/src/app/brunch.ts b/src/app/brunch.ts index 56007f7ed..aea84d76d 100644 --- a/src/app/brunch.ts +++ b/src/app/brunch.ts @@ -6,7 +6,6 @@ import { parseArgs } from 'node:util'; import { isBrunchDevEnabled } from '../dev/brunch-dev.js'; import { projectWorkspaceState } from '../projections/workspace/workspace-state.js'; -import { renderWorkspaceState } from '../renderers/workspace/workspace-state.js'; import { createRpcHandlers, runJsonRpcLineServer } from '../rpc/handlers.js'; import { createProductUpdatePublisher } from '../rpc/product-updates.js'; import { @@ -14,6 +13,7 @@ import { type WorkspaceSessionCoordinator, } from '../session/workspace-session-coordinator.js'; import { runBrunchTui } from './brunch-tui.js'; +import { renderWorkspaceState } from './print-workspace-state.js'; export interface BrunchCliOptions { argv?: string[]; diff --git a/src/renderers/workspace/workspace-state.ts b/src/app/print-workspace-state.ts similarity index 84% rename from src/renderers/workspace/workspace-state.ts rename to src/app/print-workspace-state.ts index 9f14f748c..e1b557217 100644 --- a/src/renderers/workspace/workspace-state.ts +++ b/src/app/print-workspace-state.ts @@ -1,4 +1,4 @@ -import type { WorkspaceState } from '../../projections/workspace/workspace-state.js'; +import type { WorkspaceState } from '../projections/workspace/workspace-state.js'; export function renderWorkspaceState(state: WorkspaceState): string { const lines = [ diff --git a/src/renderers/README.md b/src/renderers/README.md index 2cfc5aa8c..82b3764d4 100644 --- a/src/renderers/README.md +++ b/src/renderers/README.md @@ -4,11 +4,10 @@ SPEC decisions: D52-L, D60-L, D83-L ## Owns -`src/renderers/` now owns reusable text that is **not** deliberately model-facing: product/human print output and debug/report text. +`src/renderers/` now owns reusable text that is **not** deliberately model-facing. Print-mode workspace-state text moved to the app print owner; this directory only retains debug/report transcript markdown until the session move lands. ```text renderers/ -├── workspace/workspace-state.ts print-mode workspace state text └── session/transcript.ts debug/report transcript markdown ``` diff --git a/src/scripts/README.md b/src/scripts/README.md index f30e351af..859b746e5 100644 --- a/src/scripts/README.md +++ b/src/scripts/README.md @@ -7,7 +7,7 @@ SPEC decisions: D52-L Local executable utilities and script-facing helpers that are not product domain layers. No standing script utilities are currently owned here. -Print-mode workspace-state projection/rendering moved to `projections/workspace/` and `renderers/workspace/`; `app/` now calls those shared seams directly. +Print-mode workspace-state projection moved to `projections/workspace/`, and its terse human rendering is app-local in `app/print-workspace-state.ts`; `app/` calls those seams directly. ## Does not own diff --git a/src/treedocs.yaml b/src/treedocs.yaml index 30d850810..e13338b51 100644 --- a/src/treedocs.yaml +++ b/src/treedocs.yaml @@ -291,6 +291,7 @@ tree: pi-extensions.ts: 'Implements pi extensions.' pi-settings.ts: 'Implements pi settings.' pi-subagents.ts: 'Implements pi subagents.' + print-workspace-state.ts: 'Implements print workspace state.' constants.ts: 'Implements constants.' db: README.md: 'Documents this source subtree.' @@ -394,8 +395,6 @@ tree: README.md: 'Documents this source subtree.' session: transcript.ts: 'Implements transcript.' - workspace: - workspace-state.ts: 'Implements workspace state.' rpc: README.md: 'Documents this source subtree.' handlers.ts: 'Implements handlers.' From f7527f951feb0b9c00eeb3bd6d6f537bd2047cf4 Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Fri, 26 Jun 2026 09:26:54 +0200 Subject: [PATCH 25/54] Move transcript markdown into session --- memory/REFACTOR.md | 2 +- src/agents/contexts/session/README.md | 2 +- src/projections/session/transcript-context.ts | 2 +- src/session/README.md | 3 ++- src/session/__tests__/transcript-markdown.test.ts | 2 +- src/session/session-transcript.ts | 2 +- .../session/transcript.ts => session/transcript-markdown.ts} | 2 +- src/treedocs.yaml | 3 +-- 8 files changed, 9 insertions(+), 9 deletions(-) rename src/{renderers/session/transcript.ts => session/transcript-markdown.ts} (95%) diff --git a/memory/REFACTOR.md b/memory/REFACTOR.md index a5bbcdc40..60e1b1328 100644 --- a/memory/REFACTOR.md +++ b/memory/REFACTOR.md @@ -54,7 +54,7 @@ The key judo move is deletion, not another rearrangement: remove the temporary p 3. [x] Delete the `projections/session -> agents/registry` temporary edge and update topology docs/tests so projections no longer own or import agent body locations, foreground roster, or tool policy. 4. [x] Repair `read_graph related` by making it share the semantic relation vocabulary already used by neighborhood rendering, or by deleting the separate related formatter in favor of a filtered-neighborhood render. Add a focused golden/invariant for the related mode so raw category/direction arrows and raw fallback ids do not silently return. 5. [x] Move print-mode workspace-state text out of `src/renderers/` to the app/print owner, update `brunch --mode print` imports, and delete the old workspace renderer file/directory. -6. [ ] Move debug transcript markdown out of `src/renderers/` to the session owner, update `session-transcript.ts` imports, and delete the old session renderer file/directory. +6. [x] Move debug transcript markdown out of `src/renderers/` to the session owner, update `session-transcript.ts` imports, and delete the old session renderer file/directory. 7. [ ] Delete `src/renderers/README.md` and the now-empty `src/renderers/` topology. Update root/topology READMEs, SPEC/PLAN references, and lint/import-boundary comments to remove `renderers/` as a live source layer. 8. [ ] Reconcile stale path fossils found during the move: old `.pi/extensions/system-prompts`, `.pi/extensions/graph`, `../web`, and renderers references in co-located READMEs and memory files. diff --git a/src/agents/contexts/session/README.md b/src/agents/contexts/session/README.md index 1ca7218c4..390d5375f 100644 --- a/src/agents/contexts/session/README.md +++ b/src/agents/contexts/session/README.md @@ -2,4 +2,4 @@ SPEC decisions: D40-L, D45-L, D60-L, D83-L -Owns reusable model-facing session context fragments, currently the runtime-frame render and shared soft-readiness estimate. Transcript debug/report text remains in `src/renderers/session/transcript.ts` until it becomes deliberate agent context. +Owns reusable model-facing session context fragments, currently the runtime-frame render and shared soft-readiness estimate. Transcript debug/report text is human/product debug output owned by `src/session/transcript-markdown.ts`, not agent context. diff --git a/src/projections/session/transcript-context.ts b/src/projections/session/transcript-context.ts index fc48e1bbd..c9d435a99 100644 --- a/src/projections/session/transcript-context.ts +++ b/src/projections/session/transcript-context.ts @@ -9,7 +9,7 @@ * - debug filtering policy: user, assistant, and Brunch-owned custom tool results * * Used by: - * - renderers/session/transcript.ts + * - session/transcript-markdown.ts * - `.brunch/debug/transcript.md` writers */ diff --git a/src/session/README.md b/src/session/README.md index 57d17f54d..f13748c1f 100644 --- a/src/session/README.md +++ b/src/session/README.md @@ -148,7 +148,7 @@ schema, and the product-state-gated rows must stay explicit deferred tripwires. - `.pi/extensions/brunch-data/context/` — for direct workspace overview reads; pure cwd inventory comes from `workspace/`. - `projections/session/` — for reusable transcript-context DTO projection. - `projections/workspace/` — for reusable workspace-state DTO projection. -- `renderers/session/` — for reusable transcript markdown rendering. +- `transcript-markdown.ts` — for debug transcript markdown rendering beside the session transcript utilities. - `agents/contexts/workspace/` — for workspace inventory / overview agent-context text over source session read shapes. - `rpc/` — for session.* and workspace.* RPC handlers. - `.pi/extensions/` — for session lifecycle hooks. @@ -164,6 +164,7 @@ These files migrated here on 2026-06-02: | `brunch-session-envelope.ts` | session envelope reader | | `session-projection-reader.ts` | JSONL projection target resolution | | `session-transcript.ts` | transcript row projection | +| `transcript-markdown.ts` | debug transcript markdown text | | `exchange-projection.ts` | exchange extraction | | `runtime-state.ts` | runtime-state transcript entries | | `structured-exchange.ts` | structured exchange schemas/types | diff --git a/src/session/__tests__/transcript-markdown.test.ts b/src/session/__tests__/transcript-markdown.test.ts index 2d53521c3..665bedfb5 100644 --- a/src/session/__tests__/transcript-markdown.test.ts +++ b/src/session/__tests__/transcript-markdown.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest'; import type { ProjectedTranscriptContext } from '../../projections/session/transcript-context.js'; -import { formatTranscript } from '../../renderers/session/transcript.js'; +import { formatTranscript } from '../transcript-markdown.js'; describe('debug transcript markdown', () => { it('renders projected transcript messages without non-text assistant blocks', () => { diff --git a/src/session/session-transcript.ts b/src/session/session-transcript.ts index 221ef789d..b150e3003 100644 --- a/src/session/session-transcript.ts +++ b/src/session/session-transcript.ts @@ -5,7 +5,7 @@ import { fileURLToPath } from 'node:url'; import type { FileEntry } from '@earendil-works/pi-coding-agent'; import { projectTranscriptContext } from '../projections/session/transcript-context.js'; -import { formatTranscript } from '../renderers/session/transcript.js'; +import { formatTranscript } from './transcript-markdown.js'; type TranscriptEntry = FileEntry; diff --git a/src/renderers/session/transcript.ts b/src/session/transcript-markdown.ts similarity index 95% rename from src/renderers/session/transcript.ts rename to src/session/transcript-markdown.ts index 9990094e6..3b55115a3 100644 --- a/src/renderers/session/transcript.ts +++ b/src/session/transcript-markdown.ts @@ -21,7 +21,7 @@ import type { UserMessage, } from '@earendil-works/pi-ai'; -import type { ProjectedTranscriptContext } from '../../projections/session/transcript-context.js'; +import type { ProjectedTranscriptContext } from '../projections/session/transcript-context.js'; export function formatTranscript( context: ProjectedTranscriptContext, diff --git a/src/treedocs.yaml b/src/treedocs.yaml index e13338b51..0a3a45922 100644 --- a/src/treedocs.yaml +++ b/src/treedocs.yaml @@ -393,8 +393,6 @@ tree: workspace-state.ts: 'Implements workspace state.' renderers: README.md: 'Documents this source subtree.' - session: - transcript.ts: 'Implements transcript.' rpc: README.md: 'Documents this source subtree.' handlers.ts: 'Implements handlers.' @@ -440,6 +438,7 @@ tree: pending-exchange.ts: 'Implements pending exchange.' synthetic-tool-call.ts: 'Implements synthetic tool call.' structured-exchange-loop.ts: 'Implements structured exchange loop.' + transcript-markdown.ts: 'Implements transcript markdown.' workspace-overview-context.ts: 'Implements workspace overview context.' workspace-session-coordinator: canonical-session-files.ts: 'Implements canonical session files.' From 1071d66fd9db053ed68f8b14281873f12578da31 Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Fri, 26 Jun 2026 09:29:43 +0200 Subject: [PATCH 26/54] Retire renderers topology --- .oxlintrc.json | 17 ---------- memory/PLAN.md | 8 ++--- memory/REFACTOR.md | 2 +- memory/SPEC.md | 14 ++++---- package.json | 6 ++-- src/.pi/README.md | 2 +- src/.pi/components/README.md | 2 +- src/.pi/extensions/README.md | 3 +- src/README.md | 9 ++---- src/agents/contexts/README.md | 3 +- src/agents/prompts/README.md | 2 +- src/app/README.md | 2 +- .../__tests__/topology-boundaries.test.ts | 2 +- src/renderers/README.md | 32 ------------------- src/scripts/README.md | 2 +- src/treedocs.yaml | 2 -- src/workspace/README.md | 2 +- 17 files changed, 27 insertions(+), 83 deletions(-) delete mode 100644 src/renderers/README.md diff --git a/.oxlintrc.json b/.oxlintrc.json index 9914dc9d9..aabbe15d7 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -25,22 +25,6 @@ ] } }, - { - "files": ["src/renderers/**/*.ts", "src/renderers/**/*.tsx"], - "rules": { - "no-restricted-imports": [ - "error", - { - "patterns": [ - { - "group": ["**/.pi/**", "**/app/**", "**/rpc/**", "**/web/**", "**/db/**"], - "message": "D52-L: renderers/ must not import adapter, transport, or db layers." - } - ] - } - ] - } - }, { "files": ["src/workspace/**/*.ts", "src/workspace/**/*.tsx"], "rules": { @@ -55,7 +39,6 @@ "**/db/**", "**/graph/**", "**/projections/**", - "**/renderers/**", "**/rpc/**", "**/session/**", "**/web/**" diff --git a/memory/PLAN.md b/memory/PLAN.md index 417d3b74e..69c5a2b15 100644 --- a/memory/PLAN.md +++ b/memory/PLAN.md @@ -56,7 +56,7 @@ Brunch-next has delivered the original composition spine: the host, sealed Pi pr context-pipeline/ ├── PULL graph + session reads ✓ done ├── PROJECT projections/ ✓ done -├── RENDER agents/contexts + renderers/ ◐ open: renderer-golden-coverage (FE-870) +├── RENDER agents/contexts + local human outputs ◐ open: renderer-golden-coverage (FE-870) └── COMPOSE system-prompts + skills ✓ done* *COMPOSE has one deferred full-stack real-rendered-context tripwire owned by RENDER. @@ -83,7 +83,7 @@ context-pipeline/ - `orchestrator-tool-port` (FE-1087) — **scoped.** Port the external `brunch cook` orchestrator into execute-mode tools without granting the foreground orchestrator direct shell/file-write authority. First active scope: `memory/cards/orchestrator-tool-port--plan-check-tool.md`. - `elicitor-project` (FE-1085) — **design-gated.** Cross-plane derivation (requirements -> design, design -> oracles) remains undesigned under A33-L; run `ln-design` before any scope/build. - `data-model-legibility` — **active.** Single canonical home for data-model meta-guidance, with closed-vocabulary tables generated from the typed `graph/schema` sources (D97-L). Design verdict landed (Shape C); first tracer landed (generated kind→band table + `check:data-model` drift guard, cited by `methods/capture`). Remaining: edge-category + detail-form tables, the authored judgment layer, and the subtypes→`detail` remodel. -- `renderer-golden-coverage` — **active parallel coverage track.** Remaining RENDER work lives by audience: model-facing context surfaces under `agents/contexts/`, human/product text under `renderers/`. Remaining rows need fresh scoping against `src/agents/contexts/README.md` and `src/renderers/README.md`. +- `renderer-golden-coverage` — **active parallel coverage track.** Remaining RENDER work lives by audience: model-facing context surfaces under `agents/contexts/`, human/product text beside its app/session owner. Remaining rows need fresh scoping against `src/agents/contexts/README.md`, `src/app/README.md`, and `src/session/README.md`. - `exchange-symmetry-audit` — **earned cleanup.** Delete-oriented audit of the exchange projection/renderer split; not a capability blocker. ### Parallel / Low-Conflict @@ -164,8 +164,8 @@ context-pipeline/ - **Branch:** `ln/fe-870-renderer-golden-context-tools` - **Kind:** coverage + build / hardening - **Status:** next / active parallel. Substrate, ``, ``, graph overview/neighborhood renders, and band-filtered graph slice hardening are done. Remaining work needs a fresh `ln-scope` pass. -- **Objective:** Finish the RENDER stage: ``, `renderGraphSeed`, `exchanges/*`, `formatRelatedNodesResult` structural-leak repair + relocation into `renderers/`, and the `brunch print` house-style-vs-status fork. -- **Acceptance:** `src/agents/contexts/README.md` and `src/renderers/README.md` carry the audience split; required model-facing rows are built in the house style and locked with focused goldens/semantic invariants; no adapter/transport imports enter `agents/contexts/` or `renderers/`. +- **Objective:** Finish the RENDER stage: ``, `renderGraphSeed`, `exchanges/*`, `formatRelatedNodesResult` structural-leak repair, and the `brunch print` house-style-vs-status fork. +- **Acceptance:** `src/agents/contexts/README.md`, `src/app/README.md`, and `src/session/README.md` carry the audience split; required model-facing rows are built in the house style and locked with focused goldens/semantic invariants; no adapter/transport imports enter `agents/contexts/`. - **Traceability:** D19-L, D52-L, D60-L, D62-L, D83-L. ### exchange-symmetry-audit diff --git a/memory/REFACTOR.md b/memory/REFACTOR.md index 60e1b1328..9cbd4a61c 100644 --- a/memory/REFACTOR.md +++ b/memory/REFACTOR.md @@ -55,7 +55,7 @@ The key judo move is deletion, not another rearrangement: remove the temporary p 4. [x] Repair `read_graph related` by making it share the semantic relation vocabulary already used by neighborhood rendering, or by deleting the separate related formatter in favor of a filtered-neighborhood render. Add a focused golden/invariant for the related mode so raw category/direction arrows and raw fallback ids do not silently return. 5. [x] Move print-mode workspace-state text out of `src/renderers/` to the app/print owner, update `brunch --mode print` imports, and delete the old workspace renderer file/directory. 6. [x] Move debug transcript markdown out of `src/renderers/` to the session owner, update `session-transcript.ts` imports, and delete the old session renderer file/directory. -7. [ ] Delete `src/renderers/README.md` and the now-empty `src/renderers/` topology. Update root/topology READMEs, SPEC/PLAN references, and lint/import-boundary comments to remove `renderers/` as a live source layer. +7. [x] Delete `src/renderers/README.md` and the now-empty `src/renderers/` topology. Update root/topology READMEs, SPEC/PLAN references, and lint/import-boundary comments to remove `renderers/` as a live source layer. 8. [ ] Reconcile stale path fossils found during the move: old `.pi/extensions/system-prompts`, `.pi/extensions/graph`, `../web`, and renderers references in co-located READMEs and memory files. ## Decisions diff --git a/memory/SPEC.md b/memory/SPEC.md index 49a34b86b..5cf838027 100644 --- a/memory/SPEC.md +++ b/memory/SPEC.md @@ -133,7 +133,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c - **D40-L — Runtime state is transcript-backed Brunch session-agent state, not hidden extension memory.** The architectural commitment is that Brunch session-agent posture remains transcript-backed Pi JSONL state rather than hidden extension memory: posture switches are user/system authority, the foreground session agent is derived from `op_mode`, child axes default back to `AUTO` when invalidated, and tool authority remains `op_mode`-gated rather than prompt-composition-owned. Runtime-state entries are Pi JSONL state-change facts, not assistant/user chat content: init and switch entries should render, when visible, as dim non-chat state rows analogous to Pi thinking/model-change rows, and must not enter LLM context as ordinary conversation. Current materialized state lives in [`src/session/README.md`](src/session/README.md), [`src/projections/README.md`](src/projections/README.md), [`src/.pi/README.md`](src/.pi/README.md), [`src/agents/README.md`](src/agents/README.md), [`src/agents/prompts/README.md`](src/agents/prompts/README.md), and [`src/agents/runtime/README.md`](src/agents/runtime/README.md). Depends on: D17-L, D23-L, D25-L, D39-L, D58-L, D59-L. Refined by: D85-L (the AUTO-able objective axes reduce to `strategy` + `lens`; `goal` is dropped from the runtime-state axis set and inlined into the agent role prompt). Supersedes: mode-only vocabulary, extension-local mutable state as authority, storing the foreground role as independent session state, the "runtime bundle / role preset" as one knob deriving model/thinking/resources, and binding prompt-resource location to `src/.pi/context/`. - **D34-L — Command containment separates visibility suppression from effect blocking.** Current Pi extension seams can hide unsupported slash suggestions with autocomplete wrapping and can cancel branch/session effects through lifecycle hooks, but they cannot strictly suppress exact interactive built-in commands before `InteractiveMode` dispatches them. Brunch-owned commands must use product-specific names and route writes through Brunch handlers/`CommandExecutor`; extension command collisions are not an override mechanism. Strict built-in command/keybinding policy is a Pi upstream/API ask, while POC safety relies on hiding generic affordances, blocking dangerous effects (`/fork`, `/clone`, raw session replacement), allowing native `/tree` as inspection/navigation, and failing fast on branched transcripts. Brunch's command-policy code should live in `src/.pi/extensions/commands/policy.ts`, merging branch/session-effect blocking with any product command allow/deny behavior instead of preserving a branch-only module. Depends on: D2-L, D24-L, A18-L. Supersedes: treating extension `input` handlers or command-name collisions as built-in command allowlisting. - **D35-L — Dynamic TUI chrome is a Brunch projection wrapper over Pi UI primitives.** The architectural commitment is that downstream TUI affordances call one Brunch-owned renderer (`renderBrunchChrome` or its successor) with a single activated product-state value rather than scattering raw `ctx.ui.setHeader` / `setFooter` / `setWidget` / title / working-indicator calls; the wrapper is stateless projection over canonical workspace/session/graph facts, never its own mutable state. Chrome is a project-first shell surface with selected-spec context — project name labels the cwd container, spec title labels the selected graph, session label distinguishes transcript instances — and a session label must never replace spec identity or graph truth. Chrome must not consume the status-key namespace for its own summary (`ctx.ui.setStatus` stays a lateral channel for other extensions), must not advertise unwired affordances, and RPC clients must rely only on surfaces Pi actually emits (header/footer/working-indicator are TUI-only in current Pi RPC mode). Current chrome state shape, render surfaces, telemetry/refresh, startup-header behavior, and status-key filtering live in [`src/.pi/extensions/chrome/README.md`](src/.pi/extensions/chrome/README.md); launch/activation wiring lives in [`src/.pi/extensions/README.md`](src/.pi/extensions/README.md). Depends on: D2-L, D21-L, D34-L, A18-L. Supersedes: treating Pi UI methods as direct downstream affordance APIs, rendering placeholder session state such as `unbound` after a session is activated, consuming the status-key namespace for chrome's own static summary, using spec title as the default session label, or allowing two unchanged Brunch-created default names to collide in one cwd, and the earlier resume/open-launches-stay-quiet clause (superseded 2026-06-11: the shipped, test-locked behavior headers every non-cancel activation). -- **D52-L — Source topology targets `src/{app, workspace, scripts, agents, .pi, db, graph, session, projections, renderers, rpc, web}` with directed layer dependencies.** Reusable projection and rendering modules live in top-level `src/projections/` and `src/renderers/` rather than whichever domain or adapter first needed them; `src/agents/` is the Pi-independent owner for Brunch-authored LLM context ingress and foreground runtime policy (currently bundled agent prompt bodies, prompt-resource skills, foreground roster/tool policy, prompt composition, prompt-resource/tool legality, seed context composition, reusable agent-visible renderers, adapter-local tool/session text promoted into contexts, and the central registry for prompt/skill file paths); domain layers (`graph/`, `session/`) and the reusable `projections` / `renderers` / `agents` layers must not import adapters, transports, app entrypoints, or web code; `graph/` is the only layer that imports `db/`, plus the single sanctioned `db/`→`graph/schema/kinds.ts` taxonomy edge (D73-L). The concrete per-directory ownership, layout sketch, and full import matrix are owned by [`src/README.md`](src/README.md). Depends on: D2-L, D4-L, D39-L, D40-L. Refined by: D73-L. Supersedes: scattering session domain files at `src/` root; treating Pi-only agents as a host-independent top-level `src/.pi/` layer; nesting prompt composition under `src/.pi/context/`; treating reusable `project` / `format` helpers as owned by whichever adapter first needed them; treating retired `.pi/agents` / `.pi/skills` locations as the long-term conceptual owner for Brunch-authored model-facing content. +- **D52-L — Source topology targets `src/{app, workspace, scripts, agents, .pi, db, graph, session, projections, rpc, web}` with directed layer dependencies.** Reusable projection modules live in top-level `src/projections/`; human/product text rendering stays beside its app/session owner rather than a shallow shared layer; `src/agents/` is the Pi-independent owner for Brunch-authored LLM context ingress and foreground runtime policy (currently bundled agent prompt bodies, prompt-resource skills, foreground roster/tool policy, prompt composition, prompt-resource/tool legality, seed context composition, reusable agent-visible renderers, adapter-local tool/session text promoted into contexts, and the central registry for prompt/skill file paths); domain layers (`graph/`, `session/`) and the reusable `projections` / `agents` layers must not import adapters, transports, app entrypoints, or web code; `graph/` is the only layer that imports `db/`, plus the single sanctioned `db/`→`graph/schema/kinds.ts` taxonomy edge (D73-L). The concrete per-directory ownership, layout sketch, and full import matrix are owned by [`src/README.md`](src/README.md). Depends on: D2-L, D4-L, D39-L, D40-L. Refined by: D73-L. Supersedes: scattering session domain files at `src/` root; treating Pi-only agents as a host-independent top-level `src/.pi/` layer; nesting prompt composition under `src/.pi/context/`; treating reusable `project` / `format` helpers as owned by whichever adapter first needed them; treating retired `.pi/agents` / `.pi/skills` locations as the long-term conceptual owner for Brunch-authored model-facing content. - **D73-L — Domain enum taxonomy is owned by drizzle-free schema leaves; persistence and adapters are consumers, not the source.** The closed enum `const` arrays that define graph vocabulary — node kinds (`INTENT_KINDS`, `ORACLE_KINDS`, `DESIGN_KINDS`, `PLAN_KINDS`), `NODE_PLANES` (`intent`/`oracle`/`design`/`plan`), `NODE_BASES`, `EDGE_CATEGORIES`, `EDGE_STANCES`, `READINESS_BANDS`, `LENS_AFFINITIES`, `GAP_DISPOSITIONS`, and `GAP_PREDICATE_KINDS` — live in `graph/schema/kinds.ts`, a pure constants leaf that imports nothing (no drizzle, no `graph/atoms`). Both `db/schema.ts` (for `text({ enum })` column constraints, including the previously-inlined `plane` columns) and `graph/` domain modules import the arrays from this leaf; `graph/index.ts` re-exports them from the leaf so non-graph layers still avoid importing `db/` directly (I26-L). Session runtime axis vocabulary mirrors the same ownership direction in `session/schema/kinds.ts`: that leaf imports nothing and owns the `op_mode`, agent-role, `strategy`, `lens`, `auto`, and display-only planned mode choices consumed by `session/runtime-state.ts`, `projections/session/*`, and `agents/runtime/state.ts`; it deliberately contains no `goal` axis and no retired `READINESS_GRADES`. Derivations stay where they are read: `NODE_KIND_METADATA`, `formatGraphNodeCode`, `parseGraphNodeCode`, and `intentKindCategory` remain in `graph/schema/nodes.ts` (D62-L). The motivating defect: because `db/schema.ts` eagerly evaluates `sqliteTable(...)` and `verbatimModuleSyntax` emits even type-only imports at runtime, any value-import path from `web/` into the old taxonomy location pulled Drizzle into the browser bundle. Locating taxonomy in a drizzle-free leaf makes the `web/` build target structurally Drizzle-free (I44-L) and corrects the ownership direction so the domain, not the persistence layer, owns its vocabulary. Vocabulary migration status: `READINESS_GRADES` is retired (readiness is no longer a stored grade, D45-L), `ELICITATION_BACKLOG_STATUSES` is replaced by the `elicitation_gaps` disposition + predicate-shape enums (D65-L), and `READINESS_BANDS` stays. Depends on: D16-L, D52-L, D54-L, D62-L, D63-L, D64-L; I26-L. Supersedes: `db/schema.ts` owning the shared enum `const` arrays and the "enum literals flow outward from `db/schema.ts`" posture; the triplicated inline `['intent','oracle','design','plan']` plane literals. #### Data model & vocabulary @@ -298,9 +298,9 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c - **D80-L — Generalized capture is the elicitor's banded capture sweep: in-turn, synchronous, over the un-swept transcript tail.** Capture is conduct of the foreground elicitor, not product wiring: there is no observer/auditor queue on the primary path (D18-L, reaffirmed — the v1 observer failed on structure-dependence and context starvation), no product-side LLM extraction pass on the submit paths, no gateway/translation/judgment layer between the agent and graph truth, and no capture subagent in the current block. The **banded capture sweep** is one band-ordered pass that walks intent-kind groups (the same typology the `elicitation_gaps` register references via `refersTo: NodeKind`, D65-L/D75-L), committing through the real role-named `mutateGraph` grammar (D53-L/A14-L) and moving gap dispositions through `update_elicitation_gaps`. Its input window is the **un-swept transcript tail** — all conversational and digest content since the last sweep, tracked by a **sweep watermark** (prior art: the I45-L assistant-visible watermark and the own-mutation stamp) — so capture is robust to RPC-submitted messages, interruptions, and multi-message bursts, and probes get a crisp invariant: after any elicitor turn, nothing conversational remains behind the watermark. Default is a single pass; bulk material (pastes, document reads, exploration digests) may engage an **escalation valve** — chunked/iterated sweeping within the same turn — without changing window or watermark semantics. Choreography is **capture-then-ask**: the sweep commits facts and moves gaps *before* the elicitor composes its next question, so the question provably benefits from what was just captured. Consequence: the deterministic labeled-prefix capture core (`graph/capture/structured-response.ts`), its `session.submitMessage`/`session.submitExchangeResponse` wiring, and the `capture-response-to-graph` proof are retired fossils — capture becomes turn-coupled (same agent for RPC transport clients, different moment; no coverage loss). Depends on: A14-L, A22-L, D18-L, D49-L, D53-L, D63-L, D65-L, D66-L; I45-L. Supersedes: submit-time product-side capture (the D66-L "exactly as the structured-response capture tracer does" wiring), the labeled-prefix extraction core, and the capture-quality spike's product-side extraction-pass shape. - **D81-L — Capture commitment gradient: confidence, not directness; low-confidence noticings spawn elicitation gaps.** What the sweep commits is governed by confidence in grounding, not by whether the user uttered the exact words: directly-stated facts commit with `basis: explicit`; confidently-materialized items — including implied edges and structure soundly inferred from stated content — commit with `basis: implicit`, which D63-L already licenses (agent-materialized-from-user-input); low-confidence **noticings** are never committed — the sweep's prompt directs the elicitor to spawn an `elicitation_gap` instead (`basis: implicit`, rationale citing the noticing), so the false-commit guard's positive output *is* the capture-reflection behavior: one prompted discipline discharges both the guard and the gap-writeback obligation, the agenda durably carries what was noticed, and the anti-shadowing line (D65-L) holds because the gap carries question/rationale, never domain content as truth. There is **no structural gate**: the guard is commitment rules in the sweep prompting plus the false-commit scenario matrix re-aimed at the low-confidence line and run at probe tier (some spike implication rows become legitimate implicit commits under the gradient; expected gap-spawns become assertable probe outcomes); CI guards structural legality only at the `CommandExecutor` boundary. Refines: D18-L (low-confidence material now spawns gaps rather than only "folding into later questions"; preface, D47-L, remains the orientation carrier). Depends on: A22-L, D18-L, D47-L, D63-L, D65-L. Supersedes: "implications never become graph truth" as a *directness* rule, and the spike matrix's `shouldCommit` expectations as written. - **D82-L — Ground-material acquisition is a skill-structured layer in front of the sweep; bulk modes interpose a digest; a seeded situating gap routes modes.** Questions and answers are not the only way the graph gains ground material: the elicitor must also accept arbitrary pasted content, read user-referenced workspace documents, and explore-and-characterize a brownfield codebase. These are **acquisition modes** — elicit-by-question, ingest-paste, read-referenced-documents, explore-and-characterize — structured as Brunch prompt-resource skills (D58-L manifest world), each a distinct competence the elicitor reaches for; `read-referenced-documents` and `explore-and-characterize` may use Brunch-owned static `web_fetch`/`web_search` tools registered in the sealed Pi profile (D39-L/D40-L). Acquisition varies, capture stays uniform (`acquire → digest → sweep`): everything acquired lands in the transcript behind the sweep watermark. Bulk modes (exploration, research, large document reads) interpose a **digest** — an assistant-authored characterization of what was read/found (prior art: the v1 preface-of-exchange-tuple, which proved capture should run over the summary, not the raw bulk; D47-L) — and the sweep captures from digests plus conversational content while raw tool results pass behind the watermark as background. A **situating gap** is seeded at spec creation (orientation anchors: new-from-scratch / brownfield codebase / continuation of a prior thread — the grounding-advance anchors promoted from skill prose to agenda), so the opening elicitation itself routes the session into the right acquisition mode; the gap's discharge is what licenses, e.g., explore-and-characterize. Near-future direction (not current block): exploration/research acquisition delegated to **subagents** with the digest as the handback artifact — clean main-elicitor context without observer starvation, because the subagent owns its exploration context and returns only the digest. Depends on: D47-L, D57-L, D58-L, D65-L, D80-L. Supersedes: treating conversational answers as the only capture source. -- **D60-L — Agent context splits into pull / projection / render / surface, distinguishes graph-truth from active-context reads, and keeps `workspace.state` separate.** Agent context (what the agent reasons over) spans `cwd` (filesystem kickoff heuristic — `.brunch?`, session count/length, README/markdown sizes, file counts), `graph` (overview/list/query), and `node` (variable-hop neighborhood). The architectural commitment is that agent context is a staged pipeline (pull / projection / render / surface), graph reads must make visibility/projection explicit instead of silently mixing graph-truth with active-context views, the read family must stay a named-shape surface rather than a generic records API, and `workspace.state` remains a separate product-state subject rather than an agent-context alias. Current materialized state lives in [`src/graph/README.md`](src/graph/README.md), [`src/projections/README.md`](src/projections/README.md), [`src/agents/contexts/README.md`](src/agents/contexts/README.md), [`src/renderers/README.md`](src/renderers/README.md), [`src/session/README.md`](src/session/README.md), [`src/graph/queries.ts`](src/graph/queries.ts), [`src/workspace/cwd-inventory.ts`](src/workspace/cwd-inventory.ts), and [`src/session/workspace-overview-context.ts`](src/session/workspace-overview-context.ts). Depends on: D35-L, D52-L, D53-L, D62-L, D64-L. Supersedes: pre-rendering context strings in the pull layer, scattering context-build logic across `graph/`, `.pi/agents/contexts/`, and tool adapters, or silently mixing graph-truth and active-context reads. +- **D60-L — Agent context splits into pull / projection / render / surface, distinguishes graph-truth from active-context reads, and keeps `workspace.state` separate.** Agent context (what the agent reasons over) spans `cwd` (filesystem kickoff heuristic — `.brunch?`, session count/length, README/markdown sizes, file counts), `graph` (overview/list/query), and `node` (variable-hop neighborhood). The architectural commitment is that agent context is a staged pipeline (pull / projection / render / surface), graph reads must make visibility/projection explicit instead of silently mixing graph-truth with active-context views, the read family must stay a named-shape surface rather than a generic records API, and `workspace.state` remains a separate product-state subject rather than an agent-context alias. Current materialized state lives in [`src/graph/README.md`](src/graph/README.md), [`src/projections/README.md`](src/projections/README.md), [`src/agents/contexts/README.md`](src/agents/contexts/README.md), [`src/app/README.md`](src/app/README.md), [`src/session/README.md`](src/session/README.md), [`src/graph/queries.ts`](src/graph/queries.ts), [`src/workspace/cwd-inventory.ts`](src/workspace/cwd-inventory.ts), and [`src/session/workspace-overview-context.ts`](src/session/workspace-overview-context.ts). Depends on: D35-L, D52-L, D53-L, D62-L, D64-L. Supersedes: pre-rendering context strings in the pull layer, scattering context-build logic across `graph/`, `.pi/agents/contexts/`, and tool adapters, or silently mixing graph-truth and active-context reads. - **D83-L — Context-render house style: a markdown frame (md-pen) with TOON for uniform data and a fenced ASCII tree for hierarchy, wrapped in `
` tags; agent context clusters into `` / `` / `` scopes.** Refines D60-L's RENDER stage. LLM-facing agent-context renders adopt one consistent dialect instead of ad-hoc `[bracket]` + bullet lists: - - **Audience scope.** `src/agents/contexts/` owns the LLM agent-context dialect (the `
` scope-clustering plus TOON data blocks and the documents tree). `src/renderers/` now remains for human/product-only text such as print-mode `workspace.state` and debug transcript output. A golden lock is audience-agnostic — it pins any stable text-output contract, human or LLM. (Open: whether `brunch print` should eventually render the house-style human views rather than a separate terse status dump — an `ln-plan` call, not assumed here.) + - **Audience scope.** `src/agents/contexts/` owns the LLM agent-context dialect (the `
` scope-clustering plus TOON data blocks and the documents tree). human/product-only text such as print-mode `workspace.state` and debug transcript output now lives beside its app/session owner. A golden lock is audience-agnostic — it pins any stable text-output contract, human or LLM. (Open: whether `brunch print` should eventually render the house-style human views rather than a separate terse status dump — an `ln-plan` call, not assumed here.) - **Markdown frame** — built with **md-pen** (zero-dependency, GFM, CommonMark-audited), through the wrapper seam at `src/agents/contexts/primitives/markdown.ts`: headings, sections, prose, blockquote notes, inline code, fenced blocks. Retires hand-rolled markdown string concatenation. - **Uniform record sets — format by *size and legibility*, not uniformity alone.** *Large or unbounded* sets (ranked elicitation gaps, long session lists, mentions) render as **TOON** (`@toon-format/toon`, the `src/agents/contexts/primitives/toon.ts` wrapper seam): token-efficient, lossless, with `[N]`-length + `{fields}`-header guardrails that improve model parse-reliability. *Small bounded* rosters (e.g. the workspace spec roster), positional/relational sets where table columns carry the reading contract (e.g. graph overview node and edge tables), and any human-facing render use **markdown tables** for stronger read-comprehension. The TOON token advantage concentrates on large arrays, so a short table gains little from it (per the TOON “keep examples small” guidance); for very large graph overviews, TOON is a future compact variant rather than the default overview contract. - **Hierarchy / file trees** (the documents surface) — a pure-JS ASCII-tree renderer (**stringify-tree** chosen; archy is the equivalent alternative) fed by Brunch's existing gitignore-aware filesystem walk (`workspace/cwd-inventory.ts`) and embedded in a fenced ` ```tree ` block. **Not** the system `tree` binary: Brunch already owns the walk, and a system executable is undistributable for an `npm`-installed CLI and non-deterministic/untestable inside a context path; the binary's only added value (the walk) is already ours, so we depend on a renderer, not a binary. @@ -309,7 +309,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c - **Scope clustering** — the D60-L “agent context” subjects are regrouped along the `workspace → spec → session` hierarchy (D19-L): **``** (cwd scope) carries project identity, the documents tree, and the spec roster, and **carries no sessions**; **``** (selected-spec scope) carries the spec header/readiness, graph overview, anchored neighborhood, ranked elicitation gaps, the spec's **sessions** (a session binds to exactly one spec, D19-L), and future reconciliation needs; **``** (live-session scope) carries the runtime-posture frame, mentions, the world-update watermark, lifecycle, and recent transcript. The soft readiness line is computed over the selected spec's full elicitation-gap register, while the `Gaps` block renders only the ask-eligible ranked agenda; seed and `` context share the same renderer so their readiness numbers cannot diverge by caller-chosen population. - **Lexicon** — these LLM-facing context renders are distinct from the `workspace.state` product-state projection, which D60-L keeps for print/RPC/UI status; the `` context render is not `workspace.state`, and `renderWorkspaceState` stays the product-state renderer. (The earlier `` tag sketch is renamed `` to avoid the collision.) - **Dependencies** — three small leaf libraries (md-pen, `@toon-format/toon`, stringify-tree), each *retiring owned format-generation code* (the md-pen/TOON wrapper seams are already stubbed; the tree library replaces a hand-built formatter), so net owned surface decreases — the trade that justifies them under a dependencies-resist posture. - - **Rollout** — incremental: ``, ``, graph, session runtime-frame, and structured-exchange result renders now live under `src/agents/contexts/`; transcript debug/report rendering remains in `src/renderers/session/transcript.ts` until it becomes deliberate agent context. + - **Rollout** — incremental: ``, ``, graph, session runtime-frame, and structured-exchange result renders now live under `src/agents/contexts/`; transcript debug/report rendering lives in `src/session/transcript-markdown.ts` as a human/product debug artifact. - **Closed audit** — per-session `turnCount` is derived once while inspecting canonical session files and counts only current Pi v3 JSONL message entries (`type: "message"` with `message.role: "user" | "assistant"`); tool/custom entries are excluded, and downstream workspace/specification overview renders reuse that inspected count rather than reparsing the file. - **D85-L — Brunch prompt-resource axis model: two AUTO axes (`strategy`, `lens`); `goal` inlined into the agent role; graph-write mechanism is method-routed, not a strategy.** A 2026-06-18 grill consolidation of the `agents/skills/` topology and the D58-L manifest axes, implemented across FE-893, FE-861, and FE-898. Four locked moves: 1. **Two AUTO objective axes, not three.** The runtime manifest advertises only `strategy` and `lens`; **`goal` is dropped as a manifest/runtime axis**. The four goal postures (`grounding-advance`, `elicit-expand`, `commit-converge`, always-on `capture-posture`) **inline into the `elicitor` agent role prompt** (`src/agents/prompts/elicitor/SYSTEM.md`), selected inline by the agent from the pushed readiness-band/posture context. Rationale: `goal` was already internal/readiness-derived and not user-mutable (D59-L), so advertising it as an AUTO-selectable axis was indirection over what is agent-directed-by-band anyway. Consequences for the build: `compose.ts` drops the `` family, `manifestsForState` drops `goals`, `runtime-state.ts` / `agents/runtime/policy.ts` / `affordances.ts` drop the `goal` axis slot, and the runtime header drops the goal line. Capability-readiness (D74-L) is unaffected — it keys on gaps, not goal. @@ -319,7 +319,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c Prompt-shape closure (revised 2026-06-22): (a) **`SKILL.md` directory topology is adopted for every strategy/lens/method** because the Agent Skills standard is now the target prompt-resource format; `references/` remains deferred until a concrete skill needs progressive disclosure, and D39-L's code-owned path list remains the availability surface. (b) **`src/agents/prompts//SYSTEM.md`** is adopted for live and named future bodies; no flat agent-body shape remains open. (c) **`[sub]` sub-agent notation** is deferred until the first real delegated sub-agent lands; no empty sub-agent stubs are introduced. (d) **`_generated/` typed-vocab references** are **materialized** (first instance: the kind→band table at `src/graph/schema/_generated/ontology.md`, generated by `npm run generate:ontology` from the typed `graph/schema` sources and drift-checked by `npm run check:data-model`, wired into `npm run check`); they are read-only projections locked separately from authored prompt-resource bodies, and prompt resources cite them rather than restating vocabulary (the data-model-legibility frontier owns the expansion to further tables + the authored judgment layer). Current state: [`src/agents/skills/README.md`](src/agents/skills/README.md). Resolved 2026-06-18: the capture home is `methods/capture`, absorbing the former `infer-and-capture` method name; the full D80/D81/D82 conduct body remains FE-861. Depends on: D23-L, D25-L, D26-L, D39-L, D40-L, D58-L, D59-L, D65-L, D73-L, D80-L, D81-L. Refines: D25-L, D26-L, D40-L, D58-L, D59-L. Supersedes: `goal` as an AUTO-able manifest/runtime axis (the "objective axes `strategy`, `lens`, and `goal`" triple in D40-L/D58-L/D59-L → two axes, goal inlined); `propose-graph` / `project-graph` as `strategy`-axis members (D25-L/D26-L list them as strategies); treating gap spawn/close as a `review-for-gaps` method responsibility; `infer-and-capture` as a separate method; treating the `capture_*` / candidate / `tool_meta` prompt-resource references as removable fossils; and the 2026-06-19 deferral of Agent Skills `SKILL.md` topology. - Depends on: D19-L, D52-L, D60-L, D62-L, D65-L, D75-L. Refines: D60-L (RENDER stage). Supersedes: the ad-hoc `[bracket]`-header + bullet-list render style as the house convention; hand-rolled markdown and tree string generation in renderers; carrying sessions in the `` cwd render. + Depends on: D19-L, D52-L, D60-L, D62-L, D65-L, D75-L. Refines: D60-L (RENDER stage). Supersedes: the ad-hoc `[bracket]`-header + bullet-list render style as the house convention; hand-rolled markdown and tree string generation in the old renderer layer; carrying sessions in the `` cwd render. - **D95-L — Elicitor capability spine: `capture` / `generate` / `project` are the three elicitor capabilities, named over the existing skill axes — not a fourth axis or a re-axis.** The elicitor's work decomposes into three capabilities by what each does to the graph: **capture** commits ground material already present in the transcript tail into graph truth (the D80-L banded sweep + D81-L commitment gradient + D82-L acquisition layer, already specced); **generate** proposes new typed graph expressions on a requested plane from grounding plus a lens frame, fanning candidates out and committing the chosen one through review (D96-L); **project** derives nodes on one plane from a subset/plane of the existing graph with connecting cross-plane edges (e.g. requirements→design, design→oracles, A33-L). This is a capability vocabulary layered over D85-L's `strategy` / `lens` / `method` axes (held frozen, A35-L), not a replacement topology: capture is always-on conduct of every elicitor turn (D85-L move 3), while generate and project are method-routed capabilities requested just-in-time and gated only advisorily by capability-readiness (D74-L/D86-L). Background acquisition subagents (D82-L near-future, A34-L) are the `acquire` arm feeding capture, not a fourth capability. Depends on: D74-L, D80-L, D81-L, D82-L, D85-L, D86-L. Supersedes: the proposed `grounding` / `elicitation` / `projection` lifecycle directories as a replacement skill topology. - **D96-L — `generate` is one deep plane-parameterized skill; fan-in is a three-value mode carried by `present_candidates` + the review-set path, not three skills.** Generative proposal across the intent, design, and oracle planes is **one** `generate` skill taking the target plane (and lens frame) as a parameter, not per-plane `propose-scenarios` / `propose-design-shapes` / `propose-oracle-ensembles` skills (the earlier per-plane sketch in [`docs/design/ELICITATION_LENSES.md`](docs/design/ELICITATION_LENSES.md)). The fan-out/fan-in shape is shared: the skill fans candidate expressions out, then **fan-in is a three-value mode** — `pick` (choose one), `synthesize` (merge candidates into one), `compose` (accept several) — expressed as plane-keyed method conduct over `present_candidates` plus the review-set path rather than branched per plane. Plane-specific judgment (the "design it twice" pattern for design, oracle-family selection for oracles, the kernel/lens heuristics in [`docs/design/BEHAVIORAL_KERNELS.md`](docs/design/BEHAVIORAL_KERNELS.md) / [`docs/design/ELICITATION_LENSES.md`](docs/design/ELICITATION_LENSES.md) for intent) lives in plane-keyed skill content read on demand (D58-L manifest world), not in separate skills. This **entailed un-stubbing the `present_candidates` topology** — the tool [`src/.pi/extensions/exchanges/present-candidates.ts`](src/.pi/extensions/exchanges/present-candidates.ts), the projection [`src/projections/exchanges/present-candidates.ts`](src/projections/exchanges/present-candidates.ts), and the renderer [`src/agents/contexts/exchanges/present-candidates.ts`](src/agents/contexts/exchanges/present-candidates.ts) — which D85-L move 4 confirmed as a live anticipated stub, not a fossil: candidate presentation gets its product owner here. The materialized tool remains **pick-only at the UI boundary**: intent uses the pick as recognition/provenance, while design-plane synthesize is performed by the method after the pick and then reviewed/committed through `present_review_set → request_response → acceptReviewSet`, so no `fan_in_mode` field is needed unless a later plane proves the UI itself must carry that mode. Commitment still flows through the review-set path (D27-L); `present_candidates` recognizes fan-out presentation and never commits graph truth itself (I51-L). Depends on: D26-L, D27-L, D30-L, D31-L, D58-L, D74-L, D85-L, D95-L; A31-L, A32-L. Supersedes: per-plane generative skills as the topology; treating `present_candidates` as a permanent stub without a product owner; prebuilding a fan-in schema field before a plane proves it necessary. - **D97-L — Skill ontology-heuristic provenance: three sources — consumed renderers, generated typed-vocab, hand-authored judgment — kept distinct.** Skill bodies that teach the agent how to think about the graph model draw ontology/heuristic content from three provenance classes that must not blur: (1) **dynamic instance context** rendered into the prompt by the context-render house style (D83-L, FE-870) — graph overviews, gap agendas, neighborhoods — consumed, never restated in skill prose; (2) **generated typed-vocab references** (`_generated/`, D85-L prompt-shape closure (d)) projected from the closed `kinds.ts` enums (D73-L) and drift-checked, for any skill that must enumerate node kinds / edge categories / bands / planes; and (3) **hand-authored judgment** — the irreducible "how to reason" content (kernels, lenses, oracle-family selection) that is neither instance data nor mechanical vocabulary. The rule: a skill cites the renderer or the generated reference rather than copying its content, so ontology drift (D73-L renames, D94-L band changes) propagates through one canonical source. Depends on: D58-L, D73-L, D83-L, D85-L, D94-L; FE-870. Supersedes: hand-restating node-kind / band / edge-category / plane vocabulary inside skill bodies. @@ -509,7 +509,7 @@ src/.pi/ | **Prompt-resource manifest** | The small per-turn D58-L `` block injected into the system prompt, listing only runtime-legal Brunch resources with `kind`, `name`, `description`, and `location`. The legal set and locations are code-owned in `agents/runtime/state.ts` (not filesystem-discovered); `name` and `description` are file-owned `SKILL.md` frontmatter read via pi's loader over the explicit path list. The `system-prompts/seed/` and `.pi/extensions/brunch-data/context/` context renderers are not manifest resources. It mirrors Pi's skill-list element structure but is filtered by Brunch runtime state, capability-readiness, and allow-lists. | | **Method** | A tool-usage or workflow competence advertised as a Brunch prompt resource (`agents/skills/methods//SKILL.md`): run structured exchanges, capture (the D80/D81/D82 home; full conduct in FE-861), generate proposals/projections, read context, mutate the graph, review for gaps. Method resources explain when to use a tool family and how to sequence it with other tools; executable tool definitions should stay focused on schemas, authority, and runtime behavior. A method may also be backed by a Pi-native skill, but actual tool authority remains code-owned through `op_mode` policy and active-tool gating. `capability` is retired as a synonym — use `method` and `method` in ``. | | **Agent context** | The content the agent reasons over — `cwd`, `graph`, or `node` (D60-L): pulled (typed, read-only) from `graph/`/`session/`, optionally projected when a reusable DTO helps, rendered to LLM-string or JSON, surfaced pushed (compose) or pulled (`read_graph` / `read_workspace_context` / `read_session_context`). Graph context explicitly chooses graph-truth vs active-context reads and may filter by node kind, readiness band, edge category/direction, or absence of an edge category (gap query). Distinct from the **workspace projection** (`workspace.state`), which is product/UI state, not agent content. | -| **Context-render house style** | The RENDER-stage convention (D83-L) for LLM-facing agent context: a markdown frame (md-pen) with uniform record sets as TOON (`@toon-format/toon`) and file hierarchy as a fenced ASCII tree (stringify-tree over Brunch's gitignore-aware walk), each top-level block wrapped in an XML-style `
` tag. Format follows reader legibility, not internal shape (prose where structure misleads). Agent context clusters into three scopes mirroring `workspace → spec → session` (D19-L): `` (project / documents / spec-roster, no sessions), `` (spec header / graph / ranked gaps / sessions), `` (runtime posture / mentions / transcript). It is the agent-context *dialect* within `renderers/`; the md-pen substrate is shared with human-facing renders (print/evidence), which do not use the `
` clustering. Distinct from the `workspace.state` product-state projection (D60-L). | +| **Context-render house style** | The RENDER-stage convention (D83-L) for LLM-facing agent context: a markdown frame (md-pen) with uniform record sets as TOON (`@toon-format/toon`) and file hierarchy as a fenced ASCII tree (stringify-tree over Brunch's gitignore-aware walk), each top-level block wrapped in an XML-style `
` tag. Format follows reader legibility, not internal shape (prose where structure misleads). Agent context clusters into three scopes mirroring `workspace → spec → session` (D19-L): `` (project / documents / spec-roster, no sessions), `` (spec header / graph / ranked gaps / sessions), `` (runtime posture / mentions / transcript). It is the agent-context dialect within `agents/contexts/`; human-facing renders (print/evidence/debug) are local and do not use the `
` clustering. Distinct from the `workspace.state` product-state projection (D60-L). | | **Readiness estimate** | A soft, derived, live per-band coverage projection over `elicitation_gaps`, for UI surfacing only (D45-L). It is *not* stored, *not* authority, and gates nothing — it may regress honestly. Replaces the retired stored `readiness_grade`. | | **Capability-readiness** | The only readiness gate (D74-L): a just-in-time, capability-relative judgment made when a capability is requested, evaluated over the `elicitation_gaps` declared relevant to it. Structural gaps are checked mechanically; `manual` gaps consume an LLM satisficiency judgment (D57-L). Outcome: proceed / proceed-at-low-epistemic-status / negotiate. Never bars attempting work. | | **Readiness grade** *(retired)* | Formerly a spec-row forward-gate scalar (`grounding_onboarding | …`). Retired (D45-L): it conflated gate, display, and milestone. Superseded by capability-readiness (gate), readiness estimate (display), and a deferred milestone gate. | @@ -718,7 +718,7 @@ Dev-loop artifacts route to gitignored `.fixtures/scratch///`, res | Middle | Round-trip tests | JSONL reload, linear transcript validation, session exchange projection, compaction, graph export/import, command result serialization, `supersedes`-chain reconstruction across regeneration. | D6-L, D13-L, D24-L, D28-L; I3-L, I8-L, I10-L, I19-L. | | Middle | Property-based / model-based tests | Spec-local LSN monotonicity, change-log replay, reconciliation-need invariants, stable kind-ordinal allocation/no-reuse, mention staleness, interest-set recomputation, side-task delivery ordering, **batch-acceptance atomicity (one selected-spec LSN / one change-log entry, partial-batch impossible under mid-batch validation failure)**, **`supersedes` / `supersession` acyclicity and unique-leaf-per-thread**, **lens-routing correctness (generated elicitor entries route to the right consumer)**, **reviewer-finding turn-boundary delivery ordering**. | A8-L, A11-L (and validated A4-L/A9-L); I1-L, I4-L, I5-L, I6-L, I9-L, I12-L, I15-L, I16-L, I18-L, I39-L, I41-L. | | Middle | Contract tests | Named RPC method families and transport adapters share handler semantics; `rpc.discover` describes public methods with usable schemas/examples; `session.triggerExchange` / `session.pendingExchange` / `session.submitExchangeResponse` / `session.exchanges` preserve transcript truth; subscriptions deliver initial state payload plus ordered updates; `CommandExecutor` hides policy/transaction details; `acceptReviewSet` returns expected structured discriminants; only prevalidated proposals become reviewable review sets. | D5-L, D19-L, D20-L, D27-L, D48-L, D49-L; R11, R12, R27, R28. | -| Middle | Architectural boundary tests | No direct ORM/SQLite mutation outside `CommandExecutor`; no canonical chat/turn store; TUI/RPC/fixture code does not write `brunch.session_binding`; spec/session picker UI returns decisions rather than opening/mutating sessions; RPC/headless boot exposes structured initial-selection state instead of invoking TUI picker code; Brunch wrappers do not expose Pi branch creation/navigation as product behavior; readiness authority remains gap-derived rather than spec-row or session-local mutable state; reviewer-attributed writes target only `reconciliation_need`; Brunch-launched Pi runtimes do not load ambient `.pi/` resources or behavior-shaping settings outside the Brunch Pi Profile; Brunch product extensions load through the explicit static shell list rather than filesystem discovery or a runtime extension-metadata protocol. Layer *import* boundaries (only `graph/` imports `db/`; `renderers/` and `workspace/` stay isolated from adapter/transport/domain layers) are enforced in the inner loop via oxlint `no-restricted-imports` (`.oxlintrc.json`), not by tests here; the middle-loop tests retain only the non-lintable invariants (write/mutation targets, content greps, and the projection seam guards in `topology-boundaries.test.ts`). | D4-L, D6-L, D18-L, D21-L, D24-L, D29-L, D36-L, D39-L, D45-L, D52-L; I2-L, I10-L, I11-L, I16-L, I19-L, I22-L, I24-L, I26-L, I31-L. | +| Middle | Architectural boundary tests | No direct ORM/SQLite mutation outside `CommandExecutor`; no canonical chat/turn store; TUI/RPC/fixture code does not write `brunch.session_binding`; spec/session picker UI returns decisions rather than opening/mutating sessions; RPC/headless boot exposes structured initial-selection state instead of invoking TUI picker code; Brunch wrappers do not expose Pi branch creation/navigation as product behavior; readiness authority remains gap-derived rather than spec-row or session-local mutable state; reviewer-attributed writes target only `reconciliation_need`; Brunch-launched Pi runtimes do not load ambient `.pi/` resources or behavior-shaping settings outside the Brunch Pi Profile; Brunch product extensions load through the explicit static shell list rather than filesystem discovery or a runtime extension-metadata protocol. Layer *import* boundaries (only `graph/` imports `db/`; `workspace/` stays isolated from adapter/transport/domain layers) are enforced in the inner loop via oxlint `no-restricted-imports` (`.oxlintrc.json`), not by tests here; the middle-loop tests retain only the non-lintable invariants (write/mutation targets, content greps, and the projection seam guards in `topology-boundaries.test.ts`). | D4-L, D6-L, D18-L, D21-L, D24-L, D29-L, D36-L, D39-L, D45-L, D52-L; I2-L, I10-L, I11-L, I16-L, I19-L, I22-L, I24-L, I26-L, I31-L. | | Middle | TUI render-contract integration (VirtualTerminal harness) | A reusable xterm-headless `Terminal` (`src/.pi/__tests__/support/virtual-terminal.ts`) lets in-process vitest drive a real pi-tui `TUI` and assert on the rendered viewport: focus/input routing and overlay/dialog render for `.pi/components`. Paired with the existing fast direct-`render()`/`handleInput()` tests (the two-artifact oracle). Semantic/visible-text asserts, not viewport goldens; fidelity is bounded to xterm's model — real-terminal feel stays outer-loop manual (`demo-polish` walkthrough). | D22-L, D36-L, D52-L. | | Middle | **Differential testing** | Dry-run validation at proposal time matches real-run validation at acceptance time (no drift between modes); free-form-generation vs constrained-generation legality rates (informs whether fallback path is needed per A14-L). | D27-L; A14-L. | | Middle | JSONL replay and property assertions | Probe runs preserve source `session.jsonl` evidence that can be replayed and compared against current Brunch projections. Future brief-driven sessions, if revived, must produce the same JSONL/report artifact shape. For batch proposals/review sets: **structural-legality rate of LLM proposals tracked per-run in probe metadata as POC-phase fitness, not a merge gate**; first-attempt vs retry-with-feedback rates surfaced for human review. | A5-L, A6-L, A7-L, A14-L; I7-L; R20, R21, R22, R23. | diff --git a/package.json b/package.json index b5cb7f0a0..923d556b8 100644 --- a/package.json +++ b/package.json @@ -43,9 +43,9 @@ "db:studio": "drizzle-kit studio", "test": "vitest --run", "test:watch": "vitest", - "test:renderers": "vitest --run src/agents/contexts src/renderers", - "test:renderers:watch": "vitest src/agents/contexts src/renderers", - "test:renderers:update": "vitest --run src/agents/contexts src/renderers --update", + "test:renderers": "vitest --run src/agents/contexts src/app/__tests__/print-workspace-state.test.ts src/session/__tests__/transcript-markdown.test.ts", + "test:renderers:watch": "vitest src/agents/contexts src/app/__tests__/print-workspace-state.test.ts src/session/__tests__/transcript-markdown.test.ts", + "test:renderers:update": "vitest --run src/agents/contexts src/app/__tests__/print-workspace-state.test.ts src/session/__tests__/transcript-markdown.test.ts --update", "test:prompts": "vitest --run src/agents/runtime src/.pi/extensions/agent-runtime/system-prompts", "test:prompts:watch": "vitest src/agents/runtime src/.pi/extensions/agent-runtime/system-prompts", "test:prompts:update": "vitest --run src/agents/runtime src/.pi/extensions/agent-runtime/system-prompts --update", diff --git a/src/.pi/README.md b/src/.pi/README.md index 4f0bacbee..99b4d94eb 100644 --- a/src/.pi/README.md +++ b/src/.pi/README.md @@ -16,7 +16,7 @@ This directory is Brunch's sealed Pi-harness surface. It contains product extens - Product JSON-RPC handlers — `rpc/`. - React client UI — `web/`. - Brunch-authored model-facing prompt/context text — `agents/`. -- Reusable product projection/rendering — `projections/`, `agents/contexts/`, and `renderers/` by audience. +- Reusable product projection/rendering — `projections/`, `agents/contexts/`, and local app/session owners by audience. ## Layout diff --git a/src/.pi/components/README.md b/src/.pi/components/README.md index 8f4b011e5..efa12ef28 100644 --- a/src/.pi/components/README.md +++ b/src/.pi/components/README.md @@ -14,7 +14,7 @@ This directory owns reusable components rendered inside the embedded Pi coding-a - Product wiring, session/runtime state, or graph mutation logic — those live in `session/`, `graph/`, and `.pi/extensions/`. - React/web UI — `web/`. -- Generic project/render projections — `projections/` and `renderers/`. +- Generic projections and model-facing renders — `projections/` and `agents/contexts/`; human/product text stays beside its caller. - A component playground or workbench shell (deferred; see below). ## Layout diff --git a/src/.pi/extensions/README.md b/src/.pi/extensions/README.md index 4b34397c0..1758c9f48 100644 --- a/src/.pi/extensions/README.md +++ b/src/.pi/extensions/README.md @@ -11,7 +11,7 @@ Pi-facing registration and adaptation only: lifecycle hooks, agent tool definiti - Agent role prompt definitions, skill resource bodies, prompt composition, and prompt-resource legality — `agents/`. `agent-runtime/` is now only the Pi hook/tool adapter for that central policy. - Graph truth, graph mutation policy, or graph readers — top-level `graph/`. - Pi JSONL/session semantics, runtime-state projection, workspace coordination, or transcript exchange projection — top-level `session/`, `projections/`, and related domain seams. -- Reusable DTO projection or reusable markdown/text rendering — top-level `projections/`, `agents/contexts/` for model-facing text, and `renderers/` for human/product text. +- Reusable DTO projection or reusable markdown/text rendering — top-level `projections/`, `agents/contexts/` for model-facing text, and local product/session owners for human/product text. - Product transport handlers — `rpc/`, `app/`, and `web/`. ## Directory layout @@ -56,7 +56,6 @@ rules: graph/, session/ x> .pi/ [domain layers never import adapters] agents/prompts/ x> .pi/extensions/ [prompt bodies do not register Pi hooks] projections/ x> .pi/, rpc/, app/, web/ [no transport/UI imports] - renderers/ x> .pi/, rpc/, app/, web/ [no transport/UI imports] ``` ## TUI launch chrome diff --git a/src/README.md b/src/README.md index f59874600..4b0f6bf77 100644 --- a/src/README.md +++ b/src/README.md @@ -30,7 +30,6 @@ src/ │ workspace coordination, session binding, LSN staleness │ ├── projections/ Structured DTOs derived from domain/session/tool facts -├── renderers/ Human/product-only lossy text rendering │ ├── rpc/ Brunch JSON-RPC handlers │ protocol, method handlers, WebSocket adapter @@ -46,14 +45,12 @@ rules: graph/ -> db/ [allowed] workspace/ -> constants/ or workspace-local files only projections/* -> graph/, session/, workspace/ [read/domain imports allowed] - renderers/* -> projections/, session/, workspace/ as needed for human/product input types agents/ -> graph/, projections/, session/, workspace/ [agent-visible text over already-read facts] .pi/ -> agents/, graph/, session/, projections/ [Pi runtime adapters/resources] rpc/ -> graph/, session/, projections/ - app/ -> agents/, graph/, session/, projections/, renderers/ + app/ -> agents/, graph/, session/, projections/ graph/, session/ x> .pi/, rpc/, app/, web/ projections/ x> .pi/, rpc/, app/, web/ - renderers/ x> .pi/, rpc/, app/, web/ web/ -> rpc/ types only ``` @@ -64,7 +61,7 @@ Rules: - `agents/` owns the Brunch-authored LLM-context ingress seam. Today it hosts agent prompt bodies, prompt-resource skills, foreground roster policy, prompt composition, prompt-resource/tool legality, context seed composition, reusable agent-visible context renderers, and the central file registry. - `.pi/` owns Pi-harness extensions/components and no longer hosts Brunch-authored prompt bodies, prompt-resource skills, prompt composition, or provider-visible tool/session text. - `.pi/extensions/` registers Pi tools/hooks/UI affordances and delegates product semantics outward. -- `projections/` owns reusable structured output; `agents/contexts/` owns reusable model-facing text; `renderers/` owns human/product-only lossy text output. +- `projections/` owns reusable structured output; `agents/contexts/` owns reusable model-facing text. Human/product text now lives beside its single product owner (`app/print-workspace-state.ts`, `session/transcript-markdown.ts`) instead of a shallow shared renderer layer. - `web/` is a separate Vite build target. ## Migration notes @@ -73,7 +70,7 @@ Product entrypoints now live in `app/`; package/project identity helpers and `.b The old domain-local `src/{graph,session,structured-exchange}/project/` folders now live under `projections/{graph,session,exchanges}/`. -The old domain-local `src/{graph,session,structured-exchange}/format/` folders and `src/render/` first moved under `renderers/`; reusable model-facing renderers now live under `agents/contexts/`, while `renderers/` retains human/product-only text. +The old domain-local `src/{graph,session,structured-exchange}/format/` folders and `src/render/` first moved under `renderers/`; reusable model-facing renderers now live under `agents/contexts/`, and the shallow human/product renderer layer is retired. Runtime-state transcript entry facts live in `session/runtime-state.ts`; reusable flattened runtime-state projection lives in `projections/session/runtime-state.ts`, while foreground roster/tool policy lives in `agents/runtime/policy.ts`. diff --git a/src/agents/contexts/README.md b/src/agents/contexts/README.md index 010208b57..0f8bc0d52 100644 --- a/src/agents/contexts/README.md +++ b/src/agents/contexts/README.md @@ -26,7 +26,6 @@ rules: .pi/extensions/* -> agents/contexts/ [adapters gather data, then ask for text] session/ -> agents/contexts/seeds/ [origination asks for seed payload text] agents/contexts/ x> .pi/, app/, rpc/, web/ [no host, adapter, or transport effects] - renderers/ x> agents/contexts/ [human/product renderers do not own model text] ``` `src/.pi/__tests__/architecture.test.ts` guards the adapter half of this boundary for `brunch-data` and structured-exchange tools: Pi adapters may own schemas, labels, descriptions, prompt snippets, and TUI rendering, but provider-visible Brunch text must be imported from this subtree rather than formatted inline. @@ -37,4 +36,4 @@ Context golden files live beside their tests under `__snapshots__/` and use stoc ## Migration note -Reusable agent-visible renderers have moved here from `src/renderers/`, and formerly adapter-local model text for graph mutation/related reads plus elicitation/reconciliation register tools now lives here too. `src/renderers/` remains for human/product-only text such as print-mode workspace state and debug transcript output. +Reusable agent-visible renderers have moved here from the retired `src/renderers/` layer, and formerly adapter-local model text for graph mutation/related reads plus elicitation/reconciliation register tools now lives here too. Human/product-only text now lives beside the single owner that emits it (`app/print-workspace-state.ts`, `session/transcript-markdown.ts`). diff --git a/src/agents/prompts/README.md b/src/agents/prompts/README.md index a15371b7e..7a88328c9 100644 --- a/src/agents/prompts/README.md +++ b/src/agents/prompts/README.md @@ -31,7 +31,7 @@ This directory is markdown-only. It carries no TypeScript and registers no Pi ho - Background prompt assembly and injected-world child-session wiring — `src/.pi/extensions/subagents/`. - Strategy/lens/method prompt-resource skills — `src/agents/skills/`. - Reusable model-facing context text — `src/agents/contexts/`. -- Human/product-only text rendering — `src/renderers/`. +- Human/product-only text rendering — owned beside its product/session caller. - Pi tool definitions, lifecycle hooks, UI, and background child-session loading/running — `src/.pi/extensions/*`. ## Migration note diff --git a/src/app/README.md b/src/app/README.md index a8aa44654..70f1607f9 100644 --- a/src/app/README.md +++ b/src/app/README.md @@ -27,4 +27,4 @@ Current entrypoints: ## Dependency direction -`app/` may import from `.pi/`, `agents/`, `graph/`, `session/`, `rpc/`, `projections/`, and `renderers/` to compose product modes. Domain layers must not import `app/`. +`app/` may import from `.pi/`, `agents/`, `graph/`, `session/`, `rpc/`, and `projections/` to compose product modes. Domain layers must not import `app/`. diff --git a/src/projections/__tests__/topology-boundaries.test.ts b/src/projections/__tests__/topology-boundaries.test.ts index bc857bfcb..0c6ff5a84 100644 --- a/src/projections/__tests__/topology-boundaries.test.ts +++ b/src/projections/__tests__/topology-boundaries.test.ts @@ -50,7 +50,7 @@ function sourceImportersOf(target: string): string[] { return sourceFilesUnder(SOURCE_ROOT).filter((file) => importedSourcePaths(file).includes(target)); } -// Layer-wide import boundaries (renderers/, workspace/) are enforced statically +// Layer-wide import boundaries (workspace/) are enforced statically // in `.oxlintrc.json` via no-restricted-imports. The tests below cover the // projection-specific invariants that lint cannot express: the `.pi` schema // carve-out for exchanges, and the two seam guards (neighborhood has no diff --git a/src/renderers/README.md b/src/renderers/README.md deleted file mode 100644 index 82b3764d4..000000000 --- a/src/renderers/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# renderers/ — human/product text rendering - -SPEC decisions: D52-L, D60-L, D83-L - -## Owns - -`src/renderers/` now owns reusable text that is **not** deliberately model-facing. Print-mode workspace-state text moved to the app print owner; this directory only retains debug/report transcript markdown until the session move lands. - -```text -renderers/ -└── session/transcript.ts debug/report transcript markdown -``` - -Agent-visible context text moved to `src/agents/contexts/`: - -- graph overview/neighborhood context -- workspace/specification/session context-tool text -- structured-exchange tool-result text -- markdown/TOON/tree/section context formatting primitives - -## Boundary rules - -```pseudo -rules: - renderers/ -> projections/, session/, workspace/ [human/product input types] - renderers/ x> .pi/, rpc/, app/, web/ [no adapters/transports] - renderers/ x> agents/contexts/ [does not own model-facing text] -``` - -## Migration note - -This directory intentionally no longer carries the context-render house-style ledger. That current agent-context topology lives in `src/agents/contexts/README.md`; `memory/SPEC.md` D83-L owns the decision event. diff --git a/src/scripts/README.md b/src/scripts/README.md index 859b746e5..0ca10939b 100644 --- a/src/scripts/README.md +++ b/src/scripts/README.md @@ -14,7 +14,7 @@ Print-mode workspace-state projection moved to `projections/workspace/`, and its - Durable graph or session semantics. - Product host lifecycle and mode dispatch — `app/`. - Reusable DTO projection — `projections/`. -- Reusable text renderers intended for multiple layers — `renderers/`. +- Model-facing text renderers — `agents/contexts/`; product/session-local human text stays beside its caller. ## Dependency direction `scripts/` may import domain/session types needed to produce utility output. Domain layers, adapters, RPC, and web must not import `scripts/`. diff --git a/src/treedocs.yaml b/src/treedocs.yaml index 0a3a45922..c2427d6b6 100644 --- a/src/treedocs.yaml +++ b/src/treedocs.yaml @@ -391,8 +391,6 @@ tree: transcript-context.ts: 'Implements transcript context.' workspace: workspace-state.ts: 'Implements workspace state.' - renderers: - README.md: 'Documents this source subtree.' rpc: README.md: 'Documents this source subtree.' handlers.ts: 'Implements handlers.' diff --git a/src/workspace/README.md b/src/workspace/README.md index 4bdbda804..a6f82cf81 100644 --- a/src/workspace/README.md +++ b/src/workspace/README.md @@ -21,6 +21,6 @@ Current state: ## Dependency direction -`workspace/` provides cwd/package identity facts to `session/`, `app/`, `projections/`, `rpc/`, and `.pi` as needed. It must not depend on adapters, web code, product entrypoints, Pi, graph/DB modules, reusable projections/renderers, or session transcript mechanics. +`workspace/` provides cwd/package identity facts to `session/`, `app/`, `projections/`, `rpc/`, and `.pi` as needed. It must not depend on adapters, web code, product entrypoints, Pi, graph/DB modules, reusable projections, agent contexts, or session transcript mechanics. `src/projections/topology-boundaries.test.ts` guards this direction: workspace files may import only workspace-local modules and source constants. From c8037d0b5319a21359cb408ff1fb35764c049c29 Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Fri, 26 Jun 2026 09:31:31 +0200 Subject: [PATCH 27/54] Reconcile stale topology path fossils --- memory/REFACTOR.md | 85 -------------------------- memory/SPEC.md | 4 +- src/.pi/extensions/subagents/README.md | 4 +- 3 files changed, 4 insertions(+), 89 deletions(-) delete mode 100644 memory/REFACTOR.md diff --git a/memory/REFACTOR.md b/memory/REFACTOR.md deleted file mode 100644 index 9cbd4a61c..000000000 --- a/memory/REFACTOR.md +++ /dev/null @@ -1,85 +0,0 @@ -## Problem Statement - -The LLM-context ingress refactor moved the bulk of prompts, skills, prompt runtime, and model-facing context text into `src/agents/`, but three topology problems remain. - -First, foreground agent runtime ownership still leaks through `projections/session/runtime-policy.ts`. That file owns the foreground roster, body path references, model/thinking choices, skill grants, tool policy, and delegatable-agent allowlist. Those are agent-runtime facts, not projection facts. The current `projections/session -> agents/registry` edge is documented as temporary; leaving it in place makes `src/agents/runtime/` look central while its most important source of truth is still outside it. - -Second, `read_graph related` was relocated into `src/agents/contexts/graph/related-nodes.ts`, but the actual wording shape still leaks structural internals (`-[category/direction]->`, `plane/kind`) and lacks a focused golden. This preserves the old problem under the new path. - -Third, `src/renderers/` is now an orphan topology: after model-facing renderers moved to `src/agents/contexts/`, it contains only two small human/product renderers. A top-level renderer layer for two isolated outputs adds navigation cost without hiding meaningful complexity. Those outputs now read better beside their real owners: print-mode state near `app/`, transcript debug markdown near `session/`. - -```pseudo tree -current remaining topology -src/ -├── agents/ -│ ├── runtime/ mostly central, but imports projection-owned runtime policy -│ └── contexts/graph/related moved, but still structurally leaky -├── projections/session/ -│ └── runtime-policy owns foreground roster + tool policy + body paths -└── renderers/ orphan layer - ├── workspace/workspace-state - └── session/transcript -``` - -## Solution - -Finish the topology by making `src/agents/runtime/` the actual owner of foreground runtime policy, repairing or collapsing the `related` graph render into the clean graph-context vocabulary, and deleting the now-shallow `src/renderers/` layer. - -```pseudo tree -desired topology -src/ -├── agents/ -│ ├── runtime/ -│ │ ├── foreground roster -│ │ ├── tool policy -│ │ ├── prompt-resource/tool legality -│ │ └── body path lookup -│ └── contexts/graph/ -│ ├── overview/neighborhood -│ └── related uses the same semantic relation vocabulary or disappears behind filtered neighborhood rendering -├── projections/session/ -│ └── runtime-state projection only; no agent roster ownership -├── app/ -│ └── print-mode workspace-state text beside the only product caller -└── session/ - └── debug transcript markdown beside transcript JSONL/projection utilities -``` - -The key judo move is deletion, not another rearrangement: remove the temporary projections→agents edge and remove the orphan `renderers/` topology once its two remaining functions have obvious homes. - -## Commits - -1. [x] Add or move characterization tests for the two remaining orphan human/product renderers at their target owners: print-mode workspace text under the app layer, and debug transcript markdown under the session layer. Keep expected bytes unchanged. -2. [x] Move the foreground agent roster and tool-policy definitions into the agent runtime owner, then update projection and adapter callers to import runtime policy from `src/agents/runtime/`. Leave transcript-state projection in `projections/session`. -3. [x] Delete the `projections/session -> agents/registry` temporary edge and update topology docs/tests so projections no longer own or import agent body locations, foreground roster, or tool policy. -4. [x] Repair `read_graph related` by making it share the semantic relation vocabulary already used by neighborhood rendering, or by deleting the separate related formatter in favor of a filtered-neighborhood render. Add a focused golden/invariant for the related mode so raw category/direction arrows and raw fallback ids do not silently return. -5. [x] Move print-mode workspace-state text out of `src/renderers/` to the app/print owner, update `brunch --mode print` imports, and delete the old workspace renderer file/directory. -6. [x] Move debug transcript markdown out of `src/renderers/` to the session owner, update `session-transcript.ts` imports, and delete the old session renderer file/directory. -7. [x] Delete `src/renderers/README.md` and the now-empty `src/renderers/` topology. Update root/topology READMEs, SPEC/PLAN references, and lint/import-boundary comments to remove `renderers/` as a live source layer. -8. [ ] Reconcile stale path fossils found during the move: old `.pi/extensions/system-prompts`, `.pi/extensions/graph`, `../web`, and renderers references in co-located READMEs and memory files. - -## Decisions - -- `src/agents/runtime/` owns foreground agent roster, agent body locations, skill grants, tool policy, delegatable set, and prompt-resource/tool legality. -- `projections/session/` owns transcript-backed runtime-state projection only; it may consume agent runtime definitions but must not own them. -- `src/renderers/` should be deleted if, after the move, it has no remaining multi-consumer human/product text seam. -- Print-mode workspace-state text belongs near the `app` print-mode path because it has one product caller and no model-facing role. -- Debug transcript markdown belongs near `session` because it is a transcript/debug artifact over Pi JSONL semantics, not a general renderer layer. -- `read_graph related` must not be considered fixed by relocation alone; it needs semantic vocabulary parity with graph neighborhood rendering or removal as a separate path. -- Topology READMEs touched: `src/README.md`, `src/agents/README.md`, `src/agents/runtime/README.md`, `src/agents/contexts/README.md`, `src/agents/contexts/graph/README.md`, `src/.pi/README.md`, `src/.pi/extensions/subagents/README.md`, `src/projections/README.md`, `src/session/README.md`, and any removed `src/renderers/README.md` references in `memory/SPEC.md` / `memory/PLAN.md`. - -## Testing Decisions - -- Existing `agents/runtime` tests cover prompt-resource/tool legality; extend or move them so the foreground roster move is witnessed at the new owner. -- Existing runtime-state projection tests should continue to prove transcript projection behavior, not agent roster ownership. -- Add a related-mode graph golden/invariant before or with the wording repair. The important behavior is semantic relation text and stable graph codes, not the internal helper used. -- Preserve existing print-mode output tests and transcript markdown tests through their move; these are characterization tests for deletion of the `renderers/` layer. -- Keep `npm run verify` as the gate for each commit-sized step; this refactor touches build asset paths, import boundaries, and topology docs. - -## Out of Scope - -- Changing graph ontology vocabulary, edge categories, node kinds, readiness bands, or detail schemas. -- Changing tool availability, Pi sealed-profile behavior, or subagent spawnability. -- Rewriting prompt bodies, skill bodies, or unrelated context wording. -- Moving product web/RPC rendering or introducing a new human-rendering framework. -- Solving the larger `elicitor-project` design question or adding new agent capabilities. diff --git a/memory/SPEC.md b/memory/SPEC.md index 5cf838027..ac749d36a 100644 --- a/memory/SPEC.md +++ b/memory/SPEC.md @@ -322,7 +322,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c Depends on: D19-L, D52-L, D60-L, D62-L, D65-L, D75-L. Refines: D60-L (RENDER stage). Supersedes: the ad-hoc `[bracket]`-header + bullet-list render style as the house convention; hand-rolled markdown and tree string generation in the old renderer layer; carrying sessions in the `` cwd render. - **D95-L — Elicitor capability spine: `capture` / `generate` / `project` are the three elicitor capabilities, named over the existing skill axes — not a fourth axis or a re-axis.** The elicitor's work decomposes into three capabilities by what each does to the graph: **capture** commits ground material already present in the transcript tail into graph truth (the D80-L banded sweep + D81-L commitment gradient + D82-L acquisition layer, already specced); **generate** proposes new typed graph expressions on a requested plane from grounding plus a lens frame, fanning candidates out and committing the chosen one through review (D96-L); **project** derives nodes on one plane from a subset/plane of the existing graph with connecting cross-plane edges (e.g. requirements→design, design→oracles, A33-L). This is a capability vocabulary layered over D85-L's `strategy` / `lens` / `method` axes (held frozen, A35-L), not a replacement topology: capture is always-on conduct of every elicitor turn (D85-L move 3), while generate and project are method-routed capabilities requested just-in-time and gated only advisorily by capability-readiness (D74-L/D86-L). Background acquisition subagents (D82-L near-future, A34-L) are the `acquire` arm feeding capture, not a fourth capability. Depends on: D74-L, D80-L, D81-L, D82-L, D85-L, D86-L. Supersedes: the proposed `grounding` / `elicitation` / `projection` lifecycle directories as a replacement skill topology. - **D96-L — `generate` is one deep plane-parameterized skill; fan-in is a three-value mode carried by `present_candidates` + the review-set path, not three skills.** Generative proposal across the intent, design, and oracle planes is **one** `generate` skill taking the target plane (and lens frame) as a parameter, not per-plane `propose-scenarios` / `propose-design-shapes` / `propose-oracle-ensembles` skills (the earlier per-plane sketch in [`docs/design/ELICITATION_LENSES.md`](docs/design/ELICITATION_LENSES.md)). The fan-out/fan-in shape is shared: the skill fans candidate expressions out, then **fan-in is a three-value mode** — `pick` (choose one), `synthesize` (merge candidates into one), `compose` (accept several) — expressed as plane-keyed method conduct over `present_candidates` plus the review-set path rather than branched per plane. Plane-specific judgment (the "design it twice" pattern for design, oracle-family selection for oracles, the kernel/lens heuristics in [`docs/design/BEHAVIORAL_KERNELS.md`](docs/design/BEHAVIORAL_KERNELS.md) / [`docs/design/ELICITATION_LENSES.md`](docs/design/ELICITATION_LENSES.md) for intent) lives in plane-keyed skill content read on demand (D58-L manifest world), not in separate skills. This **entailed un-stubbing the `present_candidates` topology** — the tool [`src/.pi/extensions/exchanges/present-candidates.ts`](src/.pi/extensions/exchanges/present-candidates.ts), the projection [`src/projections/exchanges/present-candidates.ts`](src/projections/exchanges/present-candidates.ts), and the renderer [`src/agents/contexts/exchanges/present-candidates.ts`](src/agents/contexts/exchanges/present-candidates.ts) — which D85-L move 4 confirmed as a live anticipated stub, not a fossil: candidate presentation gets its product owner here. The materialized tool remains **pick-only at the UI boundary**: intent uses the pick as recognition/provenance, while design-plane synthesize is performed by the method after the pick and then reviewed/committed through `present_review_set → request_response → acceptReviewSet`, so no `fan_in_mode` field is needed unless a later plane proves the UI itself must carry that mode. Commitment still flows through the review-set path (D27-L); `present_candidates` recognizes fan-out presentation and never commits graph truth itself (I51-L). Depends on: D26-L, D27-L, D30-L, D31-L, D58-L, D74-L, D85-L, D95-L; A31-L, A32-L. Supersedes: per-plane generative skills as the topology; treating `present_candidates` as a permanent stub without a product owner; prebuilding a fan-in schema field before a plane proves it necessary. -- **D97-L — Skill ontology-heuristic provenance: three sources — consumed renderers, generated typed-vocab, hand-authored judgment — kept distinct.** Skill bodies that teach the agent how to think about the graph model draw ontology/heuristic content from three provenance classes that must not blur: (1) **dynamic instance context** rendered into the prompt by the context-render house style (D83-L, FE-870) — graph overviews, gap agendas, neighborhoods — consumed, never restated in skill prose; (2) **generated typed-vocab references** (`_generated/`, D85-L prompt-shape closure (d)) projected from the closed `kinds.ts` enums (D73-L) and drift-checked, for any skill that must enumerate node kinds / edge categories / bands / planes; and (3) **hand-authored judgment** — the irreducible "how to reason" content (kernels, lenses, oracle-family selection) that is neither instance data nor mechanical vocabulary. The rule: a skill cites the renderer or the generated reference rather than copying its content, so ontology drift (D73-L renames, D94-L band changes) propagates through one canonical source. Depends on: D58-L, D73-L, D83-L, D85-L, D94-L; FE-870. Supersedes: hand-restating node-kind / band / edge-category / plane vocabulary inside skill bodies. +- **D97-L — Skill ontology-heuristic provenance: three sources — consumed context renders, generated typed-vocab, hand-authored judgment — kept distinct.** Skill bodies that teach the agent how to think about the graph model draw ontology/heuristic content from three provenance classes that must not blur: (1) **dynamic instance context** rendered into the prompt by the context-render house style (D83-L, FE-870) — graph overviews, gap agendas, neighborhoods — consumed, never restated in skill prose; (2) **generated typed-vocab references** (`_generated/`, D85-L prompt-shape closure (d)) projected from the closed `kinds.ts` enums (D73-L) and drift-checked, for any skill that must enumerate node kinds / edge categories / bands / planes; and (3) **hand-authored judgment** — the irreducible "how to reason" content (kernels, lenses, oracle-family selection) that is neither instance data nor mechanical vocabulary. The rule: a skill cites the context renderer or the generated reference rather than copying its content, so ontology drift (D73-L renames, D94-L band changes) propagates through one canonical source. Depends on: D58-L, D73-L, D83-L, D85-L, D94-L; FE-870. Supersedes: hand-restating node-kind / band / edge-category / plane vocabulary inside skill bodies. ### Critical Invariants @@ -506,7 +506,7 @@ src/.pi/ | **AUTO** | The unpinned state of a runtime prompt-resource axis (`strategy` / `lens`): composition advertises the legal choices in the current prompt-resource manifest and instructs the agent to self-select from that manifest only, reading the selected resource when detail matters (D58-L). | | **Brunch Pi Profile** | The sealed programmatic wrapper around embedded Pi: settings policy, resource-loader policy, extension factories, keybinding/command policy, tool policy, and prompt policy. It allows Brunch-owned resources while suppressing ambient `.pi/` behavior. | | **Prompt resource** | A Brunch-owned markdown file under `src/.pi/` containing detailed strategy, lens, method, or agent-definition guidance. Prompt resources are loaded by the agent with `read` when needed; they are product control-plane assets, not ambient Pi prompt templates. | -| **Prompt-resource manifest** | The small per-turn D58-L `` block injected into the system prompt, listing only runtime-legal Brunch resources with `kind`, `name`, `description`, and `location`. The legal set and locations are code-owned in `agents/runtime/state.ts` (not filesystem-discovered); `name` and `description` are file-owned `SKILL.md` frontmatter read via pi's loader over the explicit path list. The `system-prompts/seed/` and `.pi/extensions/brunch-data/context/` context renderers are not manifest resources. It mirrors Pi's skill-list element structure but is filtered by Brunch runtime state, capability-readiness, and allow-lists. | +| **Prompt-resource manifest** | The small per-turn D58-L `` block injected into the system prompt, listing only runtime-legal Brunch resources with `kind`, `name`, `description`, and `location`. The legal set and locations are code-owned in `agents/runtime/state.ts` (not filesystem-discovered); `name` and `description` are file-owned `SKILL.md` frontmatter read via pi's loader over the explicit path list. The seed-context and `.pi/extensions/brunch-data/context/` context renderers are not manifest resources. It mirrors Pi's skill-list element structure but is filtered by Brunch runtime state, capability-readiness, and allow-lists. | | **Method** | A tool-usage or workflow competence advertised as a Brunch prompt resource (`agents/skills/methods//SKILL.md`): run structured exchanges, capture (the D80/D81/D82 home; full conduct in FE-861), generate proposals/projections, read context, mutate the graph, review for gaps. Method resources explain when to use a tool family and how to sequence it with other tools; executable tool definitions should stay focused on schemas, authority, and runtime behavior. A method may also be backed by a Pi-native skill, but actual tool authority remains code-owned through `op_mode` policy and active-tool gating. `capability` is retired as a synonym — use `method` and `method` in ``. | | **Agent context** | The content the agent reasons over — `cwd`, `graph`, or `node` (D60-L): pulled (typed, read-only) from `graph/`/`session/`, optionally projected when a reusable DTO helps, rendered to LLM-string or JSON, surfaced pushed (compose) or pulled (`read_graph` / `read_workspace_context` / `read_session_context`). Graph context explicitly chooses graph-truth vs active-context reads and may filter by node kind, readiness band, edge category/direction, or absence of an edge category (gap query). Distinct from the **workspace projection** (`workspace.state`), which is product/UI state, not agent content. | | **Context-render house style** | The RENDER-stage convention (D83-L) for LLM-facing agent context: a markdown frame (md-pen) with uniform record sets as TOON (`@toon-format/toon`) and file hierarchy as a fenced ASCII tree (stringify-tree over Brunch's gitignore-aware walk), each top-level block wrapped in an XML-style `
` tag. Format follows reader legibility, not internal shape (prose where structure misleads). Agent context clusters into three scopes mirroring `workspace → spec → session` (D19-L): `` (project / documents / spec-roster, no sessions), `` (spec header / graph / ranked gaps / sessions), `` (runtime posture / mentions / transcript). It is the agent-context dialect within `agents/contexts/`; human-facing renders (print/evidence/debug) are local and do not use the `
` clustering. Distinct from the `workspace.state` product-state projection (D60-L). | diff --git a/src/.pi/extensions/subagents/README.md b/src/.pi/extensions/subagents/README.md index a76a0f203..f72af6c24 100644 --- a/src/.pi/extensions/subagents/README.md +++ b/src/.pi/extensions/subagents/README.md @@ -103,7 +103,7 @@ context that crosses back to the parent; structured `details` remain render-only | [`subagents.test.ts`](./subagents.test.ts) | Tests parsing, config, model resolution, tool planning, semaphore fairness, registrar usage errors, abort lifecycle, and **two end-to-end faux-provider child-session runs** asserting the sealing invariants. | | [`../../../app/pi-subagents.ts`](../../../app/pi-subagents.ts) | **App composition root.** `loadBrunchSubagents({cwd, agentDir, delegatableAgents, world})` assembles `BrunchSubagentsDeps` using the sealed `pi-settings` helpers plus explicit parent-world handles and the code-owned op-mode delegatable set. Keeps `.pi/` free of `src/app` imports (deps are injected). | -Boundary rule: `.pi/extensions/subagents/*` may import the SDK and `../web/` +Boundary rule: `.pi/extensions/subagents/*` may import the SDK and `../web-tools/web/` (for `web_search`/`web_fetch`), but **never** `src/app/*`. The app layer injects the sealed primitives. @@ -138,7 +138,7 @@ Starter agents (read-only / no-write): Tool resolution (`planSubagentTools`): read-only filesystem tools come from the SDK (`createReadToolDefinition(cwd)` etc., cwd-bound, override built-ins of the -same name); web tools come from Brunch's own `../web/` factories; `read_graph` +same name); web tools come from Brunch's own `../web-tools/web/` factories; `read_graph` comes from the graph extension's reusable read-tool factory and is available only when parent graph readers are injected. The child grant is sovereign: it resolves against this catalog, not against the parent op-mode's active tool list. Write/shell From 67a84d4e2d7377668048185484f069984c2816c4 Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Fri, 26 Jun 2026 09:32:24 +0200 Subject: [PATCH 28/54] stub knip config --- knip.jsonc | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 knip.jsonc diff --git a/knip.jsonc b/knip.jsonc new file mode 100644 index 000000000..c6dd4ec01 --- /dev/null +++ b/knip.jsonc @@ -0,0 +1,3 @@ +{ + "$schema": "https://unpkg.com/knip@6/schema-jsonc.json", +} From 5d419407801708b6b91abe63e3f69b6d939b7000 Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Fri, 26 Jun 2026 09:40:05 +0200 Subject: [PATCH 29/54] Acknowledge Pi theme setting getter --- memory/REFACTOR.md | 78 ++++++++++++++++++++++++++++ src/app/__tests__/brunch-tui.test.ts | 1 + src/app/pi-settings.ts | 1 + 3 files changed, 80 insertions(+) create mode 100644 memory/REFACTOR.md diff --git a/memory/REFACTOR.md b/memory/REFACTOR.md new file mode 100644 index 000000000..617ceb51f --- /dev/null +++ b/memory/REFACTOR.md @@ -0,0 +1,78 @@ +## Problem Statement + +The last topology pass removed the orphan renderer layer and moved the foreground roster into the agent runtime owner, but two policy seams still keep the agent-runtime model from reading as one settled topology. + +First, capability-readiness is still housed in the session projection area even though it is runtime posture policy: it decides whether strategy/lens/method surfaces are legal or advisory for an agent turn. Agent runtime policy imports it, so the policy owner still depends on a projection module for its own gating vocabulary. + +Second, the runtime affordance projection is now a test-only wrapper around agent-runtime policy. It no longer earns a reusable DTO layer: the real policy functions already live in the agent runtime owner, and there is no product/RPC/web consumer for the wrapper shape. + +A small amount of durable documentation and script vocabulary also preserves old topology names: subagent proof text still names the retired prompt-body home, and test scripts still say renderers after the renderer layer was deleted. + +One unrelated but current gate failure remains: the settings-audit test is red because Pi added a getter that the audited Brunch settings list has not acknowledged. The refactor cannot be considered safely complete while the normal verification gate is red. + +```pseudo tree +current residual shape +src/ +├── agents/runtime/ +│ ├── policy foreground roster + axis/tool policy +│ └── state manifest/tool activation policy +├── projections/session/ +│ ├── capability-readiness agent capability policy still outside agents/runtime +│ ├── affordances test-only wrapper over agents/runtime policy +│ └── runtime-state real reusable transcript DTO projection +└── package scripts/docs old renderer and prompt-home vocabulary remains +``` + +## Solution + +Collapse runtime posture policy fully under the agent runtime owner, delete the test-only projection wrapper, and reconcile vocabulary so durable truth matches the new topology. Keep the projection layer focused on reusable DTOs over session/transcript facts. + +```pseudo tree +desired residual shape +src/ +├── agents/runtime/ +│ ├── policy +│ ├── state +│ └── capability-readiness capability→gap policy used by policy/state +├── projections/session/ +│ └── runtime-state + transcript/readiness DTOs only +└── scripts/docs current names: context/text surfaces, agents/prompts +``` + +The judo move is deletion: remove the affordance wrapper instead of relocating it, and move only the capability policy that is still load-bearing. + +## Commits + +- [x] Restore the verification gate by acknowledging the new audited Pi settings getter in the Brunch settings boundary test/policy, without broadening any ambient settings behavior. +- [ ] Move capability-readiness into the agent runtime owner and update direct consumers/tests so agent posture policy no longer imports capability policy from the projection layer. +- [ ] Delete the runtime-affordance projection wrapper and point its remaining test obligations at the canonical agent-runtime policy functions. Keep the required/deferred affordance ledger but make it cite the runtime owner, not a projection module. +- [ ] Reconcile projection topology docs and boundary tests so session projections no longer claim capability-readiness or affordances as projection-owned modules. +- [ ] Reconcile durable SPEC and README path fossils from the retired prompt-body home to the current prompt home. +- [ ] Rename the renderer test scripts to match the current context/text-surface topology, preserving backwards compatibility only if there is a real user-facing reason; otherwise delete the old renderer names. +- [ ] Retire this refactor plan once the cleanup is committed and the normal verification gate passes. + +## Decisions + +- Agent runtime owns capability-readiness because it is posture/tool/resource legality policy, not an information-preserving session DTO. +- Session projections keep runtime-state and transcript/readiness DTOs; they do not own agent roster, agent body locations, tool policy, capability gates, or affordance menus. +- The runtime-affordance wrapper is deleted unless a real product/RPC/web consumer appears during implementation; tests should not justify a production module. +- The required/deferred affordance ledger remains useful, but it should name the canonical runtime policy owner directly. +- Script names should reflect live topology. Retired renderer vocabulary should not remain unless explicitly preserved as a compatibility alias. +- The settings-audit gate repair is a prerequisite cleanup, not part of the agent-context topology model. +- Topology READMEs touched: root source topology, agents runtime, projections, session, and any Pi/subagent docs that still name retired prompt-body paths. + +## Testing Decisions + +- Capability-readiness tests move with the module and keep the same behavioral oracle: capability→gap map, proceed / low-epistemic / negotiate outcomes, no refusal state, live coverage flip, and loud failure for missing required gap kinds. +- Runtime-policy tests should own axis/menu legality directly after deleting the affordance wrapper: AUTO excludes freestyle, pin surfaces retain freestyle, gated lenses negotiate on uncovered gaps, and missing gap registers fail loud. +- Projection boundary tests should prove projections no longer import or own agent runtime policy beyond the explicit runtime-state DTO consumer edge. +- The settings-audit test should pass by explicitly tracking the new getter; do not loosen the audit into a wildcard. +- Final gate is `npm run verify`; the pass is not done while the known settings-audit failure remains. + +## Out of Scope + +- Changing capability-readiness semantics, readiness bands, graph-write floor behavior, or the no-refusal invariant. +- Adding new runtime affordance transport to RPC/web. +- Reworking the foreground roster shape, prompt bodies, or prompt-resource skill content. +- Touching the extra Knip configuration work except to preserve it as someone else's already-tracked work if it remains in the branch. +- Any broader cleanup of archived planning history beyond current durable SPEC/README references that would mislead active work. diff --git a/src/app/__tests__/brunch-tui.test.ts b/src/app/__tests__/brunch-tui.test.ts index b39aef5fe..46a642c70 100644 --- a/src/app/__tests__/brunch-tui.test.ts +++ b/src/app/__tests__/brunch-tui.test.ts @@ -1322,6 +1322,7 @@ describe('Brunch TUI boot', () => { expect(settingsManager.getImageAutoResize()).toBe(true); expect(settingsManager.getBlockImages()).toBe(false); expect(settingsManager.getTransport()).toBe('auto'); + expect(settingsManager.getThemeSetting()).toBeUndefined(); expect(settingsManager.getTheme()).toBeUndefined(); expect(settingsManager.getLastChangelogVersion()).toBeUndefined(); expect(settingsManager.getCollapseChangelog()).toBe(false); diff --git a/src/app/pi-settings.ts b/src/app/pi-settings.ts index 374e23c63..5206babb4 100644 --- a/src/app/pi-settings.ts +++ b/src/app/pi-settings.ts @@ -61,6 +61,7 @@ export const BRUNCH_SETTINGS_AUDITED_GETTERS = [ 'getDefaultModel', 'getSteeringMode', 'getFollowUpMode', + 'getThemeSetting', 'getTheme', 'getDefaultThinkingLevel', 'getTransport', From 24393b1bb55551475de8da125cc44fb95e6e0676 Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Fri, 26 Jun 2026 09:42:01 +0200 Subject: [PATCH 30/54] Move capability readiness under agents runtime --- memory/REFACTOR.md | 2 +- src/agents/README.md | 6 +++--- src/agents/runtime/README.md | 3 ++- .../runtime}/__tests__/capability-readiness.test.ts | 0 .../session => agents/runtime}/capability-readiness.ts | 0 src/agents/runtime/policy.ts | 5 +---- src/agents/runtime/state.ts | 2 +- 7 files changed, 8 insertions(+), 10 deletions(-) rename src/{projections/session => agents/runtime}/__tests__/capability-readiness.test.ts (100%) rename src/{projections/session => agents/runtime}/capability-readiness.ts (100%) diff --git a/memory/REFACTOR.md b/memory/REFACTOR.md index 617ceb51f..95a4d01b9 100644 --- a/memory/REFACTOR.md +++ b/memory/REFACTOR.md @@ -44,7 +44,7 @@ The judo move is deletion: remove the affordance wrapper instead of relocating i ## Commits - [x] Restore the verification gate by acknowledging the new audited Pi settings getter in the Brunch settings boundary test/policy, without broadening any ambient settings behavior. -- [ ] Move capability-readiness into the agent runtime owner and update direct consumers/tests so agent posture policy no longer imports capability policy from the projection layer. +- [x] Move capability-readiness into the agent runtime owner and update direct consumers/tests so agent posture policy no longer imports capability policy from the projection layer. - [ ] Delete the runtime-affordance projection wrapper and point its remaining test obligations at the canonical agent-runtime policy functions. Keep the required/deferred affordance ledger but make it cite the runtime owner, not a projection module. - [ ] Reconcile projection topology docs and boundary tests so session projections no longer claim capability-readiness or affordances as projection-owned modules. - [ ] Reconcile durable SPEC and README path fossils from the retired prompt-body home to the current prompt home. diff --git a/src/agents/README.md b/src/agents/README.md index 4f5e649c5..745aad3e7 100644 --- a/src/agents/README.md +++ b/src/agents/README.md @@ -4,7 +4,7 @@ SPEC decisions: D39-L, D40-L, D52-L, D60-L, D85-L, D90-L, D91-L, D93-L ## Owns -`src/agents/` is the Pi-independent home for Brunch-authored model-facing context and runtime policy. It now owns bundled agent prompt bodies, Brunch prompt-resource skills, foreground roster policy, prompt composition/runtime legality, seed context composition, reusable agent-visible context renderers, and the central registry for prompt/skill paths. +`src/agents/` is the Pi-independent home for Brunch-authored model-facing context and runtime policy. It now owns bundled agent prompt bodies, Brunch prompt-resource skills, foreground roster policy, capability-readiness policy, prompt composition/runtime legality, seed context composition, reusable agent-visible context renderers, and the central registry for prompt/skill paths. ```text agents/ @@ -24,7 +24,7 @@ rules: agents/registry.ts -> agents/prompts/*/SYSTEM.md [body file locations] agents/registry.ts -> agents/skills/*/*/SKILL.md [prompt-resource locations] agents/contexts/ -> graph/, projections/, session/, workspace/ [agent-visible text over already-read facts] - agents/runtime/ -> agents/registry, projections/session/capability-readiness, session/schema + agents/runtime/ -> agents/registry, agents/prompts, agents/skills, session/schema .pi/extensions/* -> agents/ [adapters ask for Brunch-authored context] session/ -> agents/contexts/seeds/ [origination asks for seed payload text] projections/session/runtime-state.ts -> agents/runtime/policy.ts [consume code-owned roster] @@ -33,4 +33,4 @@ rules: ## Migration note -Agent prompt bodies, prompt-resource skills, foreground roster/tool policy, prompt composition, prompt-resource/tool legality, seed context composition, reusable agent-visible context renderers, and formerly adapter-local model-facing text live here. Pi extensions remain runtime adapters that register hooks/tools, gather data, and call this layer for Brunch-authored text. +Agent prompt bodies, prompt-resource skills, foreground roster/tool policy, capability-readiness policy, prompt composition, prompt-resource/tool legality, seed context composition, reusable agent-visible context renderers, and formerly adapter-local model-facing text live here. Pi extensions remain runtime adapters that register hooks/tools, gather data, and call this layer for Brunch-authored text. diff --git a/src/agents/runtime/README.md b/src/agents/runtime/README.md index be0e94e6e..3d67be316 100644 --- a/src/agents/runtime/README.md +++ b/src/agents/runtime/README.md @@ -4,11 +4,12 @@ SPEC decisions: D40-L, D52-L, D58-L, D85-L, D90-L, D93-L ## Owns -Runtime prompt policy that is Pi-independent: foreground roster definitions, foreground prompt composition, prompt-resource manifest rendering/loading, active method/tool derivation, and agent body location lookup. +Runtime prompt policy that is Pi-independent: foreground roster definitions, capability-readiness over selected-spec gaps, foreground prompt composition, prompt-resource manifest rendering/loading, active method/tool derivation, and agent body location lookup. ```text runtime/ ├── README.md +├── capability-readiness.ts capability → gap policy and negotiate/proceed outcomes ├── compose.ts pure prompt composer: agent body + runtime header + context + manifest ├── policy.ts foreground roster, tool policy, delegatable set, axis legality ├── prompt-skills.ts prompt-resource manifest loader/renderer diff --git a/src/projections/session/__tests__/capability-readiness.test.ts b/src/agents/runtime/__tests__/capability-readiness.test.ts similarity index 100% rename from src/projections/session/__tests__/capability-readiness.test.ts rename to src/agents/runtime/__tests__/capability-readiness.test.ts diff --git a/src/projections/session/capability-readiness.ts b/src/agents/runtime/capability-readiness.ts similarity index 100% rename from src/projections/session/capability-readiness.ts rename to src/agents/runtime/capability-readiness.ts diff --git a/src/agents/runtime/policy.ts b/src/agents/runtime/policy.ts index 466cea4d6..43043e8ae 100644 --- a/src/agents/runtime/policy.ts +++ b/src/agents/runtime/policy.ts @@ -1,8 +1,4 @@ import type { ElicitationGap } from '../../graph/schema/elicitation-gaps.js'; -import { - evaluateCapabilityReadiness, - type CapabilityId, -} from '../../projections/session/capability-readiness.js'; import type { BrunchAgentState, ToolPolicyId } from '../../session/runtime-state.js'; import type { ForegroundAgentManifest } from '../../session/schema/agent-manifest.js'; import type { @@ -15,6 +11,7 @@ import type { import { AGENT_METHOD_IDS } from '../../session/schema/kinds.js'; import { BRUNCH_ORCHESTRATOR_STUB_TOOL } from '../../session/schema/tool-names.js'; import { bundledAgentBodyRepoPath } from '../registry.js'; +import { evaluateCapabilityReadiness, type CapabilityId } from './capability-readiness.js'; export interface ToolPolicyDefinition { id: ToolPolicyId; diff --git a/src/agents/runtime/state.ts b/src/agents/runtime/state.ts index 3a8d79284..7865166d4 100644 --- a/src/agents/runtime/state.ts +++ b/src/agents/runtime/state.ts @@ -1,5 +1,4 @@ import type { ElicitationGap } from '../../graph/schema/elicitation-gaps.js'; -import type { CapabilityId } from '../../projections/session/capability-readiness.js'; import { AGENT_LENS_IDS, AGENT_METHOD_IDS, @@ -8,6 +7,7 @@ import { type AgentRoleId, } from '../../session/schema/kinds.js'; import { bundledAgentBodyLocation } from '../registry.js'; +import type { CapabilityId } from './capability-readiness.js'; import { AUTO_EXCLUDED_STRATEGIES, axisOptionsForRuntimeState, From 1f516dd7eb1a6060c588abfd0b965835e17da228 Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Fri, 26 Jun 2026 09:44:40 +0200 Subject: [PATCH 31/54] Delete runtime affordance projection wrapper --- memory/REFACTOR.md | 2 +- .../runtime/__tests__/policy.test.ts} | 95 ++++++++++--------- .../__tests__/readiness-estimate.test.ts | 6 +- src/projections/session/affordances.ts | 42 -------- src/session/README.md | 28 +++--- .../runtime-affordances-coverage.test.ts | 30 +++--- 6 files changed, 83 insertions(+), 120 deletions(-) rename src/{projections/session/__tests__/affordances.test.ts => agents/runtime/__tests__/policy.test.ts} (55%) delete mode 100644 src/projections/session/affordances.ts diff --git a/memory/REFACTOR.md b/memory/REFACTOR.md index 95a4d01b9..d9ba3d0cb 100644 --- a/memory/REFACTOR.md +++ b/memory/REFACTOR.md @@ -45,7 +45,7 @@ The judo move is deletion: remove the affordance wrapper instead of relocating i - [x] Restore the verification gate by acknowledging the new audited Pi settings getter in the Brunch settings boundary test/policy, without broadening any ambient settings behavior. - [x] Move capability-readiness into the agent runtime owner and update direct consumers/tests so agent posture policy no longer imports capability policy from the projection layer. -- [ ] Delete the runtime-affordance projection wrapper and point its remaining test obligations at the canonical agent-runtime policy functions. Keep the required/deferred affordance ledger but make it cite the runtime owner, not a projection module. +- [x] Delete the runtime-affordance projection wrapper and point its remaining test obligations at the canonical agent-runtime policy functions. Keep the required/deferred affordance ledger but make it cite the runtime owner, not a projection module. - [ ] Reconcile projection topology docs and boundary tests so session projections no longer claim capability-readiness or affordances as projection-owned modules. - [ ] Reconcile durable SPEC and README path fossils from the retired prompt-body home to the current prompt home. - [ ] Rename the renderer test scripts to match the current context/text-surface topology, preserving backwards compatibility only if there is a real user-facing reason; otherwise delete the old renderer names. diff --git a/src/projections/session/__tests__/affordances.test.ts b/src/agents/runtime/__tests__/policy.test.ts similarity index 55% rename from src/projections/session/__tests__/affordances.test.ts rename to src/agents/runtime/__tests__/policy.test.ts index 496d73cfd..401995ef9 100644 --- a/src/projections/session/__tests__/affordances.test.ts +++ b/src/agents/runtime/__tests__/policy.test.ts @@ -3,29 +3,39 @@ import { fileURLToPath } from 'node:url'; import { describe, expect, it } from 'vitest'; +import { groundingFloorGaps } from '../../../graph/schema/elicitation-gap-fixtures.js'; +import { resolveBrunchAgentState } from '../../../projections/session/runtime-state.js'; +import { DEFAULT_BRUNCH_AGENT_STATE } from '../../../session/runtime-state.js'; import { axisOptionsForRuntimeState, + defaultLensForRuntimeState, + defaultStrategyForRuntimeState, pinnableAxisOptionsForRuntimeState, -} from '../../../agents/runtime/policy.js'; -import { groundingFloorGaps } from '../../../graph/schema/elicitation-gap-fixtures.js'; -import { DEFAULT_BRUNCH_AGENT_STATE } from '../../../session/runtime-state.js'; -import { affordances } from '../affordances.js'; -import { resolveBrunchAgentState } from '../runtime-state.js'; +} from '../policy.js'; function resolved(overrides: Partial = {}) { return resolveBrunchAgentState({ ...DEFAULT_BRUNCH_AGENT_STATE, ...overrides }); } -describe('runtime affordances derivation', () => { +describe('agent runtime policy posture affordances', () => { it('reports legal options and default-on-switch values for runtime posture axes', () => { - expect(affordances(resolved(), groundingFloorGaps())).toEqual({ + const state = resolved(); + + expect({ + strategy: { + legalOptions: axisOptionsForRuntimeState('strategy', state, groundingFloorGaps()), + defaultOnSwitch: defaultStrategyForRuntimeState(state), + }, + lens: { + legalOptions: axisOptionsForRuntimeState('lens', state, groundingFloorGaps()), + defaultOnSwitch: defaultLensForRuntimeState(state), + }, + }).toEqual({ strategy: { - selection: 'auto', legalOptions: ['step-wise-decision-tree', 'step-wise-disambiguate'], defaultOnSwitch: 'auto', }, lens: { - selection: 'auto', legalOptions: ['intent', 'design', 'oracle'], defaultOnSwitch: 'auto', }, @@ -33,33 +43,37 @@ describe('runtime affordances derivation', () => { }); it('keeps floor options legal when relevant gaps have zero coverage', () => { - const derived = affordances(resolved(), groundingFloorGaps({ defaultCoverage: 0 })); + const gaps = groundingFloorGaps({ defaultCoverage: 0 }); - expect(derived.strategy.legalOptions).toEqual(['step-wise-decision-tree', 'step-wise-disambiguate']); - expect(derived.lens.legalOptions).toEqual(['intent']); - - expect( - affordances(resolved({ agentStrategy: 'freestyle' }), groundingFloorGaps({ coverage: { context: 0 } })) - .strategy, - ).toEqual({ - selection: 'freestyle', - legalOptions: ['freestyle', 'step-wise-decision-tree', 'step-wise-disambiguate'], - defaultOnSwitch: 'auto', - }); + expect(axisOptionsForRuntimeState('strategy', resolved(), gaps)).toEqual([ + 'step-wise-decision-tree', + 'step-wise-disambiguate', + ]); + expect(axisOptionsForRuntimeState('lens', resolved(), gaps)).toEqual(['intent']); + expect(axisOptionsForRuntimeState('strategy', resolved({ agentStrategy: 'freestyle' }), gaps)).toEqual([ + 'freestyle', + 'step-wise-decision-tree', + 'step-wise-disambiguate', + ]); }); it('keeps strategy options capability-independent while gating generative lenses', () => { - const uncovered = affordances(resolved(), groundingFloorGaps({ defaultCoverage: 0 })); - const covered = affordances(resolved(), groundingFloorGaps()); + const uncovered = groundingFloorGaps({ defaultCoverage: 0 }); + const covered = groundingFloorGaps(); - expect(uncovered.strategy.legalOptions).toEqual(['step-wise-decision-tree', 'step-wise-disambiguate']); - expect(covered.strategy.legalOptions).toEqual(['step-wise-decision-tree', 'step-wise-disambiguate']); - expect(uncovered.lens.legalOptions).not.toContain('design'); - expect(uncovered.lens.legalOptions).not.toContain('oracle'); + expect(axisOptionsForRuntimeState('strategy', resolved(), uncovered)).toEqual([ + 'step-wise-decision-tree', + 'step-wise-disambiguate', + ]); + expect(axisOptionsForRuntimeState('strategy', resolved(), covered)).toEqual([ + 'step-wise-decision-tree', + 'step-wise-disambiguate', + ]); + expect(axisOptionsForRuntimeState('lens', resolved(), uncovered)).not.toContain('design'); + expect(axisOptionsForRuntimeState('lens', resolved(), uncovered)).not.toContain('oracle'); }); it('keeps freestyle on the user-pin surface even while the AUTO manifest excludes it', () => { - // Same AUTO state: the manifest view omits freestyle, the pin surface keeps it. expect(axisOptionsForRuntimeState('strategy', resolved(), groundingFloorGaps())).not.toContain( 'freestyle', ); @@ -77,7 +91,6 @@ describe('runtime affordances derivation', () => { 'step-wise-disambiguate', ]); expect(pinnableAxisOptionsForRuntimeState('lens', resolved(), uncovered)).toEqual(['intent']); - expect(pinnableAxisOptionsForRuntimeState('lens', resolved(), groundingFloorGaps())).toEqual([ 'intent', 'design', @@ -86,18 +99,15 @@ describe('runtime affordances derivation', () => { }); it('excludes freestyle from AUTO strategy affordances but reports a pinned legal strategy', () => { - expect(affordances(resolved(), groundingFloorGaps()).strategy.legalOptions).not.toContain('freestyle'); - - expect(affordances(resolved({ agentStrategy: 'freestyle' }), groundingFloorGaps()).strategy).toEqual({ - selection: 'freestyle', - legalOptions: ['freestyle', 'step-wise-decision-tree', 'step-wise-disambiguate'], - defaultOnSwitch: 'auto', - }); + expect(axisOptionsForRuntimeState('strategy', resolved(), groundingFloorGaps())).not.toContain( + 'freestyle', + ); + expect( + axisOptionsForRuntimeState('strategy', resolved({ agentStrategy: 'freestyle' }), groundingFloorGaps()), + ).toEqual(['freestyle', 'step-wise-decision-tree', 'step-wise-disambiguate']); }); it('fails loud when a gated lens requires a kind absent from the register (config bug, not uncovered)', () => { - // A capability-relevant kind missing from the gap register is a seeding/config bug; - // the affordance projection must surface it, not silently omit the option. const missingThesis = groundingFloorGaps().filter((g) => g.refersTo !== 'thesis'); expect(() => axisOptionsForRuntimeState('lens', resolved(), missingThesis)).toThrow( /no presence gap for thesis/, @@ -113,12 +123,7 @@ describe('runtime affordances derivation', () => { axisOptionsForRuntimeState('lens', resolved(), groundingFloorGaps({ coverage: { thesis: 0 } })), ).toEqual(['intent']); - for (const sourcePath of [ - fileURLToPath(new URL('../affordances.ts', import.meta.url)), - fileURLToPath(new URL('../../../agents/runtime/policy.ts', import.meta.url)), - ]) { - const source = readFileSync(sourcePath, 'utf8'); - expect(source).not.toMatch(/ReadinessGrade|GRADE_RANK|MIN_GRADE/); - } + const source = readFileSync(fileURLToPath(new URL('../policy.ts', import.meta.url)), 'utf8'); + expect(source).not.toMatch(/ReadinessGrade|GRADE_RANK|MIN_GRADE/); }); }); diff --git a/src/projections/session/__tests__/readiness-estimate.test.ts b/src/projections/session/__tests__/readiness-estimate.test.ts index 797203930..41bd5ba7a 100644 --- a/src/projections/session/__tests__/readiness-estimate.test.ts +++ b/src/projections/session/__tests__/readiness-estimate.test.ts @@ -68,11 +68,7 @@ describe('readiness estimate projection', () => { ); expect(driverSource).not.toMatch(/NODE_KIND_METADATA|bandsForKind|schema\/nodes/); - for (const relativePath of [ - '../../../agents/runtime/policy.ts', - '../affordances.ts', - '../../../agents/runtime/state.ts', - ]) { + for (const relativePath of ['../../../agents/runtime/policy.ts', '../../../agents/runtime/state.ts']) { const source = readFileSync(fileURLToPath(new URL(relativePath, import.meta.url)), 'utf8'); expect(source).not.toMatch(/readiness-estimate|readinessEstimate/); } diff --git a/src/projections/session/affordances.ts b/src/projections/session/affordances.ts deleted file mode 100644 index dd7b31f4e..000000000 --- a/src/projections/session/affordances.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { - axisOptionsForRuntimeState, - defaultLensForRuntimeState, - defaultStrategyForRuntimeState, - type ResolvedBrunchAgentState, -} from '../../agents/runtime/policy.js'; -import type { ElicitationGap } from '../../graph/schema/elicitation-gaps.js'; -import type { - AgentLensId, - AgentLensSelection, - AgentStrategyId, - AgentStrategySelection, -} from '../../session/schema/kinds.js'; - -interface AxisAffordance { - readonly selection: TSelection; - readonly legalOptions: readonly TId[]; - readonly defaultOnSwitch: TSelection; -} - -export interface RuntimeAffordances { - readonly strategy: AxisAffordance; - readonly lens: AxisAffordance; -} - -export function affordances( - state: ResolvedBrunchAgentState, - gaps: readonly ElicitationGap[], -): RuntimeAffordances { - return { - strategy: { - selection: state.agentStrategy, - legalOptions: axisOptionsForRuntimeState('strategy', state, gaps), - defaultOnSwitch: defaultStrategyForRuntimeState(state), - }, - lens: { - selection: state.agentLens, - legalOptions: axisOptionsForRuntimeState('lens', state, gaps), - defaultOnSwitch: defaultLensForRuntimeState(state), - }, - }; -} diff --git a/src/session/README.md b/src/session/README.md index f13748c1f..0c7151108 100644 --- a/src/session/README.md +++ b/src/session/README.md @@ -106,34 +106,32 @@ directly instead of growing a wrapper. | `cwd_inventory` | `workspace/cwd-inventory.ts` (`inspectWorkspaceCwdInventory`) | `read_workspace_context`, `agents/contexts/workspace/workspace-context.ts` | Workspace-owned direct PULL read. The typed inventory already matches the tool/renderer seam, so no `projections/workspace/workspace-context` wrapper survives. | | `workspace_overview` | `workspace-overview-context.ts` (`inspectWorkspaceOverview`) | `read_workspace_context`, origination seed context, `agents/contexts/workspace/workspace-context.ts` | Session-side composition over graph specs and canonical session files. Same no-wrapper rationale as `cwd_inventory`: the source shape is already the consumer shape. | | `workspace_session_state` | `WorkspaceSessionCoordinator` (`WorkspaceSessionState`) | `projections/workspace/workspace-state.ts`, `chromeStateForWorkspace`, app/rpc/web workspace flows | Source union owned by the coordinator. Downstream code may flatten it, but the coordinator remains the authority for the narrow chrome snapshot and status-variant field set. | -| `agent_runtime_vocab` | `schema/kinds.ts`, `schema/tool-names.ts` | `runtime-state.ts`, `agents/runtime/policy.ts`, `projections/session/affordances.ts`, `agents/runtime/state.ts`, `.pi/extensions/agent-runtime/orchestrator-stub/` | Pure vocabulary leaf for runtime axes, agent-role ids, and shared Brunch tool-name constants; imports nothing and mirrors D73-L's graph taxonomy direction on the session side. | -| `agent_runtime_state` | `latestValidBrunchAgentStateEntryData` and transcript-backed runtime-state facts in `session/runtime-state.ts` | `projections/session/runtime-state.ts`, `projections/session/affordances.ts`, `.pi/extensions/agent-runtime/runtime/` | Transcript-backed source read. Projection/policy layers derive from these facts rather than storing parallel hidden runtime memory. | +| `agent_runtime_vocab` | `schema/kinds.ts`, `schema/tool-names.ts` | `runtime-state.ts`, `agents/runtime/policy.ts`, `agents/runtime/state.ts`, `.pi/extensions/agent-runtime/` | Pure vocabulary leaf for runtime axes, agent-role ids, and shared Brunch tool-name constants; imports nothing and mirrors D73-L's graph taxonomy direction on the session side. | +| `agent_runtime_state` | `latestValidBrunchAgentStateEntryData` and transcript-backed runtime-state facts in `session/runtime-state.ts` | `projections/session/runtime-state.ts`, `agents/runtime/policy.ts`, `.pi/extensions/agent-runtime/` | Transcript-backed source read. Projection/policy layers derive from these facts rather than storing parallel hidden runtime memory. | ## Runtime affordance coverage ledger Runtime posture affordances are pure derivations over projected runtime state plus -capability-readiness over selected-spec gaps. `projections/session/affordances.ts` -owns legal option sets and default-on-switch values; `session.runtimeState` -currently exposes only the selected value per axis. Deferred means eligible or -known but not currently transported for that consumer. +capability-readiness over selected-spec gaps. `agents/runtime/policy.ts` owns +legal option sets and default-on-switch values; `session.runtimeState` currently +exposes only the selected value per axis. Deferred means eligible or known but +not currently transported for that consumer. | Row | Canonical owner | Agent | RPC | Web | Reason for deferred | | --- | --- | --- | --- | --- | --- | -| `goal.options` | `affordances.goal.legalOptions` | required | deferred | deferred | Transport follows a concrete UI/client need; agent already needs legality. | -| `goal.default_on_switch` | `affordances.goal.defaultOnSwitch` | required | deferred | deferred | Transport follows a concrete posture-switch surface. | -| `goal.selection` | `session.runtimeState.agent.goal` | required | required | deferred | RPC already reports current posture; web has no posture UI yet. | -| `strategy.options` | `affordances.strategy.legalOptions` | required | deferred | deferred | Transport follows a concrete UI/client need; AUTO excludes `freestyle`. | -| `strategy.default_on_switch` | `affordances.strategy.defaultOnSwitch` | required | deferred | deferred | Transport follows a concrete posture-switch surface. | +| `strategy.options` | `agents/runtime/policy.axisOptionsForRuntimeState(strategy)` | required | deferred | deferred | Transport follows a concrete UI/client need; AUTO excludes `freestyle`. | +| `strategy.default_on_switch` | `agents/runtime/policy.defaultStrategyForRuntimeState` | required | deferred | deferred | Transport follows a concrete posture-switch surface. | | `strategy.selection` | `session.runtimeState.agent.strategy` | required | required | deferred | RPC already reports current posture; web has no posture UI yet. | -| `lens.options` | `affordances.lens.legalOptions` | required | deferred | deferred | Transport follows a concrete UI/client need. | -| `lens.default_on_switch` | `affordances.lens.defaultOnSwitch` | required | deferred | deferred | Transport follows a concrete posture-switch surface. | +| `lens.options` | `agents/runtime/policy.axisOptionsForRuntimeState(lens)` | required | deferred | deferred | Transport follows a concrete UI/client need. | +| `lens.default_on_switch` | `agents/runtime/policy.defaultLensForRuntimeState` | required | deferred | deferred | Transport follows a concrete posture-switch surface. | | `lens.selection` | `session.runtimeState.agent.lens` | required | required | deferred | RPC already reports current posture; web has no posture UI yet. | | `active-review-set` | product-state-gated review-cycle surface | deferred | deferred | deferred | Needs current review-set product state; not derivable from runtime policy alone. | | `turn-mode` | product-state-gated freestyle-vs-structured turn surface | deferred | deferred | deferred | Needs current turn/exchange mode state; not derivable from runtime policy alone. | `runtime-affordances-coverage.test.ts` guards the required subsets: agent rows -must remain covered by the shared derivation, RPC rows by the public session -schema, and the product-state-gated rows must stay explicit deferred tripwires. +must remain covered by the shared runtime policy derivation, RPC rows by the +public session schema, and the product-state-gated rows must stay explicit +deferred tripwires. ## Does NOT own diff --git a/src/session/__tests__/runtime-affordances-coverage.test.ts b/src/session/__tests__/runtime-affordances-coverage.test.ts index 5d92d38fa..63dd0cb8d 100644 --- a/src/session/__tests__/runtime-affordances-coverage.test.ts +++ b/src/session/__tests__/runtime-affordances-coverage.test.ts @@ -1,7 +1,11 @@ import { describe, expect, it } from 'vitest'; +import { + axisOptionsForRuntimeState, + defaultLensForRuntimeState, + defaultStrategyForRuntimeState, +} from '../../agents/runtime/policy.js'; import { groundingFloorGaps } from '../../graph/schema/elicitation-gap-fixtures.js'; -import { affordances } from '../../projections/session/affordances.js'; import { resolveBrunchAgentState } from '../../projections/session/runtime-state.js'; import { sessionRpcMethods } from '../../rpc/methods/session.js'; import { DEFAULT_BRUNCH_AGENT_STATE } from '../runtime-state.js'; @@ -9,14 +13,14 @@ import { DEFAULT_BRUNCH_AGENT_STATE } from '../runtime-state.js'; const runtimeAffordanceLedger = [ { row: 'strategy.options', - owner: 'affordances.strategy.legalOptions', + owner: 'agents/runtime/policy.axisOptionsForRuntimeState(strategy)', agent: 'required', rpc: 'deferred', web: 'deferred', }, { row: 'strategy.default_on_switch', - owner: 'affordances.strategy.defaultOnSwitch', + owner: 'agents/runtime/policy.defaultStrategyForRuntimeState', agent: 'required', rpc: 'deferred', web: 'deferred', @@ -30,14 +34,14 @@ const runtimeAffordanceLedger = [ }, { row: 'lens.options', - owner: 'affordances.lens.legalOptions', + owner: 'agents/runtime/policy.axisOptionsForRuntimeState(lens)', agent: 'required', rpc: 'deferred', web: 'deferred', }, { row: 'lens.default_on_switch', - owner: 'affordances.lens.defaultOnSwitch', + owner: 'agents/runtime/policy.defaultLensForRuntimeState', agent: 'required', rpc: 'deferred', web: 'deferred', @@ -98,13 +102,15 @@ describe('runtime affordances coverage ledger', () => { ]); }); - it('covers all agent-required rows through the shared affordances derivation', () => { - const derived = affordances(resolveBrunchAgentState(DEFAULT_BRUNCH_AGENT_STATE), groundingFloorGaps()); - const derivedRows = Object.entries(derived).flatMap(([axis, axisAffordance]) => { - const { selection: _selection, ...derivedFields } = axisAffordance; - expect(Object.keys(derivedFields).sort()).toEqual(['defaultOnSwitch', 'legalOptions']); - return [`${axis}.options`, `${axis}.default_on_switch`]; - }); + it('covers all agent-required rows through the shared runtime policy derivation', () => { + const state = resolveBrunchAgentState(DEFAULT_BRUNCH_AGENT_STATE); + const gaps = groundingFloorGaps(); + const derivedRows = [ + axisOptionsForRuntimeState('strategy', state, gaps).length > 0 && 'strategy.options', + defaultStrategyForRuntimeState(state) && 'strategy.default_on_switch', + axisOptionsForRuntimeState('lens', state, gaps).length > 0 && 'lens.options', + defaultLensForRuntimeState(state) && 'lens.default_on_switch', + ].filter((row): row is string => typeof row === 'string'); expect(new Set(derivedRows)).toEqual( new Set(requiredRowsFor('agent').filter((row) => !row.endsWith('.selection'))), From 60f2d8619911868eda7612c8e49a650f4d65b153 Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Fri, 26 Jun 2026 09:46:31 +0200 Subject: [PATCH 32/54] Reconcile projection runtime topology --- memory/REFACTOR.md | 2 +- src/README.md | 2 +- src/projections/README.md | 10 ++++------ .../__tests__/topology-boundaries.test.ts | 13 +++++++++++++ src/treedocs.yaml | 3 +-- 5 files changed, 20 insertions(+), 10 deletions(-) diff --git a/memory/REFACTOR.md b/memory/REFACTOR.md index d9ba3d0cb..6ffd42db9 100644 --- a/memory/REFACTOR.md +++ b/memory/REFACTOR.md @@ -46,7 +46,7 @@ The judo move is deletion: remove the affordance wrapper instead of relocating i - [x] Restore the verification gate by acknowledging the new audited Pi settings getter in the Brunch settings boundary test/policy, without broadening any ambient settings behavior. - [x] Move capability-readiness into the agent runtime owner and update direct consumers/tests so agent posture policy no longer imports capability policy from the projection layer. - [x] Delete the runtime-affordance projection wrapper and point its remaining test obligations at the canonical agent-runtime policy functions. Keep the required/deferred affordance ledger but make it cite the runtime owner, not a projection module. -- [ ] Reconcile projection topology docs and boundary tests so session projections no longer claim capability-readiness or affordances as projection-owned modules. +- [x] Reconcile projection topology docs and boundary tests so session projections no longer claim capability-readiness or affordances as projection-owned modules. - [ ] Reconcile durable SPEC and README path fossils from the retired prompt-body home to the current prompt home. - [ ] Rename the renderer test scripts to match the current context/text-surface topology, preserving backwards compatibility only if there is a real user-facing reason; otherwise delete the old renderer names. - [ ] Retire this refactor plan once the cleanup is committed and the normal verification gate passes. diff --git a/src/README.md b/src/README.md index 4b0f6bf77..2af96551b 100644 --- a/src/README.md +++ b/src/README.md @@ -58,7 +58,7 @@ Rules: - `workspace/` owns cwd-scoped identity, inventory, and workspace default-state persistence. It must not import Pi, session, graph, DB, projection, renderer, adapter, transport, app, or web modules. - `graph/` imports from `db/`. No other layer imports `db/` directly. -- `agents/` owns the Brunch-authored LLM-context ingress seam. Today it hosts agent prompt bodies, prompt-resource skills, foreground roster policy, prompt composition, prompt-resource/tool legality, context seed composition, reusable agent-visible context renderers, and the central file registry. +- `agents/` owns the Brunch-authored LLM-context ingress seam. Today it hosts agent prompt bodies, prompt-resource skills, foreground roster policy, capability-readiness policy, prompt composition, prompt-resource/tool legality, context seed composition, reusable agent-visible context renderers, and the central file registry. - `.pi/` owns Pi-harness extensions/components and no longer hosts Brunch-authored prompt bodies, prompt-resource skills, prompt composition, or provider-visible tool/session text. - `.pi/extensions/` registers Pi tools/hooks/UI affordances and delegates product semantics outward. - `projections/` owns reusable structured output; `agents/contexts/` owns reusable model-facing text. Human/product text now lives beside its single product owner (`app/print-workspace-state.ts`, `session/transcript-markdown.ts`) instead of a shallow shared renderer layer. diff --git a/src/projections/README.md b/src/projections/README.md index bd3dc77f4..627f122d5 100644 --- a/src/projections/README.md +++ b/src/projections/README.md @@ -26,10 +26,8 @@ Disposition: `✓` resolved (direct lock or accepted transitive proof) · `●` | `graph/commit-result` | — | ○ | `export {}` topology stub. | | `graph/reconciliation-needs` | — | ○ | `export {}` topology stub. | | `session/transcript-context` | 2 | ✓ | `transcript-context.test.ts` — no non-empty markdown-bearing message disappears across the Pi `buildSessionContext()` + `convertToLlm()` seam; non-renderable entries drop at the projection boundary. | -| `session/runtime-state` | 13 | ✓ | `runtime-state.test.ts` — direct flattened-shape invariant for defaults, last-writer-wins runtime posture, mentions/world/lifecycle slots, and non-linear transcript rejection. | -| `session/affordances` | 1 | ✓ | `affordances.test.ts` — gap-driven legality + default-on-switch derivation tested directly. Legal options are a menu projection over capability-readiness; omitted options are not capability refusals (I31-L). | -| `session/capability-readiness` | 1 | ✓ | D74-L/D75-L tracer gate, not a reusable DTO. `capability-readiness.test.ts` locks the explicit capability→node-kind map, proceed / low-epistemic / negotiate outcomes, no-refusal invariant, loud failure when the gap register lacks a required kind, same-kind discrimination through `question`, and live presence-coverage flip. `session/affordances` now consumes it for axis-option legality. **D86-L: capability-readiness gates AUTO axis menus (`strategy`/`lens`) and the non-graph-write `review-for-gaps` method only — it never withholds a graph-write tool. `mutate_graph` and the review-set tools (`present_review_set`/`request_response`) are floor in elicit mode (their `commit-graph`/`generate-proposal` methods are absent from `METHOD_CAPABILITY` in `agents/runtime/state.ts`); `negotiate` is advisory (establishment offer + epistemic scaling), proven by `state.test.ts` + the tier-2 live-boot legality test.** | -| `session/readiness-estimate` | — | ✓ | D45-L soft per-band coverage rollup over `ElicitationGap[]`; UI-only and gates nothing. `readiness-estimate.test.ts` locks every-band shape, empty-band zero, importance-weighted mean, honest regression, no grade imports, and no legality-path imports. | +| `session/runtime-state` | 13 | ✓ | `runtime-state.test.ts` — direct flattened-shape invariant for defaults, last-writer-wins runtime posture, mentions/world/lifecycle slots, and non-linear transcript rejection. This is the only session projection that consumes `agents/runtime/policy.ts`, because it resolves transcript facts against the code-owned foreground roster. | +| `session/readiness-estimate` | — | ✓ | D45-L soft per-band coverage rollup over `ElicitationGap[]`; UI-only and gates nothing. `readiness-estimate.test.ts` locks every-band shape, empty-band zero, importance-weighted mean, honest regression, no grade imports, and no legality-path imports. Capability-readiness and runtime affordance menus are runtime policy, not projection-owned DTOs. | | `session/assistant-visible-watermark` | 2 | ✓ | Carrier projection over the authoritative `continuity-entry-classifier` watermark set. Unit tests guard seed/overview/own-mutation/`worldUpdate` carriers, narrow-read exclusion, and cross-spec failure. | | `session/continuity-entry-classifier` | 2 | ✓ | Shared FE-847 taxonomy for watermark-carrier vs continuity-only-non-debt vs debt-bearing entries; consumed by watermark projection and origination tail classification. | | `session/sweep-watermark` | 1 | ✓ | FE-861 D80-L sweep-window projection. `sweep-watermark.test.ts` locks the transcript-backed marker, conversational/digest tail classification, raw-background exclusion, monotonic idempotent advance, and graph-LSN watermark separation. | @@ -53,7 +51,7 @@ Upstream note (PULL): `●` projections lock against their read sources, so thos ```pseudo projections/ graph/ graph read/command DTO projection - session/ transcript-context and runtime-state DTO projection + session/ transcript-context, runtime-state, watermarks, and readiness-estimate DTO projections exchanges/ canonical toolResult.details construction and transcript details → domain DTO adapters workspace/ workspace/session state DTO projection ``` @@ -68,4 +66,4 @@ projections/ x> .pi/, rpc/, app/, web/ Current migration notes: - `projections/exchanges/*` imports Zod schemas from `.pi/extensions/exchanges/schemas/` because D37-L/D41-L currently place the structured-exchange schema lock at that Pi transcript seam. That is an explicit temporary exception, not a general adapter dependency permission. -- `projections/session/runtime-state.ts` owns flattened runtime-state DTO projection while `session/runtime-state.ts` owns transcript entry facts and append helpers. It consumes the code-owned foreground roster/tool policy from `agents/runtime/policy.ts`; projections do not own agent body locations, foreground roster definitions, or tool policy. +- `projections/session/runtime-state.ts` owns flattened runtime-state DTO projection while `session/runtime-state.ts` owns transcript entry facts and append helpers. It consumes the code-owned foreground roster/tool policy from `agents/runtime/policy.ts`; projections do not own agent body locations, foreground roster definitions, capability-readiness, runtime affordance menus, or tool policy. diff --git a/src/projections/__tests__/topology-boundaries.test.ts b/src/projections/__tests__/topology-boundaries.test.ts index 0c6ff5a84..51e994cec 100644 --- a/src/projections/__tests__/topology-boundaries.test.ts +++ b/src/projections/__tests__/topology-boundaries.test.ts @@ -92,4 +92,17 @@ describe('projection topology boundaries', () => { ); expect(importedSourcePaths('src/session/runtime-state.ts')).not.toContain('src/agents/runtime/policy.ts'); }); + + it('keeps agent runtime policy out of session projection ownership except runtime-state resolution', () => { + const sessionProjectionFiles = sourceFilesUnder('src/projections/session').filter( + (file) => !file.includes('/__tests__/') && !file.endsWith('.test.ts'), + ); + expect(sessionProjectionFiles).not.toContain('src/projections/session/affordances.ts'); + expect(sessionProjectionFiles).not.toContain('src/projections/session/capability-readiness.ts'); + + const runtimePolicyImporters = sessionProjectionFiles.filter((file) => + importedSourcePaths(file).includes('src/agents/runtime/policy.ts'), + ); + expect(runtimePolicyImporters).toEqual(['src/projections/session/runtime-state.ts']); + }); }); diff --git a/src/treedocs.yaml b/src/treedocs.yaml index c2427d6b6..d9b8f036c 100644 --- a/src/treedocs.yaml +++ b/src/treedocs.yaml @@ -233,6 +233,7 @@ tree: elicitor--auto-high-coverage.md: 'Markdown resource.' elicitor--pinned-strategy-lens.md: 'Markdown resource.' elicitor--pushed-context.md: 'Markdown resource.' + capability-readiness.ts: 'Implements capability readiness.' compose.ts: 'Implements compose.' policy.ts: 'Implements runtime policy.' prompt-skills.ts: 'Implements prompt skills.' @@ -380,9 +381,7 @@ tree: overview.ts: 'Implements overview.' reconciliation-needs.ts: 'Implements reconciliation needs.' session: - affordances.ts: 'Implements affordances.' assistant-visible-watermark.ts: 'Implements assistant visible watermark.' - capability-readiness.ts: 'Implements capability readiness.' continuity-entry-classifier.ts: 'Implements continuity entry classifier.' readiness-estimate.ts: 'Implements readiness estimate.' runtime-state.ts: 'Implements runtime state.' From b9855a5ba71561d57f63a69fb76a66f38fe629cf Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Fri, 26 Jun 2026 09:48:59 +0200 Subject: [PATCH 33/54] Reconcile prompt body path fossils --- memory/REFACTOR.md | 2 +- memory/SPEC.md | 14 +++++++------- src/README.md | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/memory/REFACTOR.md b/memory/REFACTOR.md index 6ffd42db9..3a7049610 100644 --- a/memory/REFACTOR.md +++ b/memory/REFACTOR.md @@ -47,7 +47,7 @@ The judo move is deletion: remove the affordance wrapper instead of relocating i - [x] Move capability-readiness into the agent runtime owner and update direct consumers/tests so agent posture policy no longer imports capability policy from the projection layer. - [x] Delete the runtime-affordance projection wrapper and point its remaining test obligations at the canonical agent-runtime policy functions. Keep the required/deferred affordance ledger but make it cite the runtime owner, not a projection module. - [x] Reconcile projection topology docs and boundary tests so session projections no longer claim capability-readiness or affordances as projection-owned modules. -- [ ] Reconcile durable SPEC and README path fossils from the retired prompt-body home to the current prompt home. +- [x] Reconcile durable SPEC and README path fossils from the retired prompt-body home to the current prompt home. - [ ] Rename the renderer test scripts to match the current context/text-surface topology, preserving backwards compatibility only if there is a real user-facing reason; otherwise delete the old renderer names. - [ ] Retire this refactor plan once the cleanup is committed and the normal verification gate passes. diff --git a/memory/SPEC.md b/memory/SPEC.md index ac749d36a..3c7b20092 100644 --- a/memory/SPEC.md +++ b/memory/SPEC.md @@ -133,7 +133,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c - **D40-L — Runtime state is transcript-backed Brunch session-agent state, not hidden extension memory.** The architectural commitment is that Brunch session-agent posture remains transcript-backed Pi JSONL state rather than hidden extension memory: posture switches are user/system authority, the foreground session agent is derived from `op_mode`, child axes default back to `AUTO` when invalidated, and tool authority remains `op_mode`-gated rather than prompt-composition-owned. Runtime-state entries are Pi JSONL state-change facts, not assistant/user chat content: init and switch entries should render, when visible, as dim non-chat state rows analogous to Pi thinking/model-change rows, and must not enter LLM context as ordinary conversation. Current materialized state lives in [`src/session/README.md`](src/session/README.md), [`src/projections/README.md`](src/projections/README.md), [`src/.pi/README.md`](src/.pi/README.md), [`src/agents/README.md`](src/agents/README.md), [`src/agents/prompts/README.md`](src/agents/prompts/README.md), and [`src/agents/runtime/README.md`](src/agents/runtime/README.md). Depends on: D17-L, D23-L, D25-L, D39-L, D58-L, D59-L. Refined by: D85-L (the AUTO-able objective axes reduce to `strategy` + `lens`; `goal` is dropped from the runtime-state axis set and inlined into the agent role prompt). Supersedes: mode-only vocabulary, extension-local mutable state as authority, storing the foreground role as independent session state, the "runtime bundle / role preset" as one knob deriving model/thinking/resources, and binding prompt-resource location to `src/.pi/context/`. - **D34-L — Command containment separates visibility suppression from effect blocking.** Current Pi extension seams can hide unsupported slash suggestions with autocomplete wrapping and can cancel branch/session effects through lifecycle hooks, but they cannot strictly suppress exact interactive built-in commands before `InteractiveMode` dispatches them. Brunch-owned commands must use product-specific names and route writes through Brunch handlers/`CommandExecutor`; extension command collisions are not an override mechanism. Strict built-in command/keybinding policy is a Pi upstream/API ask, while POC safety relies on hiding generic affordances, blocking dangerous effects (`/fork`, `/clone`, raw session replacement), allowing native `/tree` as inspection/navigation, and failing fast on branched transcripts. Brunch's command-policy code should live in `src/.pi/extensions/commands/policy.ts`, merging branch/session-effect blocking with any product command allow/deny behavior instead of preserving a branch-only module. Depends on: D2-L, D24-L, A18-L. Supersedes: treating extension `input` handlers or command-name collisions as built-in command allowlisting. - **D35-L — Dynamic TUI chrome is a Brunch projection wrapper over Pi UI primitives.** The architectural commitment is that downstream TUI affordances call one Brunch-owned renderer (`renderBrunchChrome` or its successor) with a single activated product-state value rather than scattering raw `ctx.ui.setHeader` / `setFooter` / `setWidget` / title / working-indicator calls; the wrapper is stateless projection over canonical workspace/session/graph facts, never its own mutable state. Chrome is a project-first shell surface with selected-spec context — project name labels the cwd container, spec title labels the selected graph, session label distinguishes transcript instances — and a session label must never replace spec identity or graph truth. Chrome must not consume the status-key namespace for its own summary (`ctx.ui.setStatus` stays a lateral channel for other extensions), must not advertise unwired affordances, and RPC clients must rely only on surfaces Pi actually emits (header/footer/working-indicator are TUI-only in current Pi RPC mode). Current chrome state shape, render surfaces, telemetry/refresh, startup-header behavior, and status-key filtering live in [`src/.pi/extensions/chrome/README.md`](src/.pi/extensions/chrome/README.md); launch/activation wiring lives in [`src/.pi/extensions/README.md`](src/.pi/extensions/README.md). Depends on: D2-L, D21-L, D34-L, A18-L. Supersedes: treating Pi UI methods as direct downstream affordance APIs, rendering placeholder session state such as `unbound` after a session is activated, consuming the status-key namespace for chrome's own static summary, using spec title as the default session label, or allowing two unchanged Brunch-created default names to collide in one cwd, and the earlier resume/open-launches-stay-quiet clause (superseded 2026-06-11: the shipped, test-locked behavior headers every non-cancel activation). -- **D52-L — Source topology targets `src/{app, workspace, scripts, agents, .pi, db, graph, session, projections, rpc, web}` with directed layer dependencies.** Reusable projection modules live in top-level `src/projections/`; human/product text rendering stays beside its app/session owner rather than a shallow shared layer; `src/agents/` is the Pi-independent owner for Brunch-authored LLM context ingress and foreground runtime policy (currently bundled agent prompt bodies, prompt-resource skills, foreground roster/tool policy, prompt composition, prompt-resource/tool legality, seed context composition, reusable agent-visible renderers, adapter-local tool/session text promoted into contexts, and the central registry for prompt/skill file paths); domain layers (`graph/`, `session/`) and the reusable `projections` / `agents` layers must not import adapters, transports, app entrypoints, or web code; `graph/` is the only layer that imports `db/`, plus the single sanctioned `db/`→`graph/schema/kinds.ts` taxonomy edge (D73-L). The concrete per-directory ownership, layout sketch, and full import matrix are owned by [`src/README.md`](src/README.md). Depends on: D2-L, D4-L, D39-L, D40-L. Refined by: D73-L. Supersedes: scattering session domain files at `src/` root; treating Pi-only agents as a host-independent top-level `src/.pi/` layer; nesting prompt composition under `src/.pi/context/`; treating reusable `project` / `format` helpers as owned by whichever adapter first needed them; treating retired `.pi/agents` / `.pi/skills` locations as the long-term conceptual owner for Brunch-authored model-facing content. +- **D52-L — Source topology targets `src/{app, workspace, scripts, agents, .pi, db, graph, session, projections, rpc, web}` with directed layer dependencies.** Reusable projection modules live in top-level `src/projections/`; human/product text rendering stays beside its app/session owner rather than a shallow shared layer; `src/agents/` is the Pi-independent owner for Brunch-authored LLM context ingress and foreground runtime policy (currently bundled agent prompt bodies, prompt-resource skills, foreground roster/tool policy, capability-readiness policy, prompt composition, prompt-resource/tool legality, seed context composition, reusable agent-visible renderers, adapter-local tool/session text promoted into contexts, and the central registry for prompt/skill file paths); domain layers (`graph/`, `session/`) and the reusable `projections` / `agents` layers must not import adapters, transports, app entrypoints, or web code; `graph/` is the only layer that imports `db/`, plus the single sanctioned `db/`→`graph/schema/kinds.ts` taxonomy edge (D73-L). The concrete per-directory ownership, layout sketch, and full import matrix are owned by [`src/README.md`](src/README.md). Depends on: D2-L, D4-L, D39-L, D40-L. Refined by: D73-L. Supersedes: scattering session domain files at `src/` root; treating Pi-only agents as a host-independent top-level `src/.pi/` layer; nesting prompt composition under `src/.pi/context/`; treating reusable `project` / `format` helpers as owned by whichever adapter first needed them; treating retired Pi-owned prompt/skill homes as the long-term conceptual owner for Brunch-authored model-facing content. - **D73-L — Domain enum taxonomy is owned by drizzle-free schema leaves; persistence and adapters are consumers, not the source.** The closed enum `const` arrays that define graph vocabulary — node kinds (`INTENT_KINDS`, `ORACLE_KINDS`, `DESIGN_KINDS`, `PLAN_KINDS`), `NODE_PLANES` (`intent`/`oracle`/`design`/`plan`), `NODE_BASES`, `EDGE_CATEGORIES`, `EDGE_STANCES`, `READINESS_BANDS`, `LENS_AFFINITIES`, `GAP_DISPOSITIONS`, and `GAP_PREDICATE_KINDS` — live in `graph/schema/kinds.ts`, a pure constants leaf that imports nothing (no drizzle, no `graph/atoms`). Both `db/schema.ts` (for `text({ enum })` column constraints, including the previously-inlined `plane` columns) and `graph/` domain modules import the arrays from this leaf; `graph/index.ts` re-exports them from the leaf so non-graph layers still avoid importing `db/` directly (I26-L). Session runtime axis vocabulary mirrors the same ownership direction in `session/schema/kinds.ts`: that leaf imports nothing and owns the `op_mode`, agent-role, `strategy`, `lens`, `auto`, and display-only planned mode choices consumed by `session/runtime-state.ts`, `projections/session/*`, and `agents/runtime/state.ts`; it deliberately contains no `goal` axis and no retired `READINESS_GRADES`. Derivations stay where they are read: `NODE_KIND_METADATA`, `formatGraphNodeCode`, `parseGraphNodeCode`, and `intentKindCategory` remain in `graph/schema/nodes.ts` (D62-L). The motivating defect: because `db/schema.ts` eagerly evaluates `sqliteTable(...)` and `verbatimModuleSyntax` emits even type-only imports at runtime, any value-import path from `web/` into the old taxonomy location pulled Drizzle into the browser bundle. Locating taxonomy in a drizzle-free leaf makes the `web/` build target structurally Drizzle-free (I44-L) and corrects the ownership direction so the domain, not the persistence layer, owns its vocabulary. Vocabulary migration status: `READINESS_GRADES` is retired (readiness is no longer a stored grade, D45-L), `ELICITATION_BACKLOG_STATUSES` is replaced by the `elicitation_gaps` disposition + predicate-shape enums (D65-L), and `READINESS_BANDS` stays. Depends on: D16-L, D52-L, D54-L, D62-L, D63-L, D64-L; I26-L. Supersedes: `db/schema.ts` owning the shared enum `const` arrays and the "enum literals flow outward from `db/schema.ts`" posture; the triplicated inline `['intent','oracle','design','plan']` plane literals. #### Data model & vocabulary @@ -271,7 +271,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c - **Data gatherers** — read-only context fetchers whose output grounds proposals: **explorer** (codebase + selected-spec graph recon: `read`, `grep`, `find`, `ls`, `read_graph`) and **researcher** (web research: `web_search`, `web_fetch`). `read_graph` is granted only when the app root injects parent graph readers; no write-capable graph child exists yet. - **Projectors/reviewers** — **projector** (no tools) emits one variant of a candidate proposal from a grounding bundle and lens frame; **reviewer** (no tools) checks supplied candidate material before main-agent presentation or commitment. The main agent achieves diversity by issuing parallel `tasks: []` invocations of `projector` with intentionally distinct framings — the subagent realization of the "design it twice" pattern from `ln-design` and the parallel fan-out anticipated by `ln-oracles`. Each `projector` invocation runs in its own isolated context so variants don't cross-contaminate; the main agent collects outputs and owns any product write. This division mirrors the batch-proposal flow in D26-L. Worker-style write-capable subagents and nesting remain deferred beyond the initial `execute`/`orchestrator` standup. Cross-extension agent registration (Amos's `globalThis.__pi_subagents` bridge), raw `pi` subprocesses, and ambient `~/.pi` discovery are rejected for the POC because they conflict with profile sealing. Subagents remain an optional enhancement to candidate-proposal diversity and future delegated acquisition, not a load-bearing M0–M9 substrate. Depends on: D2-L, D26-L, D27-L, D30-L, D31-L, D39-L, D40-L, D41-L. Distinct from: D15-L Side task (non-blocking, status-via-custom-message), the deferred Side chat (user-invoked overlay; see Future Direction Register). Supersedes: subprocess/argv-shaped subagents and the `globalThis.__pi_subagents` bridge. Refined by: D90-L (shared foreground/background manifest + code-owned background discovery), D91-L (semi-permeable seal + assembled prompt + injected world), D92-L (sovereign tool grants + op_mode delegatable-set gate). -- **D90-L — Foreground and background agents share one manifest model; background discovery is code-owned (frontmatter is authoring DX, not a second agent model).** Agent definitions project into one `AgentManifest` (`id`, `kind`, `description`, `model`, `thinking`, body at the canonical `src/agents/prompts//SYSTEM.md` convention, a skills grant, a tools grant, and a `canDelegate` set naming the background agents it may spawn — D92-L/D93-L) discriminated by `kind: "foreground" | "background"` — the execution **lifecycle/host**, not a noun: a foreground agent is a live op_mode-derived Pi session; a background agent is a spawned-to-completion sealed child. The kinds keep **distinct authority sources**: a foreground agent's identity is derived from `op_mode` (D40-L) and its tool/skill legality is dynamic (op_mode policy + live gaps); a background agent's identity is caller-chosen (`{agent, task}`) and its skills/tools come from its authored manifest. DX-vs-strictness is reconciled by keeping **frontmatter as the authoring surface** for background agents while making **discovery code-owned**: the `readdir` scan over `agents/*.md` is retired for an explicit registry id list (mirroring how `state.ts` loads foreground bodies/skills through `loadSkills({ skillPaths, includeDefaults: false })`), so D39-L "no filesystem discovery" holds and frontmatter authoring survives. "subagent" stays the tool/UX noun (the main-agent tool call), not the kind name. Depends on: D39-L, D40-L, D44-L, D58-L. Refines: D44-L (the parallel frontmatter-discovered format collapses into the shared manifest; background agent bodies migrated from `src/.pi/extensions/subagents/agents/*.md` onto the canonical `src/agents/prompts//SYSTEM.md` convention, so SPEC carries one agent-body layout — D44-L and the `src/.pi/extensions/subagents/README.md` topology notes reconcile to that path). Establishing frontier: `subagent-reconciliation`. Supersedes: `readdir` filesystem discovery of subagent definitions; the standalone subagent frontmatter format as a second, separate agent model. +- **D90-L — Foreground and background agents share one manifest model; background discovery is code-owned (frontmatter is authoring DX, not a second agent model).** Agent definitions project into one `AgentManifest` (`id`, `kind`, `description`, `model`, `thinking`, body at the canonical `src/agents/prompts//SYSTEM.md` convention, a skills grant, a tools grant, and a `canDelegate` set naming the background agents it may spawn — D92-L/D93-L) discriminated by `kind: "foreground" | "background"` — the execution **lifecycle/host**, not a noun: a foreground agent is a live op_mode-derived Pi session; a background agent is a spawned-to-completion sealed child. The kinds keep **distinct authority sources**: a foreground agent's identity is derived from `op_mode` (D40-L) and its tool/skill legality is dynamic (op_mode policy + live gaps); a background agent's identity is caller-chosen (`{agent, task}`) and its skills/tools come from its authored manifest. DX-vs-strictness is reconciled by keeping **frontmatter as the authoring surface** for background agents while making **discovery code-owned**: the `readdir` scan over `agents/*.md` is retired for an explicit registry id list (mirroring how `state.ts` loads foreground bodies/skills through `loadSkills({ skillPaths, includeDefaults: false })`), so D39-L "no filesystem discovery" holds and frontmatter authoring survives. "subagent" stays the tool/UX noun (the main-agent tool call), not the kind name. Depends on: D39-L, D40-L, D44-L, D58-L. Refines: D44-L (the parallel frontmatter-discovered format collapses into the shared manifest; background agent bodies migrated from extension-local `.md` discovery onto the canonical `src/agents/prompts//SYSTEM.md` convention, so SPEC carries one agent-body layout — D44-L and the `src/.pi/extensions/subagents/README.md` topology notes reconcile to that path). Establishing frontier: `subagent-reconciliation`. Supersedes: `readdir` filesystem discovery of subagent definitions; the standalone subagent frontmatter format as a second, separate agent model. - **D91-L — Background subagents run a semi-permeable seal: explicitly-injected parent world handles plus an assembled (not verbatim) prompt; ambient leakage stays closed.** This deliberately reopens the D44-L/I29-L "no graph access, no Brunch RPC, no inherited context" clause. The seal stays closed against **ambient** leakage (in-memory auth/settings/session, no `~/.pi` discovery — D39-L intact) but opens to **explicitly injected** parent world handles the app root (`src/app/pi-subagents.ts`) supplies at spawn: the same `GraphReaders` the foreground uses scoped to the parent's `specId`, the spec/workspace context seed, and a bounded **session digest** (the parent branch flattened via `sessionManager.getBranch()`, the pattern in pi's `summarize.ts` example). The child's system prompt becomes **assembled, not verbatim**: body + a background control header (sealed child, delegated task, snapshot view) + world snapshot + a `` manifest built from the manifest's skills grant + router rules — reusing the foreground composer's extracted prompt-skill core (`renderBrunchSkills`, the skill-manifest loader) plus the selected workspace/spec seed renderer from `src/agents/contexts/seeds/turn-context.ts`, minus the foreground-only elicitation-recommendation block. World binding is **snapshot-at-spawn** (the child runs to completion against a fixed view) where the foreground is live-per-turn. Read access is asymmetric **by design**: the **session digest** is a snapshot block baked into the prompt (expensive, rarely re-pulled), while the **graph** is exposed as Brunch read tools (`read_graph` now; `read_session_context`, `read_elicitation_gaps`, … remain future grants) the child calls on demand (a recon agent iterates on graph). Return to the main agent is the ordinary tool-call result: findings re-enter main-agent context as the tool-result `content`; the structured `details` payload (`{ agent, status, text, … }`) is render-only via custom `renderCall`/`renderResult`, never model context. Write-capable children stay deferred (gated by D92-L); when they land, a `mutate_graph` against the parent's `specId` is a real side effect crossing back *outside* the tool result, and is named here so the write slice does not surprise. Depends on: D39-L, D43-L, D44-L, D58-L, D60-L, D82-L. Establishing frontier: `subagent-reconciliation`. Supersedes: the D44-L/I29-L clause that subagents have no graph access, no Brunch RPC/graph reads, no inherited world context, and a verbatim-body system prompt. - **D92-L — Background tool grants are sovereign per-agent ceilings gated by a code-owned, op_mode-keyed delegatable-set allowlist — not parent-subset containment.** The earlier containment invariant (child tools ⊆ the parent's current legal set) is rejected: delegation is **capability-inverting on purpose** — a low-privilege orchestrator (the `execute`-mode foreground agent holding only data-layer tools) may spawn a narrow high-privilege child (e.g. a file-writing worker) so writes are quarantined in a child that does one job and exits. Each background agent's tool grant is therefore **sovereign** (authored in its manifest; may exceed the parent's). The surviving safety boundary is not a tool subset but **which background agents an op_mode may spawn**: a **code-owned, op_mode-keyed delegatable-set allowlist** living beside the op_mode policy, *not* authored in frontmatter (otherwise a manifest could self-advertise into a read-only mode). This lifts D40-L's registration ≠ advertisement from tools to agents: every background agent is registered; op_mode decides which are advertised as spawnable. A read-only `elicit` session is write-safe because elicit's delegatable set **excludes** write-capable agents, not because children are subset-bounded. Enabling write tools later = author the write-capable worker manifest + add it to the relevant op_mode's delegatable set (an advertisement change), not a re-derivation of parent authority. Depends on: D39-L, D40-L, D44-L. Establishing frontier: `subagent-reconciliation`. Refined by: D93-L (the delegatable-set allowlist becomes a per-agent `AgentManifest` `canDelegate` field; for a foreground mode it is that mode's code-owned delegatable set, and it generalizes to background→background nesting). Supersedes: the parent-subset tool-containment model for subagents; D44-L's "read-only/no-tool allowlist" as the only background tool posture; the framing that write-capable subagents wait on an execute mode raising both parent and child ceilings together. - **D93-L — Operational mode and foreground agent collapse to one op-mode-keyed source of truth; the foreground roster is `elicit` / `execute` / `code`.** A foreground agent and its operational mode are 1:1 (D40-L: the foreground agent is derived from `op_mode`), so the prior **three-record fragmentation** — id enums in `src/session/schema/kinds.ts`, `OPERATIONAL_MODE_DEFINITIONS` + `AGENT_ROLE_DEFINITIONS` + `TOOL_POLICY_DEFINITIONS` in the former projections runtime-policy module, and `AGENT_PROMPT_DEFINITIONS` in `src/agents/runtime/state.ts` (which duplicated `model`/`thinking`/`allowedStrategies`/`allowedLenses` across two of them) — collapses to a **single op-mode-keyed record**. An operational mode IS `{ foreground AgentManifest (D90-L), tool policy, canDelegate set }`; background agents live in a sibling `AgentManifest` registry, and the per-agent **`canDelegate`** field (D92-L generalized from op_mode-keyed to a manifest field) links a foreground mode to the background agents it may spawn — **code-owned for foreground modes** so the write-safety boundary (I49-L) holds; it also generalizes to background→background nesting. The foreground roster is three modes: **`elicit` → `elicitor`** (current: read-only + graph-writes-via-tools; delegates to read-only gatherers); **`execute` → `orchestrator`** (live initial standup: direct shell/file writes blocked, no delegated workers yet, and one code-owned `orchestrator_stub` grant proves the non-dev runnable path; future worker delegation quarantines writes in spawned high-privilege workers per D92-L); and **`code` → `pi-coder`** (planned direct-coding assistant with direct `read`/`edit`/`write`/`bash`, **augmented to be Brunch-tooling-aware** — it also sees the graph read/capture tools). `execute` and `code` contrast deliberately: the orchestrator does not write directly, while the pi-coder will write directly (and does not delegate by default). `pi-coder` **instantiates** the augment case D58-L anticipated — its `SYSTEM.md` augments Pi's base coding-agent prompt rather than replacing it; whether other foreground roles should instead suppress/replace the base stays open per D58-L. (`code` is provisional naming for the pi-coder mode; rename is a one-token change. `code` remains in `PLANNED_OPERATIONAL_MODE_IDS` until built; `execute` is live.) Depends on: D23-L, D40-L, D58-L, D90-L, D92-L; I49-L. Establishing frontier: `subagent-reconciliation` (model + collapse land here; `execute` stood up here with the stub grant, while `code` remains declarative build-out — see PLAN). Supersedes: the three-record foreground-agent fragmentation as separate sources of truth; `defaultRole`/`allowedRoles` as a flexible many-roles-per-mode model (it is 1:1); the `executor` name for the execute-mode agent (now `orchestrator`). @@ -298,7 +298,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c - **D80-L — Generalized capture is the elicitor's banded capture sweep: in-turn, synchronous, over the un-swept transcript tail.** Capture is conduct of the foreground elicitor, not product wiring: there is no observer/auditor queue on the primary path (D18-L, reaffirmed — the v1 observer failed on structure-dependence and context starvation), no product-side LLM extraction pass on the submit paths, no gateway/translation/judgment layer between the agent and graph truth, and no capture subagent in the current block. The **banded capture sweep** is one band-ordered pass that walks intent-kind groups (the same typology the `elicitation_gaps` register references via `refersTo: NodeKind`, D65-L/D75-L), committing through the real role-named `mutateGraph` grammar (D53-L/A14-L) and moving gap dispositions through `update_elicitation_gaps`. Its input window is the **un-swept transcript tail** — all conversational and digest content since the last sweep, tracked by a **sweep watermark** (prior art: the I45-L assistant-visible watermark and the own-mutation stamp) — so capture is robust to RPC-submitted messages, interruptions, and multi-message bursts, and probes get a crisp invariant: after any elicitor turn, nothing conversational remains behind the watermark. Default is a single pass; bulk material (pastes, document reads, exploration digests) may engage an **escalation valve** — chunked/iterated sweeping within the same turn — without changing window or watermark semantics. Choreography is **capture-then-ask**: the sweep commits facts and moves gaps *before* the elicitor composes its next question, so the question provably benefits from what was just captured. Consequence: the deterministic labeled-prefix capture core (`graph/capture/structured-response.ts`), its `session.submitMessage`/`session.submitExchangeResponse` wiring, and the `capture-response-to-graph` proof are retired fossils — capture becomes turn-coupled (same agent for RPC transport clients, different moment; no coverage loss). Depends on: A14-L, A22-L, D18-L, D49-L, D53-L, D63-L, D65-L, D66-L; I45-L. Supersedes: submit-time product-side capture (the D66-L "exactly as the structured-response capture tracer does" wiring), the labeled-prefix extraction core, and the capture-quality spike's product-side extraction-pass shape. - **D81-L — Capture commitment gradient: confidence, not directness; low-confidence noticings spawn elicitation gaps.** What the sweep commits is governed by confidence in grounding, not by whether the user uttered the exact words: directly-stated facts commit with `basis: explicit`; confidently-materialized items — including implied edges and structure soundly inferred from stated content — commit with `basis: implicit`, which D63-L already licenses (agent-materialized-from-user-input); low-confidence **noticings** are never committed — the sweep's prompt directs the elicitor to spawn an `elicitation_gap` instead (`basis: implicit`, rationale citing the noticing), so the false-commit guard's positive output *is* the capture-reflection behavior: one prompted discipline discharges both the guard and the gap-writeback obligation, the agenda durably carries what was noticed, and the anti-shadowing line (D65-L) holds because the gap carries question/rationale, never domain content as truth. There is **no structural gate**: the guard is commitment rules in the sweep prompting plus the false-commit scenario matrix re-aimed at the low-confidence line and run at probe tier (some spike implication rows become legitimate implicit commits under the gradient; expected gap-spawns become assertable probe outcomes); CI guards structural legality only at the `CommandExecutor` boundary. Refines: D18-L (low-confidence material now spawns gaps rather than only "folding into later questions"; preface, D47-L, remains the orientation carrier). Depends on: A22-L, D18-L, D47-L, D63-L, D65-L. Supersedes: "implications never become graph truth" as a *directness* rule, and the spike matrix's `shouldCommit` expectations as written. - **D82-L — Ground-material acquisition is a skill-structured layer in front of the sweep; bulk modes interpose a digest; a seeded situating gap routes modes.** Questions and answers are not the only way the graph gains ground material: the elicitor must also accept arbitrary pasted content, read user-referenced workspace documents, and explore-and-characterize a brownfield codebase. These are **acquisition modes** — elicit-by-question, ingest-paste, read-referenced-documents, explore-and-characterize — structured as Brunch prompt-resource skills (D58-L manifest world), each a distinct competence the elicitor reaches for; `read-referenced-documents` and `explore-and-characterize` may use Brunch-owned static `web_fetch`/`web_search` tools registered in the sealed Pi profile (D39-L/D40-L). Acquisition varies, capture stays uniform (`acquire → digest → sweep`): everything acquired lands in the transcript behind the sweep watermark. Bulk modes (exploration, research, large document reads) interpose a **digest** — an assistant-authored characterization of what was read/found (prior art: the v1 preface-of-exchange-tuple, which proved capture should run over the summary, not the raw bulk; D47-L) — and the sweep captures from digests plus conversational content while raw tool results pass behind the watermark as background. A **situating gap** is seeded at spec creation (orientation anchors: new-from-scratch / brownfield codebase / continuation of a prior thread — the grounding-advance anchors promoted from skill prose to agenda), so the opening elicitation itself routes the session into the right acquisition mode; the gap's discharge is what licenses, e.g., explore-and-characterize. Near-future direction (not current block): exploration/research acquisition delegated to **subagents** with the digest as the handback artifact — clean main-elicitor context without observer starvation, because the subagent owns its exploration context and returns only the digest. Depends on: D47-L, D57-L, D58-L, D65-L, D80-L. Supersedes: treating conversational answers as the only capture source. -- **D60-L — Agent context splits into pull / projection / render / surface, distinguishes graph-truth from active-context reads, and keeps `workspace.state` separate.** Agent context (what the agent reasons over) spans `cwd` (filesystem kickoff heuristic — `.brunch?`, session count/length, README/markdown sizes, file counts), `graph` (overview/list/query), and `node` (variable-hop neighborhood). The architectural commitment is that agent context is a staged pipeline (pull / projection / render / surface), graph reads must make visibility/projection explicit instead of silently mixing graph-truth with active-context views, the read family must stay a named-shape surface rather than a generic records API, and `workspace.state` remains a separate product-state subject rather than an agent-context alias. Current materialized state lives in [`src/graph/README.md`](src/graph/README.md), [`src/projections/README.md`](src/projections/README.md), [`src/agents/contexts/README.md`](src/agents/contexts/README.md), [`src/app/README.md`](src/app/README.md), [`src/session/README.md`](src/session/README.md), [`src/graph/queries.ts`](src/graph/queries.ts), [`src/workspace/cwd-inventory.ts`](src/workspace/cwd-inventory.ts), and [`src/session/workspace-overview-context.ts`](src/session/workspace-overview-context.ts). Depends on: D35-L, D52-L, D53-L, D62-L, D64-L. Supersedes: pre-rendering context strings in the pull layer, scattering context-build logic across `graph/`, `.pi/agents/contexts/`, and tool adapters, or silently mixing graph-truth and active-context reads. +- **D60-L — Agent context splits into pull / projection / render / surface, distinguishes graph-truth from active-context reads, and keeps `workspace.state` separate.** Agent context (what the agent reasons over) spans `cwd` (filesystem kickoff heuristic — `.brunch?`, session count/length, README/markdown sizes, file counts), `graph` (overview/list/query), and `node` (variable-hop neighborhood). The architectural commitment is that agent context is a staged pipeline (pull / projection / render / surface), graph reads must make visibility/projection explicit instead of silently mixing graph-truth with active-context views, the read family must stay a named-shape surface rather than a generic records API, and `workspace.state` remains a separate product-state subject rather than an agent-context alias. Current materialized state lives in [`src/graph/README.md`](src/graph/README.md), [`src/projections/README.md`](src/projections/README.md), [`src/agents/contexts/README.md`](src/agents/contexts/README.md), [`src/app/README.md`](src/app/README.md), [`src/session/README.md`](src/session/README.md), [`src/graph/queries.ts`](src/graph/queries.ts), [`src/workspace/cwd-inventory.ts`](src/workspace/cwd-inventory.ts), and [`src/session/workspace-overview-context.ts`](src/session/workspace-overview-context.ts). Depends on: D35-L, D52-L, D53-L, D62-L, D64-L. Supersedes: pre-rendering context strings in the pull layer, scattering context-build logic across `graph/`, tool adapters, and ad hoc prompt-body context folders, or silently mixing graph-truth and active-context reads. - **D83-L — Context-render house style: a markdown frame (md-pen) with TOON for uniform data and a fenced ASCII tree for hierarchy, wrapped in `
` tags; agent context clusters into `` / `` / `` scopes.** Refines D60-L's RENDER stage. LLM-facing agent-context renders adopt one consistent dialect instead of ad-hoc `[bracket]` + bullet lists: - **Audience scope.** `src/agents/contexts/` owns the LLM agent-context dialect (the `
` scope-clustering plus TOON data blocks and the documents tree). human/product-only text such as print-mode `workspace.state` and debug transcript output now lives beside its app/session owner. A golden lock is audience-agnostic — it pins any stable text-output contract, human or LLM. (Open: whether `brunch print` should eventually render the house-style human views rather than a separate terse status dump — an `ln-plan` call, not assumed here.) - **Markdown frame** — built with **md-pen** (zero-dependency, GFM, CommonMark-audited), through the wrapper seam at `src/agents/contexts/primitives/markdown.ts`: headings, sections, prose, blockquote notes, inline code, fenced blocks. Retires hand-rolled markdown string concatenation. @@ -356,7 +356,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c | I27-L | Session display names are presentation metadata only: every Brunch-created session gets a neutral workspace-global default `session_info` label (`Untitled Session N`) at creation, unchanged defaults do not collide across specs in one cwd, later user/generated names may replace the default, and no naming path mutates spec identity, session binding, or graph truth. | planned (creation/boundary tests for workspace-global default allocation across specs and replacement sessions; session-lifecycle naming tests with empty transcript/auth failure/success paths; picker/chrome projection tests read session names when present) | D6-L, D21-L, D35-L, D42-L | | I26-L | Runtime schema-library imports stay deliberately scoped: Zod may appear in D41-L-acknowledged product/protocol schema seams — the structured-exchange schemas (`src/.pi/extensions/exchanges/schemas/`), the graph-owned `present_review_set` payload teaching schema co-located with its deep validator (`src/graph/review-set.ts`), and the dev-gated query-tool params (`src/.pi/extensions/{session-query,introspect-query}/`), each converting to Pi `TSchema` only through a single per-plane `z.toJSONSchema(..., { unrepresentable: 'throw' })` cast adapter (`exchanges/pi-schema.ts`, `shared/pi-tool-schema.ts`); TypeBox remains valid for unrelated Pi tool parameters (e.g. graph tools), small config/frontmatter contracts, and Drizzle-derived row schemas; no boundary may hand-author parallel Zod and TypeBox sources for the same shape. Pi tool parameter schemas authored in Zod must export JSON Schema draft 2020-12 (Zod v4 default), so tuples emit `prefixItems` rather than the draft-07 array-`items`/`additionalItems` form that strict provider validators (Anthropic) reject. Drizzle row/insert/update schemas are not hand-authored alongside their target tables. | covered (structured-exchange schema tests prove Zod parse/export and assert semantic details contracts stay in `src/.pi/extensions/exchanges/schemas/` except for the graph-owned review-set payload teaching schema imported from `src/graph/review-set.ts`; the legacy `shared/model.ts` details interface is retired; structured-exchange TypeBox usage is quarantined to the single Pi `TSchema` cast adapter in `src/.pi/extensions/exchanges/pi-schema.ts`, and the dev query tools to `src/.pi/extensions/shared/pi-tool-schema.ts`; `session-query`/`introspect-query` tests assert the advertised parameter schema is draft 2020-12 with no draft-07 tuple form; the no-direct-`db/`-imports-outside-`graph/` boundary is enforced statically by oxlint `no-restricted-imports` (`.oxlintrc.json`), with the residual `architecture.test.ts` greps covering only the db→graph kinds-only edge and `db/schema.ts` enum-array ownership that lint cannot express; Drizzle derivation via `drizzle-typebox` in `row-schemas.ts`) | D41-L | | I28-L | Auto-compaction output preserves the configured anchor set byte-stable: every entry kind listed in [src/.pi/extensions/compaction/index.ts](src/.pi/extensions/compaction/index.ts) is reconstructable post-compaction according to its `select` rule (`first | latest | active-leaves | all-unresolved`); LLM-generated narrative summary never replaces or rephrases preserved-anchor content; extension failure falls through to Pi default compaction rather than dropping anchors silently. | planned (compaction round-trip property tests at M9 plus inner-loop anchor-rendering unit tests and TypeBox schema validation of the anchor contract) | D43-L; R15, R13; I3-L, I4-L, I8-L, I12-L | -| I29-L | Subagent SDK child sessions inherit Brunch Pi Profile sealing while allowing explicitly injected parent-world reads: every `subagent` tool invocation builds an in-process `AgentSession` from explicit sealed services (in-memory auth/settings/session managers, no ambient resources, assembled background system prompt, parent model registry, explicit tool allowlist); subagents never load ambient user/project `.pi/` skills, prompts, themes, extensions, context files, or behavior-shaping settings; subagents never gain direct access to the parent's `CommandExecutor`, Brunch RPC handlers, or graph persistence; parent world access is injected by the app root as a snapshot prompt block plus selected-spec read tools such as `read_graph`; parent aborts prevent prompt execution before/during setup and abort live child sessions; subagent results return to the main agent only as tool result content (no side-effect transcript writes). | covered for the implemented SDK seam by `src/.pi/extensions/subagents/subagents.test.ts`: frontmatter/config validation (including duplicate keys), explicit registry loading from `src/.pi/agents//SYSTEM.md` while ignoring unlisted planted bodies, tool allowlist conformance for `explorer`/`projector`/`researcher`, sealed faux-provider child sessions with no inherited base prompt or conversation, assembled prompt snapshot coverage (selected spec/workspace/session digest, no foreground elicitation recommendation), unknown-tool failure, `read_graph` availability only with injected parent graph readers, parent-spec-only graph read content with sibling-spec negative assertion, bounded concurrency including waiter/new-arrival race, invalid invocation shape rejection before runner call, and parent-abort setup/live-session behavior. Startup advertisement remains dev-gated by whether a launch path supplies subagent deps to `createBrunchPiExtensions(...)`. | D2-L, D39-L, D40-L, D44-L, D91-L; I1-L, I2-L, I11-L, I24-L | +| I29-L | Subagent SDK child sessions inherit Brunch Pi Profile sealing while allowing explicitly injected parent-world reads: every `subagent` tool invocation builds an in-process `AgentSession` from explicit sealed services (in-memory auth/settings/session managers, no ambient resources, assembled background system prompt, parent model registry, explicit tool allowlist); subagents never load ambient user/project `.pi/` skills, prompts, themes, extensions, context files, or behavior-shaping settings; subagents never gain direct access to the parent's `CommandExecutor`, Brunch RPC handlers, or graph persistence; parent world access is injected by the app root as a snapshot prompt block plus selected-spec read tools such as `read_graph`; parent aborts prevent prompt execution before/during setup and abort live child sessions; subagent results return to the main agent only as tool result content (no side-effect transcript writes). | covered for the implemented SDK seam by `src/.pi/extensions/subagents/subagents.test.ts`: frontmatter/config validation (including duplicate keys), explicit registry loading from `src/agents/prompts//SYSTEM.md` while ignoring unlisted planted bodies, tool allowlist conformance for `explorer`/`projector`/`researcher`, sealed faux-provider child sessions with no inherited base prompt or conversation, assembled prompt snapshot coverage (selected spec/workspace/session digest, no foreground elicitation recommendation), unknown-tool failure, `read_graph` availability only with injected parent graph readers, parent-spec-only graph read content with sibling-spec negative assertion, bounded concurrency including waiter/new-arrival race, invalid invocation shape rejection before runner call, and parent-abort setup/live-session behavior. Startup advertisement remains dev-gated by whether a launch path supplies subagent deps to `createBrunchPiExtensions(...)`. | D2-L, D39-L, D40-L, D44-L, D91-L; I1-L, I2-L, I11-L, I24-L | | I30-L | Elicitor capture commits only high-confidence graph truth; under the D81-L gradient, directly-stated facts commit `explicit`, confidently-materialized facts/edges commit `implicit`, low-confidence noticings never become graph truth — they map to existing-or-new `elicitation_gaps` as agenda — and contradictions with existing graph truth route to `reconciliation_need` rather than gap or overwrite. | covered for deterministic routing (`src/graph/__tests__/capture-commitment-gradient-gate.test.ts` proves the FE-861 routing gate through the real `mutate_graph`, `update_elicitation_gaps`, and `update_reconciliation_needs` adapters: explicit→commit, implicit→commit, low→one gap, contradiction→one semantic-conflict recon need, structural answered derivation, manual gap close on the graph clock, illegal capture batches failing loud, and the closed capture-quality-spike scenario family re-aimed from binary `shouldCommit` to gradient `expectedOutcome` rows across free prose, file refs, implication-heavy, and contradiction classes. `src/probes/capture-quality-loop.ts` keeps the LLM-in-loop probe as fitness by scoring gradient-routing accuracy, not gating classification quality. `src/.pi/extensions/brunch-data/reconciliation/index.test.ts` proves the recon-need tool pair over `CommandExecutor`/`getOpenReconciliationNeeds` plus elicit-posture legality. `src/projections/session/sweep-watermark.test.ts` plus `src/.pi/__tests__/extension-registry.test.ts` prove the D80-L transcript-position sweep watermark: conversational/digest tail classification, raw background exclusion, idempotent marker advance, graph-LSN watermark separation, and live `before_agent_start` wiring. The submit-time labeled-prefix capture module, its `session.*` wiring, and the `capture-response-to-graph` / `submit-message-capture` proofs were deleted 2026-06-19 (D80-L fossil retirement); `session.submitMessage` / `session.submitExchangeResponse` results no longer carry a `capture` field. Confidence/dedup quality remains fitness.) | D8-L, D18-L, D47-L, D65-L, D80-L, D81-L; A22-L | | I31-L | Readiness never bars graph truth or work; it is just-in-time capability-readiness over relevant gaps, not a stored grade or kind whitelist. There is no `readiness_grade` scalar; capability availability is judged on request against the relevant `elicitation_gaps` (D74-L) and may proceed, proceed at low epistemic status, or negotiate — it never refuses outright. The `CommandExecutor` must not reject a graph node solely because its kind belongs to a later readiness band (D64-L). The soft `readiness estimate` (D45-L) is UI-only and gates nothing. Capability-readiness never *withholds a graph-write tool*: `mutate_graph` and the review-set tools stay in the active tool set regardless of readiness; `negotiate` is advisory (establishment offer + epistemic scaling), never a tool gate (D86-L). | partially covered (`src/projections/session/capability-readiness.test.ts` covers the D74-L tracer gate, including proceed / proceed_low_epistemic / negotiate, no-refusal, no grade-symbol import, and a live `presence` coverage flip; `src/projections/session/affordances.test.ts` covers the first consumer rewire: menu legality omits gated options while relevant gaps negotiate and includes them when coverage rises, with no grade symbols in `agents/runtime/policy.ts` / `affordances.ts`, and a required `NodeKind` absent from the gap register fails loud (config bug ≠ uncovered — readiness omission never masks a seeding error); `src/projections/session/readiness-estimate.test.ts` covers the soft D45-L estimate shape, empty-band zero, importance-weighted per-band coverage, honest regression, and no legality-path imports; `src/.pi/extensions/agent-runtime/runtime/state.test.ts`, `src/agents/runtime/__tests__/compose.test.ts`, `src/agents/contexts/seeds/__tests__/turn-context.test.ts`, and `src/.pi/__tests__/prompting.test.ts` cover the prompt consumer path: selected-spec gaps render as the soft per-band estimate, readiness-thin pinned axes remain visible, gated methods stay withheld, `readiness_grade=` is absent from prompt display, and the turn boundary threads the same gaps into cwd context without prompt-assembly failure; `src/session/workspace-session-coordinator.test.ts`, `src/app/__tests__/print-workspace-state.test.ts`, `src/session/workspace-overview-context.test.ts`, `src/.pi/__tests__/context-tools.test.ts`, `src/rpc/handlers.test.ts`, and `src/web/app.test.tsx` cover the workspace/chrome display retirement: `chrome.phase` / `chrome.chatMode` no longer project through coordinator/RPC/web/chrome fixtures, and workspace overview session inventory no longer carries or renders `readinessGrade`; `createSpec` / `getSpec` persistence, seed/export fixture contracts, probes, and selected-spec prompt carriers no longer persist or transport a readiness grade; the D86-L graph-write-tool-floor sub-claim is covered — `state.test.ts` proves `mutate_graph` + review-set tools stay floor while `propose-graph`/`project-graph` readiness `negotiate`s and only the non-graph-write `review-for-gaps` is withheld, and `dev/__tests__/tier-2-harness.test.ts` proves the same through a real `runBrunchTui` boot at thin vs covered grounding) | D20-L, D45-L, D64-L, D74-L, D86-L | | I32-L | Public RPC structured-exchange driving never requires a client to speak raw Pi RPC: after Brunch method discovery and workspace/spec/session activation, each pending assistant-originated exchange is answered exactly once through `session.submitExchangeResponse`, and the deterministic permutation run produces linear Pi JSONL whose structured exchange projection preserves the same prompt/answer/status/comment artifacts as the equivalent TUI structured-exchange path. | covered for deterministic FE-744 parity under canonical session method names (`session.triggerExchange`, `session.pendingExchange`, `session.submitExchangeResponse`, `session.exchanges`): `rpc.discover` contract tests, pending/respond lifecycle tests, current public-RPC structured-exchange permutations, terminal non-answered status handling, option content/rationale parity, no repeated deterministic prompts, and transcript/exchange parity assertions. | D5-L, D48-L, D49-L; I10-L, I13-L, I21-L, I23-L | @@ -581,7 +581,7 @@ src/.pi/ | **Side task** | Main-agent-invoked, non-blocking work item tracked by the Brunch `SideTaskRegistry`. The main agent fires it and does not await a return value; the only path it influences the main agent is by appending a custom-message status update to the session log that arrives at the next-turn boundary via `prepareNextTurn`. Side-task writes route through the `CommandExecutor`. Distinct from Subagent (blocking) and Side chat (user-invoked). | | **Subagent** | Main-agent-invoked, **blocking** Pi tool call (`subagent`) that runs an isolated in-process SDK child `AgentSession` with sealed services, a per-agent tool allowlist, per-agent model resolution, and no ambient resource discovery. Has no inherited conversation context, no `CommandExecutor` access, and no Brunch RPC access. Result text returns directly as tool result content. POC starter agents split into **data gatherers** (`explorer` / `researcher` — read-only context fetchers that ground proposals), a **projector** (`projector` — system-prompt-only; one variant per invocation, fan-out via parallel mode realizes the "design it twice" pattern), and a no-tool `reviewer`. | | **Projector subagent** | The system-prompt-only starter subagent that emits exactly one well-formed candidate-proposal variant per invocation given a grounding bundle plus a batch-proposal lens frame. Diversity arises from parallel `tasks: []` invocations with intentionally distinct framings; the main agent assembles outputs into review-set structured-exchange proposal details via the D31-L meta-rubric. Realizes the "design it twice" / parallel-fan-out pattern from `ln-design` and `ln-oracles` skills in subagent form. | -| **Subagent registry** | The set of registered subagent definitions loaded from the unified `src/.pi/agents//SYSTEM.md` body home through the explicit `BACKGROUND_SUBAGENT_IDS` list at extension activation. Brunch-owned only for the POC; cross-extension agent registration is deferred. | +| **Subagent registry** | The set of registered subagent definitions loaded from the unified `src/agents/prompts//SYSTEM.md` body home through the explicit `BACKGROUND_SUBAGENT_IDS` list at extension activation. Brunch-owned only for the POC; cross-extension agent registration is deferred. | | **Subagent agent definition** | A `SYSTEM.md` body with TypeBox-validated frontmatter (`name`, `description`, `tools`, `model`, `thinking`) plus a system-prompt body. The frontmatter is the authoring contract; the code-owned registry is the discovery contract; the body is the subagent's standing instructions. | | **Auto-compaction extension** | The Brunch-owned `session_before_compact` extension (`src/.pi/extensions/auto-compaction.ts`) that renders the preserved anchor set as a deterministic markdown header and prepends it to an LLM-generated narrative summary. Resolves its summarization model through the active agent definition's model preference; falls through to Pi default compaction on auth/empty-output/unexpected errors. | | **Preserved anchor set** | The configured list of transcript entry kinds and selection rules that must survive compaction byte-stable. Canonical source is [src/.pi/extensions/compaction/index.ts](src/.pi/extensions/compaction/index.ts); each rule is `{ kind, select, rationale }` where `select ∈ first | latest | active-leaves | all-unresolved`. Externalized so it can be reviewed and updated for correctness without SPEC churn. | @@ -726,7 +726,7 @@ Dev-loop artifacts route to gitignored `.fixtures/scratch///`, res | Middle | **Streaming chat transport battery (topology A — `web-driver-streaming`)** | Web-as-driver streaming relay correctness on the tier-2 faux substrate: stream↔transcript differential (message assembled from `message_update` deltas == JSONL projection), ordered incremental `AgentSessionEvent` delivery (no gaps/dupes), Pi-turn-events + Brunch-domain notifications multiplexed on one WS, live `request_answer` answer convergence through `session.answerExchange`, reconnect/resume idempotence over turn cut-points, and one-driver/many-observer fan-out (no concurrent-driver serialization — out of scope by the 2026-06-15 relaxation). Claims 1–4 are production-wired through `SessionEventRelay` and the real TUI sidecar `/rpc` transport; claim 6 is covered by a replay-less reconnect test that proves projection refetch plus live continuation without frame history; claim 7 is covered by a fan-out test that proves byte-identical concurrent observer streams plus read-only observer write rejection; command-intake slice 1 is covered by a sidecar `session.driveTurn` test that proves web-driven plain turns fan out and reduce to JSONL truth, plus contract tests that prove observer `/rpc` sockets omit live driver methods even when handles exist, driverless discovery omits `session.driveTurn`, and attached-but-not-live drivers map to `-32010`; claim 5's answered leg is covered by a sidecar `session.answerExchange` test that proves a blocked broker-backed `request_answer` promise resumes when no interactive editor is present, reduces to JSONL truth, and fans out byte-identically while observer `/rpc` sockets omit live answer methods, driverless discovery omits the method, and no-pending answers map to `-32008`; the TUI-editor precedence regression is covered by the structured-exchange request tests. Render feel stays outer-loop manual. | R12, R24; D5-L, D19-L, D37-L, D49-L, D72-L, D84-L; I22-L; A5-L; A29-L. | | Middle | Capture-analysis transcript oracle | Future `capture_*` probes persist ANALYSIS as normal Brunch toolResults, assert no graph writes occur, render full analysis in Markdown/ASCII transcripts, and assert the TUI path hides or collapses the same result without losing persisted content/details. | D17-L, D18-L, D37-L, D47-L, D50-L; I23-L, I30-L, I33-L. | | Middle | **Capture commitment-gradient routing gate + sweep-watermark property (FE-861)** | The false-commit guard is landed as a deterministic faux-substrate gate (LLM out of the loop via fixed gradient-tagged extraction) in `src/graph/__tests__/capture-commitment-gradient-gate.test.ts`: every low-confidence item is abstract-mapped to exactly one existing-or-new `elicitation_gap` and **zero** of them commit to graph truth; every explicit/implicit commit routes via the `mutateGraph` grammar with the expected basis; contradictions route to exactly one `semantic_conflict` reconciliation need via `update_reconciliation_needs`, not a gap or graph overwrite; a commit satisfying a structural (`presence`/`coverage`) gap derives `answered` (never hand-set, D65-L); `manual`-gap close routes `setElicitationGapDisposition` through the one `{specId, lsn}`/`change_log` clock (no second clock); and the closed capture-quality-spike family is re-aimed from binary `shouldCommit` to `expectedOutcome` (`commit_explicit` / `commit_implicit` / `spawn_gap` / `reconciliation_need`) with every scenario class guarded through the real adapters. `src/probes/capture-quality-loop.ts` remains the LLM-in-loop fitness probe, re-scored as gradient-routing accuracy rather than precision/recall over `shouldCommit`. The paired **sweep-watermark invariant** (prior art I45-L) is now landed in `src/projections/session/sweep-watermark.test.ts` and wired through the real `before_agent_start` product extension path in `src/.pi/__tests__/extension-registry.test.ts`: after the turn-boundary advance, no conversational/digest content remains in the un-swept tail, raw tool/background continuity may remain behind the transcript-backed marker, and the graph-LSN assistant-visible watermark is not read or moved. Per the lean steer: this gate plus the watermark property are the only deterministic capture oracles; banded-traversal quality, confidence-classification accuracy, gap/recon abstract-map/dedup quality, carry-forward/reweight feel, and digest quality stay outer-loop fitness (manual + `.brunch/debug/*`). | D8-L, D65-L, D80-L, D81-L, D85-L; A22-L; I30-L. | -| Middle | **Subagent-reconciliation oracle battery (`subagent-reconciliation`)** | Four deterministic faux-substrate oracles for the foreground/background agent reconciliation. **(1) Extraction purity** — a slice-2-entry *characterization snapshot* of the exact current foreground composed prompt must be byte-identical after the composer core is extracted (D90-L slice 2). The snapshot is trusted only as a **stability baseline** ("did the refactor change output"), not as a quality golden ("is the output good") — output quality is owned by `renderer-golden-coverage`/COMPOSE, not this guard; where an existing COMPOSE golden is *already trusted* it doubles as the tripwire, otherwise a fresh pre-extraction snapshot is captured regardless of whether the current wording is final. **(2) Code-owned discovery** — a planted unlisted `src/.pi/agents//SYSTEM.md` is not spawnable, extending the I24-L/I29-L ambient-seal tests (D90-L/D39-L). **(3) Semi-permeable seal** — a faux-provider background run asserts the assembled child prompt contains the injected world snapshot (session digest + spec/workspace), the child's Brunch graph read tool returns the parent `specId`'s graph **and never a sibling spec** (mirrors I1-L spec isolation), the ambient seal is preserved (in-memory auth/settings/session, no `~/.pi` discovery), and the result returns as tool-result `content` while `details` is render-only (D91-L). **(4) Delegatable-set write-safety boundary** — a negative-space invariant over the code-owned op_mode→delegatable-set allowlist: spawnable(op_mode) equals the allowlist, a frontmatter manifest cannot self-advertise into a read-only mode, and a **test-only write-capable background manifest** proves `elicit` refuses to spawn it — so I49-L is proven *before* the execute-mode write worker exists. Outer fitness (not gated): a real delegated-acquisition run where explorer/researcher read the live graph and return a digest, judged for usefulness. | D39-L, D40-L, D44-L, D58-L, D60-L, D82-L, D90-L, D91-L, D92-L; I29-L, I49-L. | +| Middle | **Subagent-reconciliation oracle battery (`subagent-reconciliation`)** | Four deterministic faux-substrate oracles for the foreground/background agent reconciliation. **(1) Extraction purity** — a slice-2-entry *characterization snapshot* of the exact current foreground composed prompt must be byte-identical after the composer core is extracted (D90-L slice 2). The snapshot is trusted only as a **stability baseline** ("did the refactor change output"), not as a quality golden ("is the output good") — output quality is owned by `renderer-golden-coverage`/COMPOSE, not this guard; where an existing COMPOSE golden is *already trusted* it doubles as the tripwire, otherwise a fresh pre-extraction snapshot is captured regardless of whether the current wording is final. **(2) Code-owned discovery** — a planted unlisted `src/agents/prompts//SYSTEM.md` is not spawnable, extending the I24-L/I29-L ambient-seal tests (D90-L/D39-L). **(3) Semi-permeable seal** — a faux-provider background run asserts the assembled child prompt contains the injected world snapshot (session digest + spec/workspace), the child's Brunch graph read tool returns the parent `specId`'s graph **and never a sibling spec** (mirrors I1-L spec isolation), the ambient seal is preserved (in-memory auth/settings/session, no `~/.pi` discovery), and the result returns as tool-result `content` while `details` is render-only (D91-L). **(4) Delegatable-set write-safety boundary** — a negative-space invariant over the code-owned op_mode→delegatable-set allowlist: spawnable(op_mode) equals the allowlist, a frontmatter manifest cannot self-advertise into a read-only mode, and a **test-only write-capable background manifest** proves `elicit` refuses to spawn it — so I49-L is proven *before* the execute-mode write worker exists. Outer fitness (not gated): a real delegated-acquisition run where explorer/researcher read the live graph and return a digest, judged for usefulness. | D39-L, D40-L, D44-L, D58-L, D60-L, D82-L, D90-L, D91-L, D92-L; I29-L, I49-L. | | Outer | Manual walkthrough with checklist | UX/presentation life: TUI chrome, spec/session picker, web shell feel, coherence visibility, elicitation usefulness. Adds: ambient-affordance rendering from establishment-offer structured-exchange facets; proposal/framing quality review; lens-recommendation appropriateness; review-cycle UX (approve / request-changes / reject); meta-rubric comparative-usefulness review (D31-L hypothesis test). | A17-L; R4, R14, R16, R20, R21. | | Outer | Adversarial / generative probe runs | Elicitation quality, human-gated `needs_human`, contradictory requirements, cross-session updates, long-horizon compaction, and reviewer-finding precision through small targeted probe scenarios (brief-shaped inputs are allowed, but the probe run and transcript artifacts are canonical). POC scope remains one or two known-bad scenarios per relevant invariant, not exhaustive coverage. | A5-L, A8-L, A11-L, A14-L (and validated A9-L); I4-L, I6-L, I12-L, I13-L, I16-L. | @@ -772,7 +772,7 @@ The first required probe is M0: after manual TUI interaction, a checker proves ` | I25-L | Runtime-state tests: append init/switch custom entries, reload the linear transcript, reconstruct the active `op_mode` / `strategy` / `lens` (foreground role derived from `op_mode`), tolerate stale `agentGoal` fields on old entries without re-emitting them, and verify before-agent-start/tool-call policy suppresses disallowed tools for `elicit` while selected-spec gap coverage activates commitment proposal tools. | | I26-L | Structured-exchange schema tests prove the acknowledged Zod seam parses and exports JSON Schema; future M4 architectural tests should grep/import-audit schema libraries and Drizzle row-schema derivation boundaries. | | I28-L | Inner — TypeBox schema validation of [src/.pi/extensions/compaction/index.ts](src/.pi/extensions/compaction/index.ts) shape; deterministic anchor-rendering unit tests (same branch + same config → same header bytes). Middle (M9) — compaction round-trip property tests across all configured anchors and selection rules; fallback-to-Pi-default behavior under simulated auth failure, empty LLM output, and thrown error. Outer (M9) — long-horizon adversarial fixture confirms session binding, latest runtime state, latest establishment offer, in-flight side-task results, and unresolved staleness hints remain agent-intelligible post-compaction. | -| I29-L | Inner — SDK child-session tests prove sealed service construction, agent-body system prompt ownership, no inherited parent conversation, explicit tool allowlists per starter agent, no-tools projector/reviewer behavior, duplicate/malformed frontmatter failure, explicit registry discovery from `src/.pi/agents//SYSTEM.md`, config validation, bounded concurrency, invalid caller-shape rejection before runner invocation, and parent-abort behavior before/during setup and after session creation. Middle — when startup wiring lands, a product-path smoke should prove the launch gate supplies deps intentionally and ordinary elicit sessions without deps do not register/advertise `subagent`. Outer — probe-driven proposal-generation or delegated-acquisition runs invoking explorer/researcher/projector/reviewer confirm subagent outputs ground proposals/digests without bypassing primary authority. | +| I29-L | Inner — SDK child-session tests prove sealed service construction, agent-body system prompt ownership, no inherited parent conversation, explicit tool allowlists per starter agent, no-tools projector/reviewer behavior, duplicate/malformed frontmatter failure, explicit registry discovery from `src/agents/prompts//SYSTEM.md`, config validation, bounded concurrency, invalid caller-shape rejection before runner invocation, and parent-abort behavior before/during setup and after session creation. Middle — when startup wiring lands, a product-path smoke should prove the launch gate supplies deps intentionally and ordinary elicit sessions without deps do not register/advertise `subagent`. Outer — probe-driven proposal-generation or delegated-acquisition runs invoking explorer/researcher/projector/reviewer confirm subagent outputs ground proposals/digests without bypassing primary authority. | | I30-L | FE-807 covered the now-superseded labeled-text response tracer (D80-L retires it). The FE-861 **capture commitment-gradient routing gate** is now landed for the full closed matrix (explicit/implicit commits via `mutateGraph`; low-confidence never commits and maps to one gap; contradictions route to `semantic_conflict` reconciliation needs; structural gaps derive `answered`; `manual`-gap close on the one `{specId, lsn}` clock; binary `shouldCommit` retired in favor of gradient `expectedOutcome`). The paired **sweep-watermark property** is landed (`sweep-watermark.test.ts` + live `before_agent_start` wiring), and the submit-time labeled-prefix fossil + its `capture-response-to-graph` / `submit-message-capture` proofs are now deleted (D80-L fossil retirement). Confidence-classification accuracy and gap/recon dedup quality stay fitness/blind-spot (see below). | | I31-L | Capability-readiness tests proving live gap coverage negotiates/unlocks later actions without disabling gathering/refinement; prompt/tool-policy tests proving readiness-thin pinned axes still compose while gated methods stay withheld; graph write tests proving later-band node kinds are not rejected solely because grounding is thin. | | I32-L | FE-744 public-RPC structured-exchange parity proof: `rpc.discover` contract tests, pending/respond lifecycle tests, deterministic permutation run over Brunch JSON-RPC only, no repeated deterministic prompts, and parity assertions over the resulting Pi JSONL, transcript display, and session exchange projections. | diff --git a/src/README.md b/src/README.md index 2af96551b..c57342c1c 100644 --- a/src/README.md +++ b/src/README.md @@ -74,4 +74,4 @@ The old domain-local `src/{graph,session,structured-exchange}/format/` folders a Runtime-state transcript entry facts live in `session/runtime-state.ts`; reusable flattened runtime-state projection lives in `projections/session/runtime-state.ts`, while foreground roster/tool policy lives in `agents/runtime/policy.ts`. -The earlier `src/agents/` top-level prompt subtree had moved under `src/.pi/{agents,skills}/`; the new `src/agents/` seam reclaims the name for Pi-independent LLM context ingress. Agent bodies have moved to `src/agents/prompts/`; prompt-resource skills have moved to `src/agents/skills/`. The old `src/.pi/context/` prompt-pack subtree remains retired. +The current `src/agents/` seam owns Pi-independent LLM context ingress. Agent bodies live in `src/agents/prompts/`; prompt-resource skills live in `src/agents/skills/`; prompt composition and legality live in `src/agents/runtime/`. The old `src/.pi/context/` prompt-pack subtree remains retired. From fb8be35af1c1dcf43f481e76aa6b688a26a4c7af Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Fri, 26 Jun 2026 09:50:29 +0200 Subject: [PATCH 34/54] Rename context surface test scripts --- README.md | 12 +++++------- memory/REFACTOR.md | 2 +- package.json | 6 +++--- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index b983a8f5d..d47d25ae7 100644 --- a/README.md +++ b/README.md @@ -107,21 +107,19 @@ The `src/` topology follows the current architecture decision in [`memory/SPEC.m ```text src/ -├── .pi/ # sealed Pi runtime surface: agents, skills, components, extensions -│ ├── agents/ # Brunch foreground/background agent role prompts +├── .pi/ # sealed Pi runtime surface: components, extensions, settings │ ├── components/ # reusable Pi TUI/message components │ ├── extensions/ # Brunch Pi registrars: tools, hooks, commands, UI affordances -│ ├── skills/ # Brunch agent skills read on demand by the runtime │ └── settings.json # dev-only ambient Pi settings when launching from src/ -├── app/ # CLI mode dispatch and product host wiring +├── agents/ # agent prompts, prompt-resource skills, runtime policy, model-facing context +├── app/ # CLI mode dispatch, product host wiring, and print-mode text ├── db/ # Drizzle schema, migrations, and SQLite connection lifecycle ├── dev/ # dev-only harnesses and proof tests ├── graph/ # graph domain, schema, readers, policy, and CommandExecutor ├── probes/ # product/proof drivers and reportable oracle runs ├── projections/ # structured DTOs derived from domain/session/tool facts -├── renderers/ # lossy text, markdown, and display renderers ├── rpc/ # Brunch JSON-RPC protocol, handlers, registry, and web host -├── session/ # Pi JSONL transcript projection, exchanges, runtime state, coordination +├── session/ # Pi JSONL transcript projection, exchanges, runtime state, transcript text ├── utils/ # small shared utilities ├── web/ # React browser sidecar over Brunch RPC ├── workspace/ # cwd/package identity and .brunch workspace state helpers @@ -134,7 +132,7 @@ Important boundaries: - `graph/` is the only application layer that imports `db/` directly. - `rpc/` exposes named Brunch product methods, not a generic records API. - `web/` consumes Brunch RPC projections only; it must not read SQLite, Pi RPC, local JSONL, or `.brunch/workspace.json` directly. -- `projections/` preserves reusable structure; `renderers/` may lose structure for human-readable output. +- `projections/` preserves reusable structure; `agents/contexts/` owns model-facing text; human/product text lives beside its app/session owner. ## Architecture Docs diff --git a/memory/REFACTOR.md b/memory/REFACTOR.md index 3a7049610..35d512ff6 100644 --- a/memory/REFACTOR.md +++ b/memory/REFACTOR.md @@ -48,7 +48,7 @@ The judo move is deletion: remove the affordance wrapper instead of relocating i - [x] Delete the runtime-affordance projection wrapper and point its remaining test obligations at the canonical agent-runtime policy functions. Keep the required/deferred affordance ledger but make it cite the runtime owner, not a projection module. - [x] Reconcile projection topology docs and boundary tests so session projections no longer claim capability-readiness or affordances as projection-owned modules. - [x] Reconcile durable SPEC and README path fossils from the retired prompt-body home to the current prompt home. -- [ ] Rename the renderer test scripts to match the current context/text-surface topology, preserving backwards compatibility only if there is a real user-facing reason; otherwise delete the old renderer names. +- [x] Rename the renderer test scripts to match the current context/text-surface topology, preserving backwards compatibility only if there is a real user-facing reason; otherwise delete the old renderer names. - [ ] Retire this refactor plan once the cleanup is committed and the normal verification gate passes. ## Decisions diff --git a/package.json b/package.json index 923d556b8..c62c9bc06 100644 --- a/package.json +++ b/package.json @@ -43,9 +43,9 @@ "db:studio": "drizzle-kit studio", "test": "vitest --run", "test:watch": "vitest", - "test:renderers": "vitest --run src/agents/contexts src/app/__tests__/print-workspace-state.test.ts src/session/__tests__/transcript-markdown.test.ts", - "test:renderers:watch": "vitest src/agents/contexts src/app/__tests__/print-workspace-state.test.ts src/session/__tests__/transcript-markdown.test.ts", - "test:renderers:update": "vitest --run src/agents/contexts src/app/__tests__/print-workspace-state.test.ts src/session/__tests__/transcript-markdown.test.ts --update", + "test:context-surfaces": "vitest --run src/agents/contexts src/app/__tests__/print-workspace-state.test.ts src/session/__tests__/transcript-markdown.test.ts", + "test:context-surfaces:watch": "vitest src/agents/contexts src/app/__tests__/print-workspace-state.test.ts src/session/__tests__/transcript-markdown.test.ts", + "test:context-surfaces:update": "vitest --run src/agents/contexts src/app/__tests__/print-workspace-state.test.ts src/session/__tests__/transcript-markdown.test.ts --update", "test:prompts": "vitest --run src/agents/runtime src/.pi/extensions/agent-runtime/system-prompts", "test:prompts:watch": "vitest src/agents/runtime src/.pi/extensions/agent-runtime/system-prompts", "test:prompts:update": "vitest --run src/agents/runtime src/.pi/extensions/agent-runtime/system-prompts --update", From fa1bbb838f7681170b88a0f24e2e49fd56e4f6cb Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Fri, 26 Jun 2026 09:51:23 +0200 Subject: [PATCH 35/54] Retire runtime topology refactor plan --- memory/REFACTOR.md | 78 ---------------------------------------------- 1 file changed, 78 deletions(-) delete mode 100644 memory/REFACTOR.md diff --git a/memory/REFACTOR.md b/memory/REFACTOR.md deleted file mode 100644 index 35d512ff6..000000000 --- a/memory/REFACTOR.md +++ /dev/null @@ -1,78 +0,0 @@ -## Problem Statement - -The last topology pass removed the orphan renderer layer and moved the foreground roster into the agent runtime owner, but two policy seams still keep the agent-runtime model from reading as one settled topology. - -First, capability-readiness is still housed in the session projection area even though it is runtime posture policy: it decides whether strategy/lens/method surfaces are legal or advisory for an agent turn. Agent runtime policy imports it, so the policy owner still depends on a projection module for its own gating vocabulary. - -Second, the runtime affordance projection is now a test-only wrapper around agent-runtime policy. It no longer earns a reusable DTO layer: the real policy functions already live in the agent runtime owner, and there is no product/RPC/web consumer for the wrapper shape. - -A small amount of durable documentation and script vocabulary also preserves old topology names: subagent proof text still names the retired prompt-body home, and test scripts still say renderers after the renderer layer was deleted. - -One unrelated but current gate failure remains: the settings-audit test is red because Pi added a getter that the audited Brunch settings list has not acknowledged. The refactor cannot be considered safely complete while the normal verification gate is red. - -```pseudo tree -current residual shape -src/ -├── agents/runtime/ -│ ├── policy foreground roster + axis/tool policy -│ └── state manifest/tool activation policy -├── projections/session/ -│ ├── capability-readiness agent capability policy still outside agents/runtime -│ ├── affordances test-only wrapper over agents/runtime policy -│ └── runtime-state real reusable transcript DTO projection -└── package scripts/docs old renderer and prompt-home vocabulary remains -``` - -## Solution - -Collapse runtime posture policy fully under the agent runtime owner, delete the test-only projection wrapper, and reconcile vocabulary so durable truth matches the new topology. Keep the projection layer focused on reusable DTOs over session/transcript facts. - -```pseudo tree -desired residual shape -src/ -├── agents/runtime/ -│ ├── policy -│ ├── state -│ └── capability-readiness capability→gap policy used by policy/state -├── projections/session/ -│ └── runtime-state + transcript/readiness DTOs only -└── scripts/docs current names: context/text surfaces, agents/prompts -``` - -The judo move is deletion: remove the affordance wrapper instead of relocating it, and move only the capability policy that is still load-bearing. - -## Commits - -- [x] Restore the verification gate by acknowledging the new audited Pi settings getter in the Brunch settings boundary test/policy, without broadening any ambient settings behavior. -- [x] Move capability-readiness into the agent runtime owner and update direct consumers/tests so agent posture policy no longer imports capability policy from the projection layer. -- [x] Delete the runtime-affordance projection wrapper and point its remaining test obligations at the canonical agent-runtime policy functions. Keep the required/deferred affordance ledger but make it cite the runtime owner, not a projection module. -- [x] Reconcile projection topology docs and boundary tests so session projections no longer claim capability-readiness or affordances as projection-owned modules. -- [x] Reconcile durable SPEC and README path fossils from the retired prompt-body home to the current prompt home. -- [x] Rename the renderer test scripts to match the current context/text-surface topology, preserving backwards compatibility only if there is a real user-facing reason; otherwise delete the old renderer names. -- [ ] Retire this refactor plan once the cleanup is committed and the normal verification gate passes. - -## Decisions - -- Agent runtime owns capability-readiness because it is posture/tool/resource legality policy, not an information-preserving session DTO. -- Session projections keep runtime-state and transcript/readiness DTOs; they do not own agent roster, agent body locations, tool policy, capability gates, or affordance menus. -- The runtime-affordance wrapper is deleted unless a real product/RPC/web consumer appears during implementation; tests should not justify a production module. -- The required/deferred affordance ledger remains useful, but it should name the canonical runtime policy owner directly. -- Script names should reflect live topology. Retired renderer vocabulary should not remain unless explicitly preserved as a compatibility alias. -- The settings-audit gate repair is a prerequisite cleanup, not part of the agent-context topology model. -- Topology READMEs touched: root source topology, agents runtime, projections, session, and any Pi/subagent docs that still name retired prompt-body paths. - -## Testing Decisions - -- Capability-readiness tests move with the module and keep the same behavioral oracle: capability→gap map, proceed / low-epistemic / negotiate outcomes, no refusal state, live coverage flip, and loud failure for missing required gap kinds. -- Runtime-policy tests should own axis/menu legality directly after deleting the affordance wrapper: AUTO excludes freestyle, pin surfaces retain freestyle, gated lenses negotiate on uncovered gaps, and missing gap registers fail loud. -- Projection boundary tests should prove projections no longer import or own agent runtime policy beyond the explicit runtime-state DTO consumer edge. -- The settings-audit test should pass by explicitly tracking the new getter; do not loosen the audit into a wildcard. -- Final gate is `npm run verify`; the pass is not done while the known settings-audit failure remains. - -## Out of Scope - -- Changing capability-readiness semantics, readiness bands, graph-write floor behavior, or the no-refusal invariant. -- Adding new runtime affordance transport to RPC/web. -- Reworking the foreground roster shape, prompt bodies, or prompt-resource skill content. -- Touching the extra Knip configuration work except to preserve it as someone else's already-tracked work if it remains in the branch. -- Any broader cleanup of archived planning history beyond current durable SPEC/README references that would mislead active work. From d776d10d1c50d005f268ab14bcc161070cf581a8 Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Fri, 26 Jun 2026 10:04:01 +0200 Subject: [PATCH 36/54] Reconcile runtime topology references --- memory/SPEC.md | 8 +++---- ...orchestrator-tool-port--plan-check-tool.md | 22 +++++++++---------- src/agents/runtime/__tests__/state.test.ts | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/memory/SPEC.md b/memory/SPEC.md index 3c7b20092..6fdd31ec6 100644 --- a/memory/SPEC.md +++ b/memory/SPEC.md @@ -312,7 +312,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c - **Rollout** — incremental: ``, ``, graph, session runtime-frame, and structured-exchange result renders now live under `src/agents/contexts/`; transcript debug/report rendering lives in `src/session/transcript-markdown.ts` as a human/product debug artifact. - **Closed audit** — per-session `turnCount` is derived once while inspecting canonical session files and counts only current Pi v3 JSONL message entries (`type: "message"` with `message.role: "user" | "assistant"`); tool/custom entries are excluded, and downstream workspace/specification overview renders reuse that inspected count rather than reparsing the file. - **D85-L — Brunch prompt-resource axis model: two AUTO axes (`strategy`, `lens`); `goal` inlined into the agent role; graph-write mechanism is method-routed, not a strategy.** A 2026-06-18 grill consolidation of the `agents/skills/` topology and the D58-L manifest axes, implemented across FE-893, FE-861, and FE-898. Four locked moves: - 1. **Two AUTO objective axes, not three.** The runtime manifest advertises only `strategy` and `lens`; **`goal` is dropped as a manifest/runtime axis**. The four goal postures (`grounding-advance`, `elicit-expand`, `commit-converge`, always-on `capture-posture`) **inline into the `elicitor` agent role prompt** (`src/agents/prompts/elicitor/SYSTEM.md`), selected inline by the agent from the pushed readiness-band/posture context. Rationale: `goal` was already internal/readiness-derived and not user-mutable (D59-L), so advertising it as an AUTO-selectable axis was indirection over what is agent-directed-by-band anyway. Consequences for the build: `compose.ts` drops the `` family, `manifestsForState` drops `goals`, `runtime-state.ts` / `agents/runtime/policy.ts` / `affordances.ts` drop the `goal` axis slot, and the runtime header drops the goal line. Capability-readiness (D74-L) is unaffected — it keys on gaps, not goal. + 1. **Two AUTO objective axes, not three.** The runtime manifest advertises only `strategy` and `lens`; **`goal` is dropped as a manifest/runtime axis**. The four goal postures (`grounding-advance`, `elicit-expand`, `commit-converge`, always-on `capture-posture`) **inline into the `elicitor` agent role prompt** (`src/agents/prompts/elicitor/SYSTEM.md`), selected inline by the agent from the pushed readiness-band/posture context. Rationale: `goal` was already internal/readiness-derived and not user-mutable (D59-L), so advertising it as an AUTO-selectable axis was indirection over what is agent-directed-by-band anyway. Consequences for the build: `compose.ts` drops the `` family, `manifestsForState` drops `goals`, `runtime-state.ts` / `agents/runtime/policy.ts` drop the `goal` axis slot, and the runtime header drops the goal line. Capability-readiness (D74-L) is unaffected — it keys on gaps, not goal. 2. **Graph-write mechanism is method-routed, not a strategy-axis member.** `propose-graph` (direct-commit) and `project-graph` (review-set) describe the **graph-write capability ids** (the D26-L commitment mechanisms), not interaction shape; their strategy names are retired rather than rehomed. The existing methods absorb the mechanics: `commit-graph` carries direct-commit mechanics, and `generate-proposal` carries review-set mechanics. The offer→accept / derive→review choreography lives in the inlined `commit-converge` posture, not in method bodies. The graph-write readiness gate was originally placed on those method ids via capability-readiness (**removed by D86-L**: the graph-write methods are floor — readiness is advisory for them, never a tool gate), while the `strategy` axis keeps only genuine interaction shapes: `step-wise-decision-tree`, `step-wise-disambiguate`, and `freestyle` (AUTO-excluded, D66-L). 3. **Gap-reflection conduct belongs to the capture skill, not `review-for-gaps`.** D81-L spawn-on-noticing + close-on-answered is **always-on capture-sweep conduct** (every elicitor turn), so it lives with the D80-L capture skill, not an optionally-selected method. `review-for-gaps` is demoted to the **deliberate-audit sense only** (missing support, contradictions, verification debt). Read/interpret-gap semantics stay on the `read_elicitation_gaps` tool description (tool-local), not duplicated into a skill; the D81-L commitment gradient lives once, in the capture skill, with gap-spawn as its third outlet. 4. **The prompt-content rewrite is design work entangled with live/stubbed seams — not a keyword fossil sweep.** The strategy/method bodies drift and overlap, but audit (2026-06-18) found their suspect tokens are mostly *not* dead history: `tool_meta` is live across every exchange projection; `capture_*` is a live `tool_meta.next` sequencing marker (`present_* → request_* → capture_*`), distinct from the D80-L-retired labeled-prefix capture core; and `present_candidates` + `user_rubric` / `meta_rubric` / `graph_refs` are the **anticipated payload of the live candidate topology stub** (`projections/exchanges/present-candidates.ts`, PLAN-confirmed stubbed), not removable fossils. Only `renderCall` is genuinely unreferenced (confirm against the Pi display API before removal). Rewriting prompt content must reconcile against the candidate stub, the exchange `tool_meta` model, and the D80-L sweep model rather than strip by keyword. Lexicon sweep in the same pass: `elicitation backlog` → `elicitation gaps` / `coverage obligation` (the D65-L rename; the inlined `elicit-expand` posture in `elicitor/SYSTEM.md` still carries the old term after the goal-axis drop relocated it). @@ -352,20 +352,20 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c | I22-L | Brunch TUI startup must not render prior session transcript entries or enter an agent loop until the user has explicitly activated a spec/session decision; creating a new spec implicitly creates its first session, creating a new session for an existing spec lands in a binding-only session, resuming a prior transcript is opt-in, and RPC/headless startup exposes structured initial-selection state rather than invoking TUI picker code. | covered (FE-744 coordinator tests; hierarchical spec/session picker model + component tests; `workspace.selectionState` / `workspace.activate` JSON-RPC contract tests with source assertion that RPC does not import TUI picker code; `src/probes/scripts/verify-startup-no-resume.sh` pty/ANSI-stripped TUI probe oracle proving stale transcript text is absent before explicit activation) | D11-L, D21-L, D22-L, D36-L | | I23-L | Every structured elicitation interaction that owns the response surface persists durable semantic display only through Pi `toolResult` rows rendered by `renderResult`; `renderCall` and live `ctx.ui.*` surfaces are transient. A structured-exchange tuple has a recoverable `present_*` result and, when required, exactly one terminal response result before the next agent turn consumes it; `present_question` derives free-text vs choice vs multi-choice from its own structure and `request_response` is the single terminal tool that routes every present's response (free-text/choice/multi-choice for `present_question`, review for `present_review_set`) from pending transcript state. The target details model is checked by `schema` + `v`, `exchange_id`, and `tool_meta`; request outcomes are an exactly-one property-presence union; user-authored text is `comment` and runtime-authored text is `message`; present-side status/kind/expected-request aliases and capture graph payloads are invalid in the Zod-authored schema layer. `toolResult.content` is rich markdown suitable for both TUI transcript display and model context; `toolResult.details` carries structured projection/recovery data. In TUI-driven sessions, `request_response` answers free-text prompts from the interactive editor when present; the live exchange broker is the headless/web-driver fallback, not an override of the TUI response surface. | covered for current structured-exchange tools (registered sequential `present_question`, `present_review_set`, and `request_response`; retired `present_options`, `request_answer`, `request_choice`, `request_choices`, and `request_review` tools are unregistered while their request result-detail discriminants are preserved; runtime details are emitted from canonical `schema`/`v`/snake_case Zod shapes; tests cover non-semantic `renderCall`, markdown `renderResult`, present/request details, unmatched-present recovery, active-vs-stub registry, JSON-editor fallback for multi-choice, TUI-editor precedence over an attached live broker for `request_response`, broker fallback when no interactive editor exists, `request_response` for `present_question` through the shared answer-source and choice-source dispatchers (free-text editor/broker/cancellation, TUI-only single-choice and multi-choice, headless choice unavailable, unknown diagnostics, and recovery continuation), `request_response` for `present_review_set` through the shared review source (approve/request-changes/reject with required change-request comment, cancellation, headless unavailable, emitting preserved `request_review` result details), terminal `answered`/`cancelled`/`unavailable` projection closure, option content/rationale parity, review-set `nodes`/`edges` details parity, invalid review proposal non-recovery, review pending-exchange recovery, public-RPC deterministic permutations, capture response-to-graph proof, and same-assistant-message `present_question → request_response` ordering over a real Pi RPC run. The Zod-authored schema layer is covered by JSON Schema export, drift-rejection, and source-boundary tests for present/request/capture details; `present_review_set.payload` imports the graph-owned boundary-teaching payload schema (not `z.unknown()`), so a JSON-string payload, the `mutate_graph` `{createBasis, ops}` shape, or malformed nested companions such as `grounding: string` are rejected at the param boundary rather than deep in the executor, while requiredness and field-level structural diagnostics stay owned by `graph/review-set.ts`. `present_question`'s params make the present-side choice structural: `options[]` presence selects choice mode and `multiple` selects multi-choice, so the model no longer chooses between separate question/options tools. `present_candidates` remains a named stub and intentionally unregistered.) | D12-L, D13-L, D17-L, D37-L, D38-L, D41-L | | I24-L | A Brunch-launched Pi runtime does not load ambient user/project Pi context files, extensions, skills, prompt templates, themes, or behavior-shaping settings unless Brunch's sealed Pi settings/extension boundary explicitly allows them; Brunch-owned extension-discovered resources are identified as intentional product resources. | covered for TUI-launch settings/extension boundary by contract tests: ambient resource flags and explicit extension factories are preserved; hostile ambient global/project settings are ignored by the in-memory Brunch settings policy before and after reload; audited Pi settings getters are tracked in `src/.pi/brunch-pi-settings.ts`. Subagent child-session sealing is covered separately under I29-L. | D2-L, D39-L | -| I25-L | The active `op_mode`, `strategy`, and `lens` are reconstructable from linear `brunch.agent_runtime_state` entries at turn start and through `session.runtimeState`; concrete axis ids stay separate from the `auto` selection sentinel; the foreground session-agent role is derived from `op_mode`, not separately stored; tool gating follows the reconstructed `op_mode` so `elicit` cannot use execute/dangerous tools such as raw `bash`/`write` unless explicitly permitted. Runtime-state projection remains transcript-backed and exposes empty/default mention, world-watermark, and lifecycle slots without inventing hidden extension memory; legal option/default affordances are pure projections over resolved runtime state plus capability-readiness over gaps (D74-L), not persisted state. | covered (`src/session/runtime-state.test.ts` covers default state, cumulative last-writer-wins posture, mention/world/lifecycle slot projection, and non-linear rejection; `src/rpc/handlers.test.ts` covers explicit-target `session.runtimeState` discovery/params/spec validation; `src/.pi/__tests__/operational-mode.test.ts` covers append/project/switch helpers over the reconciled two-axis vocabulary, AUTO selection for every runtime axis, stale `agentGoal` tolerance on existing transcript entries, init idempotence, previous-state values, malformed/illegal tuple rejection, role derivation from `op_mode`, and Pi JSONL reload projection; `prompting.test.ts` covers prompt/tool-policy projection from the same transcript-backed runtime state, including selected-spec gap activation for `present_review_set` / `request_review` proposal tools; `src/.pi/extensions/agent-runtime/runtime/authority-matrix.test.ts` covers the current POC authority matrix for `elicit-read-only`, including base-allowed local/web read tools, blocking `bash`/`edit`/`write`, and structured `needs_human` result representability while leaving A18-L strict built-in suppression as residue; `src/projections/session/affordances.test.ts` covers shared strategy/lens legal options, defaults, AUTO freestyle exclusion, pinned freestyle, gap-driven gated legality, and a live coverage flip; `src/session/runtime-affordances-coverage.test.ts` guards the required-vs-deferred affordance ledger). | D17-L, D23-L, D40-L, D58-L, D59-L, D66-L, D85-L | +| I25-L | The active `op_mode`, `strategy`, and `lens` are reconstructable from linear `brunch.agent_runtime_state` entries at turn start and through `session.runtimeState`; concrete axis ids stay separate from the `auto` selection sentinel; the foreground session-agent role is derived from `op_mode`, not separately stored; tool gating follows the reconstructed `op_mode` so `elicit` cannot use execute/dangerous tools such as raw `bash`/`write` unless explicitly permitted. Runtime-state projection remains transcript-backed and exposes empty/default mention, world-watermark, and lifecycle slots without inventing hidden extension memory; legal option/default affordances are pure agent-runtime policy derivations over resolved runtime state plus capability-readiness over gaps (D74-L), not persisted state. | covered (`src/session/runtime-state.test.ts` covers default state, cumulative last-writer-wins posture, mention/world/lifecycle slot projection, and non-linear rejection; `src/rpc/handlers.test.ts` covers explicit-target `session.runtimeState` discovery/params/spec validation; `src/.pi/__tests__/operational-mode.test.ts` covers append/project/switch helpers over the reconciled two-axis vocabulary, AUTO selection for every runtime axis, stale `agentGoal` tolerance on existing transcript entries, init idempotence, previous-state values, malformed/illegal tuple rejection, role derivation from `op_mode`, and Pi JSONL reload projection; `prompting.test.ts` covers prompt/tool-policy projection from the same transcript-backed runtime state, including selected-spec gap activation for `present_review_set` / `request_review` proposal tools; `src/.pi/extensions/agent-runtime/runtime/authority-matrix.test.ts` covers the current POC authority matrix for `elicit-read-only`, including base-allowed local/web read tools, blocking `bash`/`edit`/`write`, and structured `needs_human` result representability while leaving A18-L strict built-in suppression as residue; `src/agents/runtime/__tests__/policy.test.ts` covers shared strategy/lens legal options, defaults, AUTO freestyle exclusion, pinned freestyle, gap-driven gated legality, and loud missing-gap failures; `src/session/runtime-affordances-coverage.test.ts` guards the required-vs-deferred affordance ledger). | D17-L, D23-L, D40-L, D58-L, D59-L, D66-L, D85-L | | I27-L | Session display names are presentation metadata only: every Brunch-created session gets a neutral workspace-global default `session_info` label (`Untitled Session N`) at creation, unchanged defaults do not collide across specs in one cwd, later user/generated names may replace the default, and no naming path mutates spec identity, session binding, or graph truth. | planned (creation/boundary tests for workspace-global default allocation across specs and replacement sessions; session-lifecycle naming tests with empty transcript/auth failure/success paths; picker/chrome projection tests read session names when present) | D6-L, D21-L, D35-L, D42-L | | I26-L | Runtime schema-library imports stay deliberately scoped: Zod may appear in D41-L-acknowledged product/protocol schema seams — the structured-exchange schemas (`src/.pi/extensions/exchanges/schemas/`), the graph-owned `present_review_set` payload teaching schema co-located with its deep validator (`src/graph/review-set.ts`), and the dev-gated query-tool params (`src/.pi/extensions/{session-query,introspect-query}/`), each converting to Pi `TSchema` only through a single per-plane `z.toJSONSchema(..., { unrepresentable: 'throw' })` cast adapter (`exchanges/pi-schema.ts`, `shared/pi-tool-schema.ts`); TypeBox remains valid for unrelated Pi tool parameters (e.g. graph tools), small config/frontmatter contracts, and Drizzle-derived row schemas; no boundary may hand-author parallel Zod and TypeBox sources for the same shape. Pi tool parameter schemas authored in Zod must export JSON Schema draft 2020-12 (Zod v4 default), so tuples emit `prefixItems` rather than the draft-07 array-`items`/`additionalItems` form that strict provider validators (Anthropic) reject. Drizzle row/insert/update schemas are not hand-authored alongside their target tables. | covered (structured-exchange schema tests prove Zod parse/export and assert semantic details contracts stay in `src/.pi/extensions/exchanges/schemas/` except for the graph-owned review-set payload teaching schema imported from `src/graph/review-set.ts`; the legacy `shared/model.ts` details interface is retired; structured-exchange TypeBox usage is quarantined to the single Pi `TSchema` cast adapter in `src/.pi/extensions/exchanges/pi-schema.ts`, and the dev query tools to `src/.pi/extensions/shared/pi-tool-schema.ts`; `session-query`/`introspect-query` tests assert the advertised parameter schema is draft 2020-12 with no draft-07 tuple form; the no-direct-`db/`-imports-outside-`graph/` boundary is enforced statically by oxlint `no-restricted-imports` (`.oxlintrc.json`), with the residual `architecture.test.ts` greps covering only the db→graph kinds-only edge and `db/schema.ts` enum-array ownership that lint cannot express; Drizzle derivation via `drizzle-typebox` in `row-schemas.ts`) | D41-L | | I28-L | Auto-compaction output preserves the configured anchor set byte-stable: every entry kind listed in [src/.pi/extensions/compaction/index.ts](src/.pi/extensions/compaction/index.ts) is reconstructable post-compaction according to its `select` rule (`first | latest | active-leaves | all-unresolved`); LLM-generated narrative summary never replaces or rephrases preserved-anchor content; extension failure falls through to Pi default compaction rather than dropping anchors silently. | planned (compaction round-trip property tests at M9 plus inner-loop anchor-rendering unit tests and TypeBox schema validation of the anchor contract) | D43-L; R15, R13; I3-L, I4-L, I8-L, I12-L | | I29-L | Subagent SDK child sessions inherit Brunch Pi Profile sealing while allowing explicitly injected parent-world reads: every `subagent` tool invocation builds an in-process `AgentSession` from explicit sealed services (in-memory auth/settings/session managers, no ambient resources, assembled background system prompt, parent model registry, explicit tool allowlist); subagents never load ambient user/project `.pi/` skills, prompts, themes, extensions, context files, or behavior-shaping settings; subagents never gain direct access to the parent's `CommandExecutor`, Brunch RPC handlers, or graph persistence; parent world access is injected by the app root as a snapshot prompt block plus selected-spec read tools such as `read_graph`; parent aborts prevent prompt execution before/during setup and abort live child sessions; subagent results return to the main agent only as tool result content (no side-effect transcript writes). | covered for the implemented SDK seam by `src/.pi/extensions/subagents/subagents.test.ts`: frontmatter/config validation (including duplicate keys), explicit registry loading from `src/agents/prompts//SYSTEM.md` while ignoring unlisted planted bodies, tool allowlist conformance for `explorer`/`projector`/`researcher`, sealed faux-provider child sessions with no inherited base prompt or conversation, assembled prompt snapshot coverage (selected spec/workspace/session digest, no foreground elicitation recommendation), unknown-tool failure, `read_graph` availability only with injected parent graph readers, parent-spec-only graph read content with sibling-spec negative assertion, bounded concurrency including waiter/new-arrival race, invalid invocation shape rejection before runner call, and parent-abort setup/live-session behavior. Startup advertisement remains dev-gated by whether a launch path supplies subagent deps to `createBrunchPiExtensions(...)`. | D2-L, D39-L, D40-L, D44-L, D91-L; I1-L, I2-L, I11-L, I24-L | | I30-L | Elicitor capture commits only high-confidence graph truth; under the D81-L gradient, directly-stated facts commit `explicit`, confidently-materialized facts/edges commit `implicit`, low-confidence noticings never become graph truth — they map to existing-or-new `elicitation_gaps` as agenda — and contradictions with existing graph truth route to `reconciliation_need` rather than gap or overwrite. | covered for deterministic routing (`src/graph/__tests__/capture-commitment-gradient-gate.test.ts` proves the FE-861 routing gate through the real `mutate_graph`, `update_elicitation_gaps`, and `update_reconciliation_needs` adapters: explicit→commit, implicit→commit, low→one gap, contradiction→one semantic-conflict recon need, structural answered derivation, manual gap close on the graph clock, illegal capture batches failing loud, and the closed capture-quality-spike scenario family re-aimed from binary `shouldCommit` to gradient `expectedOutcome` rows across free prose, file refs, implication-heavy, and contradiction classes. `src/probes/capture-quality-loop.ts` keeps the LLM-in-loop probe as fitness by scoring gradient-routing accuracy, not gating classification quality. `src/.pi/extensions/brunch-data/reconciliation/index.test.ts` proves the recon-need tool pair over `CommandExecutor`/`getOpenReconciliationNeeds` plus elicit-posture legality. `src/projections/session/sweep-watermark.test.ts` plus `src/.pi/__tests__/extension-registry.test.ts` prove the D80-L transcript-position sweep watermark: conversational/digest tail classification, raw background exclusion, idempotent marker advance, graph-LSN watermark separation, and live `before_agent_start` wiring. The submit-time labeled-prefix capture module, its `session.*` wiring, and the `capture-response-to-graph` / `submit-message-capture` proofs were deleted 2026-06-19 (D80-L fossil retirement); `session.submitMessage` / `session.submitExchangeResponse` results no longer carry a `capture` field. Confidence/dedup quality remains fitness.) | D8-L, D18-L, D47-L, D65-L, D80-L, D81-L; A22-L | -| I31-L | Readiness never bars graph truth or work; it is just-in-time capability-readiness over relevant gaps, not a stored grade or kind whitelist. There is no `readiness_grade` scalar; capability availability is judged on request against the relevant `elicitation_gaps` (D74-L) and may proceed, proceed at low epistemic status, or negotiate — it never refuses outright. The `CommandExecutor` must not reject a graph node solely because its kind belongs to a later readiness band (D64-L). The soft `readiness estimate` (D45-L) is UI-only and gates nothing. Capability-readiness never *withholds a graph-write tool*: `mutate_graph` and the review-set tools stay in the active tool set regardless of readiness; `negotiate` is advisory (establishment offer + epistemic scaling), never a tool gate (D86-L). | partially covered (`src/projections/session/capability-readiness.test.ts` covers the D74-L tracer gate, including proceed / proceed_low_epistemic / negotiate, no-refusal, no grade-symbol import, and a live `presence` coverage flip; `src/projections/session/affordances.test.ts` covers the first consumer rewire: menu legality omits gated options while relevant gaps negotiate and includes them when coverage rises, with no grade symbols in `agents/runtime/policy.ts` / `affordances.ts`, and a required `NodeKind` absent from the gap register fails loud (config bug ≠ uncovered — readiness omission never masks a seeding error); `src/projections/session/readiness-estimate.test.ts` covers the soft D45-L estimate shape, empty-band zero, importance-weighted per-band coverage, honest regression, and no legality-path imports; `src/.pi/extensions/agent-runtime/runtime/state.test.ts`, `src/agents/runtime/__tests__/compose.test.ts`, `src/agents/contexts/seeds/__tests__/turn-context.test.ts`, and `src/.pi/__tests__/prompting.test.ts` cover the prompt consumer path: selected-spec gaps render as the soft per-band estimate, readiness-thin pinned axes remain visible, gated methods stay withheld, `readiness_grade=` is absent from prompt display, and the turn boundary threads the same gaps into cwd context without prompt-assembly failure; `src/session/workspace-session-coordinator.test.ts`, `src/app/__tests__/print-workspace-state.test.ts`, `src/session/workspace-overview-context.test.ts`, `src/.pi/__tests__/context-tools.test.ts`, `src/rpc/handlers.test.ts`, and `src/web/app.test.tsx` cover the workspace/chrome display retirement: `chrome.phase` / `chrome.chatMode` no longer project through coordinator/RPC/web/chrome fixtures, and workspace overview session inventory no longer carries or renders `readinessGrade`; `createSpec` / `getSpec` persistence, seed/export fixture contracts, probes, and selected-spec prompt carriers no longer persist or transport a readiness grade; the D86-L graph-write-tool-floor sub-claim is covered — `state.test.ts` proves `mutate_graph` + review-set tools stay floor while `propose-graph`/`project-graph` readiness `negotiate`s and only the non-graph-write `review-for-gaps` is withheld, and `dev/__tests__/tier-2-harness.test.ts` proves the same through a real `runBrunchTui` boot at thin vs covered grounding) | D20-L, D45-L, D64-L, D74-L, D86-L | +| I31-L | Readiness never bars graph truth or work; it is just-in-time capability-readiness over relevant gaps, not a stored grade or kind whitelist. There is no `readiness_grade` scalar; capability availability is judged on request against the relevant `elicitation_gaps` (D74-L) and may proceed, proceed at low epistemic status, or negotiate — it never refuses outright. The `CommandExecutor` must not reject a graph node solely because its kind belongs to a later readiness band (D64-L). The soft `readiness estimate` (D45-L) is UI-only and gates nothing. Capability-readiness never *withholds a graph-write tool*: `mutate_graph` and the review-set tools stay in the active tool set regardless of readiness; `negotiate` is advisory (establishment offer + epistemic scaling), never a tool gate (D86-L). | partially covered (`src/agents/runtime/__tests__/capability-readiness.test.ts` covers the D74-L tracer gate, including proceed / proceed_low_epistemic / negotiate, no-refusal, no grade-symbol import, and a live `presence` coverage flip; `src/agents/runtime/__tests__/policy.test.ts` covers the first consumer rewire: menu legality omits gated options while relevant gaps negotiate and includes them when coverage rises, with no grade symbols in `agents/runtime/policy.ts`, and a required `NodeKind` absent from the gap register fails loud (config bug ≠ uncovered — readiness omission never masks a seeding error); `src/projections/session/readiness-estimate.test.ts` covers the soft D45-L estimate shape, empty-band zero, importance-weighted per-band coverage, honest regression, and no legality-path imports; `src/.pi/extensions/agent-runtime/runtime/state.test.ts`, `src/agents/runtime/__tests__/compose.test.ts`, `src/agents/contexts/seeds/__tests__/turn-context.test.ts`, and `src/.pi/__tests__/prompting.test.ts` cover the prompt consumer path: selected-spec gaps render as the soft per-band estimate, readiness-thin pinned axes remain visible, gated methods stay withheld, `readiness_grade=` is absent from prompt display, and the turn boundary threads the same gaps into cwd context without prompt-assembly failure; `src/session/workspace-session-coordinator.test.ts`, `src/app/__tests__/print-workspace-state.test.ts`, `src/session/workspace-overview-context.test.ts`, `src/.pi/__tests__/context-tools.test.ts`, `src/rpc/handlers.test.ts`, and `src/web/app.test.tsx` cover the workspace/chrome display retirement: `chrome.phase` / `chrome.chatMode` no longer project through coordinator/RPC/web/chrome fixtures, and workspace overview session inventory no longer carries or renders `readinessGrade`; `createSpec` / `getSpec` persistence, seed/export fixture contracts, probes, and selected-spec prompt carriers no longer persist or transport a readiness grade; the D86-L graph-write-tool-floor sub-claim is covered — `state.test.ts` proves `mutate_graph` + review-set tools stay floor while `propose-graph`/`project-graph` readiness `negotiate`s and only the non-graph-write `review-for-gaps` is withheld, and `dev/__tests__/tier-2-harness.test.ts` proves the same through a real `runBrunchTui` boot at thin vs covered grounding) | D20-L, D45-L, D64-L, D74-L, D86-L | | I32-L | Public RPC structured-exchange driving never requires a client to speak raw Pi RPC: after Brunch method discovery and workspace/spec/session activation, each pending assistant-originated exchange is answered exactly once through `session.submitExchangeResponse`, and the deterministic permutation run produces linear Pi JSONL whose structured exchange projection preserves the same prompt/answer/status/comment artifacts as the equivalent TUI structured-exchange path. | covered for deterministic FE-744 parity under canonical session method names (`session.triggerExchange`, `session.pendingExchange`, `session.submitExchangeResponse`, `session.exchanges`): `rpc.discover` contract tests, pending/respond lifecycle tests, current public-RPC structured-exchange permutations, terminal non-answered status handling, option content/rationale parity, no repeated deterministic prompts, and transcript/exchange parity assertions. | D5-L, D48-L, D49-L; I10-L, I13-L, I21-L, I23-L | | I33-L | `capture_*` analysis entries are transcript evidence only: they persist as Brunch structured-exchange `toolResult` rows, are included by Brunch-semantic transcript renderers, are hidden or collapsed in TUI display, and never mutate graph truth or bypass `CommandExecutor`. | partially covered (minimum capture details schemas parse/export and reject graph payload fields; future runtime capture-analysis schema/rendering tests plus transcript renderer fixtures still need to prove persisted result rendering and TUI hide/collapse behavior; later graph-capture fixtures compare analysis candidates against committed graph mutations) | D17-L, D18-L, D37-L, D47-L, D50-L; I2-L, I11-L, I23-L, I30-L | | I34-L | `mutateGraph` batch validation is all-or-nothing: if any node or edge in the batch is structurally illegal, the entire batch is rejected and no partial state is persisted; the agent receives diagnostics sufficient for bounded self-correction retry. | covered (`command-executor/commit-graph-batch.test.ts` and graph-tool adapter tests cover dry-run/commit diagnostic parity for invalid basis, missing refs/codes, invalid category/stance, self-loop, invalid node kind/detail shape, rollback of nodes/edges/change_log/counters, transaction-local planning before LSN allocation/writes, and structured adapter diagnostics without thrown projected-code errors or fake endpoint refs) | D53-L; I1-L, I11-L | | I35-L | Graph context reads support multiple detail levels: a cursory/compact full-graph overview for orientation, detailed node-neighborhood context with configurable hop depth for focused work, bounded list slices by kind/readiness band, and related-node traversal by anchor and edge category. The `read_graph` parameter boundary teaches the mode-specific companion contract: `neighborhood` requires non-empty `nodeCode`, `related` requires non-empty `anchorCodes` plus `edgeCategory`, and list modes intentionally treat omitted/empty filters as unfiltered slices while unknown filters produce an empty slice. Context builders in `.pi/extensions/agent-runtime/system-prompts/seed/` (pushed seed contexts) and `.pi/extensions/brunch-data/context/` (pull tools) orchestrate which level to inject or advertise based on mode/goal/strategy/lens/readiness. | covered for current POC push and pull paths (`getGraphOverview` + `getNodeNeighborhood` in `queries.ts` with 10 tests; `src/.pi/extensions/agent-runtime/system-prompts/seed/{graph,workspace}.test.ts` covers lens-shaped overview rendering and selected-spec workspace/session/posture seed context, and `src/.pi/__tests__/context-tools.test.ts` covers the pulled context tools including bounded node-neighborhood rendering; `src/.pi/__tests__/prompting.test.ts` proves the explicit shell/product prompt path supplies selected-spec-bound graph context to `composeAgentPrompt()`). `src/graph/__tests__/observed-shapes-coverage.test.ts` guards the read mode ledger, and `src/.pi/__tests__/graph-tools.test.ts` covers `read_graph` mode-companion schema enforcement plus loud adapter diagnostics for malformed companion calls. Pulled context tools are part of the live read surface. | D52-L, D53-L, D58-L | | I36-L | Node `kind` is drawn from a per-plane closed enum structurally validated by the `CommandExecutor`. | covered (CommandExecutor rejects invalid kind-for-plane; tests in `command-executor.test.ts`) | D54-L, D56-L | | I37-L | `detail` is per-kind validated and boundary-advertised from the graph schema owner: `decision` and `term` nodes REQUIRE `detail` with their respective sub-schemas; the claim kinds `requirement`/`criterion`/`invariant` accept an OPTIONAL `form`-discriminated union (`plain`/`gherkin`/`formal`) and `context` accepts an OPTIONAL `form:"given"` payload (D88-L); all other kinds must omit `detail`; the `form` discriminant and any unknown per-form fields are rejected. `kind` drives behavior (band/edge-legality/source-question); `form` is inert payload plus a renderer hook. The agent/tool and dev-RPC mutation schemas expose the same per-kind companion shapes — including the claim/context form union — instead of accepting opaque `Unknown`. | covered (detail-required/prohibited/form-shape tests in `command-executor.test.ts`; boundary schema companion tests in `mutate-graph-edge-schema.test.ts`) | D54-L, D88-L | -| I38-L | Every Brunch prompt-resource manifest injected for an agent turn is generated from projected runtime state and spec/workspace gates: listed resources are Brunch-owned, readable under the active tool policy, legal for the current `(op_mode × strategy × lens)` tuple / capability-readiness / agent allow-list, and off-list resources are not advertised as available. AUTO axes never list readiness-illegal choices; pinned axes point to the pinned resource whenever the tuple is role/mode-legal, even if readiness negotiates. The shared affordance derivation and prompt manifest filtering use the same capability-readiness/AUTO legality source. | covered for current P0 manifest families (`src/agents/runtime/__tests__/compose.test.ts` covers default header/context/manifest output, two-AUTO-axis manifest output with no ``, AUTO capability-readiness filtering, pinned singleton resources, readiness-thin pin retention, role/mode-illegal pin rejection, and readable `src/.pi/` locations; `src/.pi/__tests__/prompting.test.ts` covers the explicit shell `before_agent_start` product path appending `composeAgentPrompt()` output from transcript-projected runtime state and no legacy composer import/resource discovery; `src/.pi/extensions/agent-runtime/runtime/state.test.ts` plus `src/projections/session/affordances.test.ts` cover shared legality/default behavior, including AUTO excluding `freestyle` and gated methods staying withheld during negotiation). FE-825 added a dev-gated introspection loop (`src/.pi/extensions/dev-mode/introspection/` + `src/dev/introspection-launcher.ts`) that records final provider payloads and pairs them with subjective model answers under `.fixtures/scratch/introspection//`; `brunch_introspect_query` now makes the captured provider payload/tool schemas/base options queryable in-chat for the same diagnostic plane. Probe fitness may still track whether the agent reads selected resources before use. | D39-L, D40-L, D58-L, D59-L, D66-L, D69-L, D85-L | +| I38-L | Every Brunch prompt-resource manifest injected for an agent turn is generated from projected runtime state and spec/workspace gates: listed resources are Brunch-owned, readable under the active tool policy, legal for the current `(op_mode × strategy × lens)` tuple / capability-readiness / agent allow-list, and off-list resources are not advertised as available. AUTO axes never list readiness-illegal choices; pinned axes point to the pinned resource whenever the tuple is role/mode-legal, even if readiness negotiates. The shared affordance derivation and prompt manifest filtering use the same capability-readiness/AUTO legality source. | covered for current P0 manifest families (`src/agents/runtime/__tests__/compose.test.ts` covers default header/context/manifest output, two-AUTO-axis manifest output with no ``, AUTO capability-readiness filtering, pinned singleton resources, readiness-thin pin retention, role/mode-illegal pin rejection, and readable `src/agents/` locations; `src/.pi/__tests__/prompting.test.ts` covers the explicit shell `before_agent_start` product path appending `composeAgentPrompt()` output from transcript-projected runtime state and no legacy composer import/resource discovery; `src/.pi/extensions/agent-runtime/runtime/state.test.ts` plus `src/agents/runtime/__tests__/policy.test.ts` cover shared legality/default behavior, including AUTO excluding `freestyle` and gated methods staying withheld during negotiation). FE-825 added a dev-gated introspection loop (`src/.pi/extensions/dev-mode/introspection/` + `src/dev/introspection-launcher.ts`) that records final provider payloads and pairs them with subjective model answers under `.fixtures/scratch/introspection//`; `brunch_introspect_query` now makes the captured provider payload/tool schemas/base options queryable in-chat for the same diagnostic plane. Probe fitness may still track whether the agent reads selected resources before use. | D39-L, D40-L, D58-L, D59-L, D66-L, D69-L, D85-L | | I39-L | Every graph node in a spec has exactly one stable projected human reference code derived from `kind` + `kind_ordinal`; `(spec_id, plane, kind, kind_ordinal)` is unique; ordinals are monotonic per `(spec_id, plane, kind)` and are not reused after deletion or supersession. | partially covered (`graph-tool-resilience` added `nodes.kind_ordinal`, `node_kind_counters`, DB uniqueness, CommandExecutor allocation for single-node/batch writes, rollback protection, `GraphNode.kindOrdinal` row mapping, globally unique 1–3 letter labels with readiness-band metadata, projected-code parsing, selected-spec adapter resolution before `CommandExecutor`, code-only `mutate_graph` / `read_graph` schemas, and code-primary prompt/tool rendering; `queries.test.ts` now pins the merged `NODE_KIND_METADATA` labels + D64-L readiness bands so schema/code drift fails loudly; remaining slice still needs deletion/supersession no-reuse coverage) | D54-L, D62-L; I1-L, I11-L | | I40-L | Accepted graph nodes and edges use only `basis ∈ explicit | implicit`; review-set approval and direct user statements produce `explicit`, `propose-graph` concept-level materialization produces `implicit`, and the mutation path is recoverable from `change_log` rather than from a persisted basis enum value such as `accepted_review_set`. | covered (`graph-tool-resilience` replaced the persisted basis enum with `explicit | implicit`, made `mutateGraph` apply one batch create-basis to all created nodes/edges, made single-node `createNode` reject retired basis values before LSN/counter/node/change-log allocation, made `propose-graph` adapter commits implicit, made review-set translation explicit, rejected retired `accepted_review_set`, and records `change_log.operation` independently; `capture-response-to-graph` proves direct structured text responses commit explicit-basis graph nodes through `CommandExecutor`; `.fixtures/runs/project-graph-review-cycle/2026-06-06-project-graph-review-cycle/` proves full review-cycle approval creates explicit-basis graph truth) | D26-L, D27-L, D53-L, D63-L | | I41-L | Same-spec `supersession` edges form an acyclic directed graph; every edge-creation path validates proposed supersession edges together with existing supersession edges before committing. | covered (`command-executor/commit-graph-batch.test.ts` rejects existing-cycle closure, intra-batch cycles, and mixed existing+batch cycles through the shared dry-run/commit planner before batch writes; rejected cycles roll back or avoid batch nodes/edges/change_log; acyclic supersession commits remain covered by query/CommandExecutor success paths) | D51-L, D53-L; I34-L | diff --git a/memory/cards/orchestrator-tool-port--plan-check-tool.md b/memory/cards/orchestrator-tool-port--plan-check-tool.md index 604086c5a..8e1535346 100644 --- a/memory/cards/orchestrator-tool-port--plan-check-tool.md +++ b/memory/cards/orchestrator-tool-port--plan-check-tool.md @@ -24,7 +24,7 @@ The execute-mode orchestrator can inspect a cook plan through a product-register - `memory/PLAN.md` — frontier: `orchestrator-tool-port`. - `src/.pi/extensions/README.md` — adapter-only ownership and boundary rules. - `src/agents/prompts/orchestrator/SYSTEM.md` — current execute-mode foreground prompt and stub wording to retire. -- `src/projections/session/runtime-policy.ts` — `execute` foreground roster and blocked direct tool policy. +- `src/agents/runtime/policy.ts` — `execute` foreground roster and blocked direct tool policy. - `src/session/schema/tool-names.ts` — shared tool-name constants. - `/Users/lunelson/Code/hashintel/brunch/ORCHESTRATOR.md` — source CLI behavior and plan format. - `/Users/lunelson/Code/hashintel/brunch/src/orchestrator/src/{types.ts,plan-loader.ts,plan-contract.ts,cook-cli.ts}` — portable plan model, loader, contract, and plan-resolution behavior to adapt. @@ -43,7 +43,7 @@ The execute-mode orchestrator can inspect a cook plan through a product-register ## Risks and Assumptions - RISK: CLI code pulls in process exits, git worktree creation, model auth, or child Pi sessions too early → MITIGATION: port only pure/read-only plan loading and contract checking in this slice; no sandbox, engine, Petrinaut stream, or worker session imports. -- RISK: The foreground `orchestrator` gains accidental write authority while replacing the stub → MITIGATION: keep `bash`, `edit`, and `write` blocked in `runtime-policy.ts`; register only the read-only `cook_plan_check` tool for this card. +- RISK: The foreground `orchestrator` gains accidental write authority while replacing the stub → MITIGATION: keep `bash`, `edit`, and `write` blocked in `agents/runtime/policy.ts`; register only the read-only `cook_plan_check` tool for this card. - RISK: External source names leak as temporary compatibility aliases → MITIGATION: canonicalize the product-facing tool name now; delete the `orchestrator_stub` tool path when the real tool is registered. - ASSUMPTION: The external cook plan contract is the right first tracer boundary for the port. → IMPACT IF FALSE: the later `cook_run` surface may need a different plan source/result model, but this slice's blast radius is limited to read-only validation and prompt/tool naming. @@ -60,12 +60,12 @@ No separate spike is cheaper than this slice: the useful proof is whether the pr ✓ `cook_plan_check` is product-registered for execute mode and returns a typed result for a valid plan path containing mode, epic count, slice count, policy-relevant findings, and source path. ✓ Invalid or contract-failing plans return deterministic typed findings/errors without creating `.brunch/cook/runs`, git worktrees, Petrinaut artifacts, or child Pi sessions. ✓ `orchestrator_stub` is no longer advertised to the foreground orchestrator, and the old stub registration path is retired. -✓ `runtime-policy.ts` still blocks direct `bash`, `edit`, and `write` for `execute`, with tests or assertions covering the new tool grant. +✓ `agents/runtime/policy.ts` still blocks direct `bash`, `edit`, and `write` for `execute`, with tests or assertions covering the new tool grant. ✓ `src/agents/prompts/orchestrator/SYSTEM.md` tells the foreground agent to use the real plan-check tool and preserves the no-direct-write instruction. ## Verification Approach -- Inner: focused unit/contract tests — plan loader/contract result shape, tool execution result, runtime-policy grant/block invariants. +- Inner: focused unit/contract tests — plan loader/contract result shape, tool execution result, runtime policy grant/block invariants. - Middle: `npm run fix` — project lint/format after edits. - Gate: `npm run verify` — full fix/test/build before tying off the branch. @@ -90,12 +90,17 @@ src/ │ ├── types.ts + │ └── __tests__/ │ └── plan-check.test.ts + -├── .pi/ -│ ├── agents/ +├── agents/ +│ ├── prompts/ │ │ └── orchestrator/ │ │ └── SYSTEM.md ~ +│ └── runtime/ +│ ├── policy.ts ~ +│ └── __tests__/ ? +├── .pi/ │ ├── extensions/ │ │ ├── README.md ~ +│ │ ├── agent-runtime/ ~ │ │ ├── orchestrator/ + │ │ │ ├── index.ts + │ │ │ └── __tests__/ @@ -104,11 +109,6 @@ src/ │ └── __tests__/ ? ├── app/ │ └── pi-extensions.ts ~ -├── projections/ -│ └── session/ -│ ├── runtime-policy.ts ~ -│ └── __tests__/ -│ └── runtime-policy.test.ts ? └── session/ └── schema/ └── tool-names.ts ~ diff --git a/src/agents/runtime/__tests__/state.test.ts b/src/agents/runtime/__tests__/state.test.ts index 0cf98f7fa..26269385c 100644 --- a/src/agents/runtime/__tests__/state.test.ts +++ b/src/agents/runtime/__tests__/state.test.ts @@ -3,9 +3,9 @@ import { fileURLToPath } from 'node:url'; import { describe, expect, it } from 'vitest'; -import { BRUNCH_ORCHESTRATOR_STUB_TOOL } from '../../../.pi/extensions/agent-runtime/orchestrator-stub/index.js'; import { groundingFloorGaps } from '../../../graph/schema/elicitation-gap-fixtures.js'; import { projectBrunchAgentState } from '../../../projections/session/runtime-state.js'; +import { BRUNCH_ORCHESTRATOR_STUB_TOOL } from '../../../session/schema/tool-names.js'; import { bundledAgentBodyLocation } from '../../registry.js'; import { FOREGROUND_AGENT_ROSTER, delegatableAgentsForRuntimeState } from '../policy.js'; import { activeToolNamesForPosture, agentBodyResourceLocation, manifestsForState } from '../state.js'; From 0d352bb440573a9d1af71e21466129f4bc3f2aa1 Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Fri, 26 Jun 2026 11:39:22 +0200 Subject: [PATCH 37/54] Add Pi extension example notes, for upcoming dev --- src/.pi/extensions/README.md | 78 ++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/src/.pi/extensions/README.md b/src/.pi/extensions/README.md index 1758c9f48..ae8eed86a 100644 --- a/src/.pi/extensions/README.md +++ b/src/.pi/extensions/README.md @@ -67,3 +67,81 @@ rules: `exchanges/schemas/` is the intentional current exception to "adapter-only": it owns the Zod-authored structured-exchange details schema per D37-L/D41-L until a separate schema-ownership slice moves or names that seam. Zod-to-Pi `TSchema` conversion is confined to two per-plane adapters: `exchanges/pi-schema.ts` (structured-exchange) and `shared/pi-tool-schema.ts` (dev-gated query tools). Both export JSON Schema draft 2020-12 (`z.toJSONSchema`), which strict provider validators require. `exchanges/shared/markdown.ts` contains Pi-rendering helpers. Keep Pi `renderCall` / `renderResult` widgets and UI-only message components local to `.pi/`; reusable provider-visible exchange result text belongs in `agents/contexts/exchanges/`. + +## Example extensions to reference for future work (relative to pi source) + +Pattern notes +- use `ctx.ui.notify` when any operation completes + +### enhancements + +implement spinner/working feedback +- `packages/coding-agent/examples/extensions/titlebar-spinner.ts` +- `packages/coding-agent/examples/extensions/working-indicator.ts` +- `packages/coding-agent/examples/extensions/working-message-test.ts` + +how to add a quit command +- `packages/coding-agent/examples/extensions/shutdown-command.ts` + +how to name the session +- `packages/coding-agent/examples/extensions/session-name.ts` + +### essentials + +how to do RPC patterns correctly +- `packages/coding-agent/examples/extensions/rpc-demo.ts` +- `packages/coding-agent/examples/rpc-extension-ui.ts` + +custom tool truncation +- `packages/coding-agent/examples/extensions/truncated-tool.ts` + +custom compaction threshold and rules +- `packages/coding-agent/examples/extensions/trigger-compact.ts` + +### executor-relevant + +orchestration/cook tool state, as session state? +- `packages/coding-agent/examples/extensions/todo.ts` + +how to have an event bus between extensions +- `packages/coding-agent/examples/extensions/event-bus.ts` + +how to confirm destructive actions +- `packages/coding-agent/examples/extensions/confirm-destructive.ts` + +how to pass session context to subagent +- `packages/coding-agent/examples/extensions/summarize.ts` + +`terminate: true` param for agent tool-outputs which don't need a following agent summary +- `packages/coding-agent/examples/extensions/structured-output.ts` + +a way of display "turn status" in the UI +- `packages/coding-agent/examples/extensions/status-line.ts` + +blocking operations on certain paths +- `packages/coding-agent/examples/extensions/protected-paths.ts` + +how to block dangerous commands +- `packages/coding-agent/examples/extensions/permission-gate.ts` + +### elicitor-relevant + +auto-confirmation of questions (take recommendation) +- `packages/coding-agent/examples/extensions/timed-confirm.ts` + +### primary agents + +how to customize system prompt dynamically +- `packages/coding-agent/examples/extensions/prompt-customizer.ts` + +how to switch operational modes +- `packages/coding-agent/examples/extensions/preset.ts` + +a fuller plan vs code mode, with UI feedback +- `packages/coding-agent/examples/extensions/plan-mode/README.md` + +how to render status on the border of the editor +- `packages/coding-agent/examples/extensions/border-status-editor.ts` + +how to set the hidden-thinking label (static) +- `packages/coding-agent/examples/extensions/hidden-thinking-label.ts` From 426d8b9bf6a8b29b3704390771f4893584c516d5 Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Fri, 26 Jun 2026 12:22:57 +0200 Subject: [PATCH 38/54] Harden Brunch Pi runtime tools --- src/app/__tests__/brunch-tui.test.ts | 63 +++++++++++++++++++++++++++- src/app/brunch-tui.ts | 7 +++- 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/src/app/__tests__/brunch-tui.test.ts b/src/app/__tests__/brunch-tui.test.ts index 46a642c70..dacea80f3 100644 --- a/src/app/__tests__/brunch-tui.test.ts +++ b/src/app/__tests__/brunch-tui.test.ts @@ -10,7 +10,7 @@ import { type ExtensionUIContext, type RegisteredCommand, } from '@earendil-works/pi-coding-agent'; -import { describe, expect, it } from 'vitest'; +import { describe, expect, it, vi } from 'vitest'; import { openWorkspaceGraphRuntime } from '../../graph/index.js'; import { groundingFloorGaps } from '../../graph/schema/elicitation-gap-fixtures.js'; @@ -78,6 +78,62 @@ describe('Brunch TUI boot', () => { } }); + it('forwards Pi replacement session-start events into product session creation', async () => { + vi.resetModules(); + const createAgentSessionFromServices = vi.fn(async () => ({ + session: { + sendCustomMessage: async () => {}, + dispose: () => {}, + }, + })); + vi.doMock('@earendil-works/pi-coding-agent', async (importOriginal) => ({ + ...(await importOriginal()), + createAgentSessionFromServices, + })); + + try { + const cwd = await mkdtemp(join(tmpdir(), 'brunch-tui-session-start-')); + const agentDir = await mkdtemp(join(tmpdir(), 'brunch-agent-dir-')); + const { createWorkspaceSessionCoordinator } = + await import('../../session/workspace-session-coordinator.js'); + const { FOREGROUND_AGENT_ROSTER } = await import('../../agents/runtime/policy.js'); + const { createBrunchAgentSessionRuntimeFactory } = await import('../brunch-tui.js'); + const coordinator = createWorkspaceSessionCoordinator({ cwd }); + const workspace = await coordinator.createSetupSession({ + specTitle: 'Replacement lifecycle', + createNewSpec: true, + }); + const createRuntime = createBrunchAgentSessionRuntimeFactory({ workspace, coordinator }); + const sessionStartEvent = { + type: 'session_start' as const, + reason: 'new' as const, + previousSessionFile: '/sessions/old.jsonl', + }; + + const created = await createRuntime({ + cwd, + agentDir, + sessionManager: workspace.session.manager, + sessionStartEvent, + }); + + expect(createAgentSessionFromServices).toHaveBeenCalledWith( + expect.objectContaining({ + sessionStartEvent, + thinkingLevel: FOREGROUND_AGENT_ROSTER.elicit.foregroundAgent.thinking, + }), + ); + const [sessionOptions] = createAgentSessionFromServices.mock.calls[0]! as unknown as [ + Record, + ]; + expect(sessionOptions).not.toHaveProperty('model'); + created.session.dispose(); + } finally { + vi.doUnmock('@earendil-works/pi-coding-agent'); + vi.resetModules(); + } + }); + it('registers graph tools on the default product runtime path', async () => { const cwd = await mkdtemp(join(tmpdir(), 'brunch-tui-graph-runtime-')); const agentDir = await mkdtemp(join(tmpdir(), 'brunch-agent-dir-')); @@ -97,6 +153,11 @@ describe('Brunch TUI boot', () => { const toolNames = created.session.getAllTools().map((tool) => tool.name); expect(toolNames).toContain('mutate_graph'); expect(toolNames).toContain('read_graph'); + expect(toolNames).toEqual(expect.arrayContaining(['read', 'grep', 'find', 'ls'])); + expect(toolNames).not.toEqual(expect.arrayContaining(['bash', 'edit', 'write'])); + const activeToolNames = created.session.getActiveToolNames(); + expect(activeToolNames).toEqual(expect.arrayContaining(['read', 'grep', 'find', 'ls'])); + expect(activeToolNames).not.toEqual(expect.arrayContaining(['bash', 'edit', 'write'])); } finally { created.session.dispose(); } diff --git a/src/app/brunch-tui.ts b/src/app/brunch-tui.ts index 6961624eb..cda18c2f6 100644 --- a/src/app/brunch-tui.ts +++ b/src/app/brunch-tui.ts @@ -314,7 +314,7 @@ export function createBrunchAgentSessionRuntimeFactory( context: BrunchTuiLaunchContext, ): CreateAgentSessionRuntimeFactory { const { coordinator, productUpdates } = context; - return async ({ cwd, agentDir: runtimeAgentDir, sessionManager }) => { + return async ({ cwd, agentDir: runtimeAgentDir, sessionManager, sessionStartEvent }) => { let currentWorkspace = await coordinator.bindCurrentSpecToReplacementSession(sessionManager); const graph = await openWorkspaceGraphRuntime(cwd); const graphDeps = { @@ -396,6 +396,7 @@ export function createBrunchAgentSessionRuntimeFactory( const liveAgentSession = context.liveAgentSession ?? { current: null }; const startupHeader = startupHeaderForActivation(context.activationDecision); const agentState = projectBrunchAgentState(sessionManager.getEntries()); + const foregroundAgent = agentState.agentRoleDefinition; const subagents = context.dev ? await loadBrunchSubagents({ cwd, @@ -483,6 +484,10 @@ export function createBrunchAgentSessionRuntimeFactory( const created = await createAgentSessionFromServices({ services, sessionManager, + ...(sessionStartEvent ? { sessionStartEvent } : {}), + noTools: 'builtin', + excludeTools: ['bash', 'edit', 'write'], + thinkingLevel: foregroundAgent.thinking, ...(context.agentServices?.model ? { model: context.agentServices.model } : {}), }); liveAgentSession.current = created.session; From c1ee6a2117bc6097105874a3c7abcd03a691bdd9 Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Fri, 26 Jun 2026 12:24:30 +0200 Subject: [PATCH 39/54] Extract Brunch Pi session option policy --- src/app/__tests__/pi-session-options.test.ts | 43 ++++++++++++++++++++ src/app/brunch-tui.ts | 11 ++--- src/app/pi-session-options.ts | 26 ++++++++++++ 3 files changed, 75 insertions(+), 5 deletions(-) create mode 100644 src/app/__tests__/pi-session-options.test.ts create mode 100644 src/app/pi-session-options.ts diff --git a/src/app/__tests__/pi-session-options.test.ts b/src/app/__tests__/pi-session-options.test.ts new file mode 100644 index 000000000..58103f0f1 --- /dev/null +++ b/src/app/__tests__/pi-session-options.test.ts @@ -0,0 +1,43 @@ +import { describe, expect, it } from 'vitest'; + +import { FOREGROUND_AGENT_ROSTER } from '../../agents/runtime/policy.js'; +import { projectBrunchPiSessionOptions } from '../pi-session-options.js'; + +describe('Brunch Pi session options', () => { + it('projects the Brunch runtime hardening policy into Pi SDK session options', () => { + const sessionStartEvent = { + type: 'session_start' as const, + reason: 'new' as const, + previousSessionFile: '/sessions/old.jsonl', + }; + + expect( + projectBrunchPiSessionOptions({ + sessionStartEvent, + thinkingLevel: FOREGROUND_AGENT_ROSTER.elicit.foregroundAgent.thinking, + }), + ).toEqual({ + sessionStartEvent, + noTools: 'builtin', + excludeTools: ['bash', 'edit', 'write'], + thinkingLevel: FOREGROUND_AGENT_ROSTER.elicit.foregroundAgent.thinking, + }); + }); + + it('keeps the default model sentinel non-owning unless a concrete override is supplied', () => { + expect( + projectBrunchPiSessionOptions({ + thinkingLevel: FOREGROUND_AGENT_ROSTER.elicit.foregroundAgent.thinking, + }), + ).not.toHaveProperty('model'); + + const model = { provider: 'test-provider', id: 'test-model' } as never; + + expect( + projectBrunchPiSessionOptions({ + thinkingLevel: FOREGROUND_AGENT_ROSTER.elicit.foregroundAgent.thinking, + model, + }), + ).toMatchObject({ model }); + }); +}); diff --git a/src/app/brunch-tui.ts b/src/app/brunch-tui.ts index cda18c2f6..e454c5542 100644 --- a/src/app/brunch-tui.ts +++ b/src/app/brunch-tui.ts @@ -58,6 +58,7 @@ import { createInMemoryBrunchIntrospectionStore, type BrunchIntrospectionStore, } from './pi-extensions.js'; +import { projectBrunchPiSessionOptions } from './pi-session-options.js'; import { applyBrunchOfflineDefault, createBrunchPiSettings } from './pi-settings.js'; import { loadBrunchSubagents } from './pi-subagents.js'; export { @@ -484,11 +485,11 @@ export function createBrunchAgentSessionRuntimeFactory( const created = await createAgentSessionFromServices({ services, sessionManager, - ...(sessionStartEvent ? { sessionStartEvent } : {}), - noTools: 'builtin', - excludeTools: ['bash', 'edit', 'write'], - thinkingLevel: foregroundAgent.thinking, - ...(context.agentServices?.model ? { model: context.agentServices.model } : {}), + ...projectBrunchPiSessionOptions({ + ...(sessionStartEvent ? { sessionStartEvent } : {}), + thinkingLevel: foregroundAgent.thinking, + ...(context.agentServices?.model ? { model: context.agentServices.model } : {}), + }), }); liveAgentSession.current = created.session; context.sessionEvents?.attachSession(created.session); diff --git a/src/app/pi-session-options.ts b/src/app/pi-session-options.ts new file mode 100644 index 000000000..064e28161 --- /dev/null +++ b/src/app/pi-session-options.ts @@ -0,0 +1,26 @@ +import type { CreateAgentSessionFromServicesOptions } from '@earendil-works/pi-coding-agent'; + +const BRUNCH_WITHHELD_BUILTIN_TOOL_NAMES = ['bash', 'edit', 'write'] as const; + +export interface BrunchPiSessionPolicyInput { + readonly sessionStartEvent?: CreateAgentSessionFromServicesOptions['sessionStartEvent']; + readonly thinkingLevel: NonNullable; + readonly model?: CreateAgentSessionFromServicesOptions['model']; +} + +type BrunchPiSessionPolicyOptions = Pick< + CreateAgentSessionFromServicesOptions, + 'excludeTools' | 'model' | 'noTools' | 'sessionStartEvent' | 'thinkingLevel' +>; + +export function projectBrunchPiSessionOptions( + input: BrunchPiSessionPolicyInput, +): BrunchPiSessionPolicyOptions { + return { + ...(input.sessionStartEvent ? { sessionStartEvent: input.sessionStartEvent } : {}), + noTools: 'builtin', + excludeTools: [...BRUNCH_WITHHELD_BUILTIN_TOOL_NAMES], + thinkingLevel: input.thinkingLevel, + ...(input.model ? { model: input.model } : {}), + }; +} From 40849befb1fc3c9462821f6aa6308f6860bba252 Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Fri, 26 Jun 2026 12:25:55 +0200 Subject: [PATCH 40/54] Split Pi runtime tests from TUI boot --- src/app/__tests__/brunch-tui.test.ts | 165 +-------------------------- src/app/__tests__/pi-runtime.test.ts | 116 +++++++++++++++++++ 2 files changed, 117 insertions(+), 164 deletions(-) create mode 100644 src/app/__tests__/pi-runtime.test.ts diff --git a/src/app/__tests__/brunch-tui.test.ts b/src/app/__tests__/brunch-tui.test.ts index dacea80f3..bec4ba09f 100644 --- a/src/app/__tests__/brunch-tui.test.ts +++ b/src/app/__tests__/brunch-tui.test.ts @@ -10,12 +10,10 @@ import { type ExtensionUIContext, type RegisteredCommand, } from '@earendil-works/pi-coding-agent'; -import { describe, expect, it, vi } from 'vitest'; +import { describe, expect, it } from 'vitest'; -import { openWorkspaceGraphRuntime } from '../../graph/index.js'; import { groundingFloorGaps } from '../../graph/schema/elicitation-gap-fixtures.js'; import { userMessage } from '../../probes/test-helpers.js'; -import { createProductUpdatePublisher } from '../../rpc/product-updates.js'; import { createWorkspaceSessionCoordinator, verifyWorkspaceSessionStores, @@ -28,7 +26,6 @@ import { applyBrunchOfflineDefault, brunchResourceLoaderOptions, createBrunchSettingsManager, - createBrunchAgentSessionRuntimeFactory, runBrunchTui, runWithScopedBrunchOfflineDefault, startupHeaderForActivation, @@ -78,166 +75,6 @@ describe('Brunch TUI boot', () => { } }); - it('forwards Pi replacement session-start events into product session creation', async () => { - vi.resetModules(); - const createAgentSessionFromServices = vi.fn(async () => ({ - session: { - sendCustomMessage: async () => {}, - dispose: () => {}, - }, - })); - vi.doMock('@earendil-works/pi-coding-agent', async (importOriginal) => ({ - ...(await importOriginal()), - createAgentSessionFromServices, - })); - - try { - const cwd = await mkdtemp(join(tmpdir(), 'brunch-tui-session-start-')); - const agentDir = await mkdtemp(join(tmpdir(), 'brunch-agent-dir-')); - const { createWorkspaceSessionCoordinator } = - await import('../../session/workspace-session-coordinator.js'); - const { FOREGROUND_AGENT_ROSTER } = await import('../../agents/runtime/policy.js'); - const { createBrunchAgentSessionRuntimeFactory } = await import('../brunch-tui.js'); - const coordinator = createWorkspaceSessionCoordinator({ cwd }); - const workspace = await coordinator.createSetupSession({ - specTitle: 'Replacement lifecycle', - createNewSpec: true, - }); - const createRuntime = createBrunchAgentSessionRuntimeFactory({ workspace, coordinator }); - const sessionStartEvent = { - type: 'session_start' as const, - reason: 'new' as const, - previousSessionFile: '/sessions/old.jsonl', - }; - - const created = await createRuntime({ - cwd, - agentDir, - sessionManager: workspace.session.manager, - sessionStartEvent, - }); - - expect(createAgentSessionFromServices).toHaveBeenCalledWith( - expect.objectContaining({ - sessionStartEvent, - thinkingLevel: FOREGROUND_AGENT_ROSTER.elicit.foregroundAgent.thinking, - }), - ); - const [sessionOptions] = createAgentSessionFromServices.mock.calls[0]! as unknown as [ - Record, - ]; - expect(sessionOptions).not.toHaveProperty('model'); - created.session.dispose(); - } finally { - vi.doUnmock('@earendil-works/pi-coding-agent'); - vi.resetModules(); - } - }); - - it('registers graph tools on the default product runtime path', async () => { - const cwd = await mkdtemp(join(tmpdir(), 'brunch-tui-graph-runtime-')); - const agentDir = await mkdtemp(join(tmpdir(), 'brunch-agent-dir-')); - const coordinator = createWorkspaceSessionCoordinator({ cwd }); - const workspace = await coordinator.createSetupSession({ - specTitle: 'Graph runtime', - createNewSpec: true, - }); - const createRuntime = createBrunchAgentSessionRuntimeFactory({ workspace, coordinator }); - const created = await createRuntime({ - cwd, - agentDir, - sessionManager: workspace.session.manager, - }); - - try { - const toolNames = created.session.getAllTools().map((tool) => tool.name); - expect(toolNames).toContain('mutate_graph'); - expect(toolNames).toContain('read_graph'); - expect(toolNames).toEqual(expect.arrayContaining(['read', 'grep', 'find', 'ls'])); - expect(toolNames).not.toEqual(expect.arrayContaining(['bash', 'edit', 'write'])); - const activeToolNames = created.session.getActiveToolNames(); - expect(activeToolNames).toEqual(expect.arrayContaining(['read', 'grep', 'find', 'ls'])); - expect(activeToolNames).not.toEqual(expect.arrayContaining(['bash', 'edit', 'write'])); - } finally { - created.session.dispose(); - } - }); - - it('binds graph tools to the coordinator current spec when the runtime factory is reused after a switch', async () => { - const cwd = await mkdtemp(join(tmpdir(), 'brunch-tui-graph-switch-')); - const agentDir = await mkdtemp(join(tmpdir(), 'brunch-agent-dir-')); - const coordinator = createWorkspaceSessionCoordinator({ cwd }); - const first = await coordinator.createSetupSession({ - specTitle: 'First spec', - createNewSpec: true, - }); - const productUpdates = createProductUpdatePublisher(); - const observedUpdates: Array = []; - const unsubscribe = productUpdates.subscribe((updates) => { - observedUpdates.push(updates); - }); - const createRuntime = createBrunchAgentSessionRuntimeFactory({ - workspace: first, - coordinator, - productUpdates, - }); - const second = await coordinator.createSetupSession({ - specTitle: 'Second spec', - createNewSpec: true, - }); - - const created = await createRuntime({ - cwd, - agentDir, - sessionManager: second.session.manager, - }); - - try { - const mutateGraph = created.session.getToolDefinition('mutate_graph') as - | { - execute: ( - id: string, - params: unknown, - signal?: AbortSignal, - onUpdate?: unknown, - ctx?: unknown, - ) => unknown; - } - | undefined; - expect(mutateGraph).toBeDefined(); - - await mutateGraph!.execute( - 'commit-after-switch', - { - ops: [ - { op: 'create_node', ref: 'n1', plane: 'intent', kind: 'goal', title: 'Second current goal' }, - ], - }, - undefined, - undefined, - undefined, - ); - - const graph = await openWorkspaceGraphRuntime(cwd); - expect(graph.forSpec(first.spec.id).queryGraph().nodes).toHaveLength(0); - expect( - graph - .forSpec(second.spec.id) - .queryGraph() - .nodes.map((node) => node.title), - ).toEqual(['Second current goal']); - expect(observedUpdates).toEqual([ - [ - { topic: 'graph.overview', specId: second.spec.id, lsn: expect.any(Number) }, - { topic: 'graph.nodeNeighborhood', specId: second.spec.id, lsn: expect.any(Number) }, - ], - ]); - } finally { - unsubscribe(); - created.session.dispose(); - } - }); - it('runs inspect, preflight, activation, and decision propagation before launching interactive mode', async () => { const events: string[] = []; const workspace = readyWorkspace('/tmp/project', 'session-ready'); diff --git a/src/app/__tests__/pi-runtime.test.ts b/src/app/__tests__/pi-runtime.test.ts new file mode 100644 index 000000000..ab5a387f8 --- /dev/null +++ b/src/app/__tests__/pi-runtime.test.ts @@ -0,0 +1,116 @@ +import { mkdtemp } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; + +import { describe, expect, it } from 'vitest'; + +import { openWorkspaceGraphRuntime } from '../../graph/index.js'; +import { createProductUpdatePublisher } from '../../rpc/product-updates.js'; +import { createWorkspaceSessionCoordinator } from '../../session/workspace-session-coordinator.js'; +import { createBrunchAgentSessionRuntimeFactory } from '../brunch-tui.js'; + +describe('Brunch Pi runtime', () => { + it('registers graph and read-only tools without built-in write tools on the product runtime path', async () => { + const cwd = await mkdtemp(join(tmpdir(), 'brunch-tui-graph-runtime-')); + const agentDir = await mkdtemp(join(tmpdir(), 'brunch-agent-dir-')); + const coordinator = createWorkspaceSessionCoordinator({ cwd }); + const workspace = await coordinator.createSetupSession({ + specTitle: 'Graph runtime', + createNewSpec: true, + }); + const createRuntime = createBrunchAgentSessionRuntimeFactory({ workspace, coordinator }); + const created = await createRuntime({ + cwd, + agentDir, + sessionManager: workspace.session.manager, + }); + + try { + const toolNames = created.session.getAllTools().map((tool) => tool.name); + expect(toolNames).toContain('mutate_graph'); + expect(toolNames).toContain('read_graph'); + expect(toolNames).toEqual(expect.arrayContaining(['read', 'grep', 'find', 'ls'])); + expect(toolNames).not.toEqual(expect.arrayContaining(['bash', 'edit', 'write'])); + const activeToolNames = created.session.getActiveToolNames(); + expect(activeToolNames).toEqual(expect.arrayContaining(['read', 'grep', 'find', 'ls'])); + expect(activeToolNames).not.toEqual(expect.arrayContaining(['bash', 'edit', 'write'])); + } finally { + created.session.dispose(); + } + }); + + it('binds graph tools to the coordinator current spec when the runtime factory is reused after a switch', async () => { + const cwd = await mkdtemp(join(tmpdir(), 'brunch-tui-graph-switch-')); + const agentDir = await mkdtemp(join(tmpdir(), 'brunch-agent-dir-')); + const coordinator = createWorkspaceSessionCoordinator({ cwd }); + const first = await coordinator.createSetupSession({ + specTitle: 'First spec', + createNewSpec: true, + }); + const productUpdates = createProductUpdatePublisher(); + const observedUpdates: Array = []; + const unsubscribe = productUpdates.subscribe((updates) => { + observedUpdates.push(updates); + }); + const createRuntime = createBrunchAgentSessionRuntimeFactory({ + workspace: first, + coordinator, + productUpdates, + }); + const second = await coordinator.createSetupSession({ + specTitle: 'Second spec', + createNewSpec: true, + }); + + const created = await createRuntime({ + cwd, + agentDir, + sessionManager: second.session.manager, + }); + + try { + const mutateGraph = created.session.getToolDefinition('mutate_graph') as + | { + execute: ( + id: string, + params: unknown, + signal?: AbortSignal, + onUpdate?: unknown, + ctx?: unknown, + ) => unknown; + } + | undefined; + expect(mutateGraph).toBeDefined(); + + await mutateGraph!.execute( + 'commit-after-switch', + { + ops: [ + { op: 'create_node', ref: 'n1', plane: 'intent', kind: 'goal', title: 'Second current goal' }, + ], + }, + undefined, + undefined, + undefined, + ); + + const graph = await openWorkspaceGraphRuntime(cwd); + expect(graph.forSpec(first.spec.id).queryGraph().nodes).toHaveLength(0); + expect( + graph + .forSpec(second.spec.id) + .queryGraph() + .nodes.map((node) => node.title), + ).toEqual(['Second current goal']); + expect(observedUpdates).toEqual([ + [ + { topic: 'graph.overview', specId: second.spec.id, lsn: expect.any(Number) }, + { topic: 'graph.nodeNeighborhood', specId: second.spec.id, lsn: expect.any(Number) }, + ], + ]); + } finally { + unsubscribe(); + created.session.dispose(); + } + }); +}); From 74b2f1f8a8cb473ff062ea458ee9f44415b64f75 Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Fri, 26 Jun 2026 12:27:03 +0200 Subject: [PATCH 41/54] Reconcile app runtime topology notes --- src/app/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/app/README.md b/src/app/README.md index 70f1607f9..f82cea94d 100644 --- a/src/app/README.md +++ b/src/app/README.md @@ -16,6 +16,12 @@ Current entrypoints: - `brunch-tui.ts` — TUI launch path, embedded Pi session runtime wiring, and the web sidecar (`startWebHost`; browser launch is opt-in via `--open-web`). +Current runtime support modules: + +- `pi-session-options.ts` — internal Brunch-to-Pi session option projection for + lifecycle forwarding, tool hardening, thinking preset, and optional concrete + model override. + ## Does not own - Graph truth, command execution, or persistence — `graph/` and `db/`. From 116021664b92bc109842666f6ac0b810dd90434e Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Fri, 26 Jun 2026 13:19:45 +0200 Subject: [PATCH 42/54] ad-hoc re-homing of all tests inside of src/.pi --- memory/SPEC.md | 85 ++++++++++--------- ...ntime-posture-axis-picker.harness.test.ts} | 8 +- .../runtime-posture-axis-picker.test.ts} | 6 +- .../__tests__/tui-lab-cycle.test.ts | 4 +- ...workspace-dialog-preflight.harness.test.ts | 6 +- .../__tests__/workspace-dialog.test.ts | 12 +-- .../agent-runtime-authority-matrix.test.ts} | 13 +-- .../__tests__/agent-runtime-runtime.test.ts} | 2 +- .../agent-runtime-system-prompts.test.ts} | 22 ++--- .../__tests__/brunch-data-context.test.ts} | 14 +-- .../brunch-data-elicitation.test.ts} | 12 +-- .../__tests__/brunch-data-graph.test.ts} | 18 ++-- .../brunch-data-reconciliation.test.ts} | 14 +-- .../{ => extensions}/__tests__/chrome.test.ts | 6 +- .../commands-runtime-switch.test.ts} | 10 +-- .../dev-mode-introspect-query.test.ts} | 12 +-- .../__tests__/dev-mode-introspection.test.ts} | 8 +- .../dev-mode-session-query.test.ts} | 4 +- .../__tests__/exchanges-boundaries.test.ts} | 0 .../exchanges-editor-envelope.test.ts} | 6 +- .../__tests__/exchanges-extension.test.ts} | 2 +- .../exchanges-present-request.test.ts} | 8 +- .../__tests__/exchanges-schemas.test.ts} | 2 +- .../__tests__/mentions.test.ts} | 2 +- .../__tests__/registry.test.ts} | 26 +++--- .../session-hooks-lifecycle.test.ts} | 2 +- .../subagents.test.ts | 8 +- .../__tests__/tui-lab.test.ts} | 6 +- .../web => __tests__}/web-tools.test.ts | 2 +- 29 files changed, 163 insertions(+), 157 deletions(-) rename src/.pi/{__tests__/runtime-axis-picker.harness.test.ts => components/__tests__/runtime-posture-axis-picker.harness.test.ts} (94%) rename src/.pi/{__tests__/runtime-axis-picker.test.ts => components/__tests__/runtime-posture-axis-picker.test.ts} (97%) rename src/.pi/{ => components}/__tests__/tui-lab-cycle.test.ts (96%) rename src/.pi/{ => components}/__tests__/workspace-dialog-preflight.harness.test.ts (90%) rename src/.pi/{ => components}/__tests__/workspace-dialog.test.ts (97%) rename src/.pi/extensions/{agent-runtime/runtime/authority-matrix.test.ts => __tests__/agent-runtime-authority-matrix.test.ts} (88%) rename src/.pi/{__tests__/operational-mode.test.ts => extensions/__tests__/agent-runtime-runtime.test.ts} (99%) rename src/.pi/{__tests__/prompting.test.ts => extensions/__tests__/agent-runtime-system-prompts.test.ts} (96%) rename src/.pi/{__tests__/context-tools.test.ts => extensions/__tests__/brunch-data-context.test.ts} (97%) rename src/.pi/extensions/{brunch-data/elicitation/index.test.ts => __tests__/brunch-data-elicitation.test.ts} (96%) rename src/.pi/{__tests__/graph-tools.test.ts => extensions/__tests__/brunch-data-graph.test.ts} (92%) rename src/.pi/extensions/{brunch-data/reconciliation/index.test.ts => __tests__/brunch-data-reconciliation.test.ts} (93%) rename src/.pi/{ => extensions}/__tests__/chrome.test.ts (98%) rename src/.pi/{__tests__/runtime-switch-command.test.ts => extensions/__tests__/commands-runtime-switch.test.ts} (97%) rename src/.pi/extensions/{dev-mode/introspect-query/index.test.ts => __tests__/dev-mode-introspect-query.test.ts} (98%) rename src/.pi/{__tests__/introspection.test.ts => extensions/__tests__/dev-mode-introspection.test.ts} (97%) rename src/.pi/extensions/{dev-mode/session-query/index.test.ts => __tests__/dev-mode-session-query.test.ts} (98%) rename src/.pi/{__tests__/structured-exchange-boundaries.test.ts => extensions/__tests__/exchanges-boundaries.test.ts} (100%) rename src/.pi/{__tests__/structured-exchange-editor-envelope.test.ts => extensions/__tests__/exchanges-editor-envelope.test.ts} (91%) rename src/.pi/{__tests__/structured-exchange-extension.test.ts => extensions/__tests__/exchanges-extension.test.ts} (98%) rename src/.pi/{__tests__/structured-exchange-present-request.test.ts => extensions/__tests__/exchanges-present-request.test.ts} (99%) rename src/.pi/{__tests__/structured-exchange-schemas.test.ts => extensions/__tests__/exchanges-schemas.test.ts} (99%) rename src/.pi/{__tests__/mention-autocomplete.test.ts => extensions/__tests__/mentions.test.ts} (98%) rename src/.pi/{__tests__/extension-registry.test.ts => extensions/__tests__/registry.test.ts} (95%) rename src/.pi/extensions/{session-hooks/session/lifecycle.test.ts => __tests__/session-hooks-lifecycle.test.ts} (98%) rename src/.pi/extensions/{subagents => __tests__}/subagents.test.ts (99%) rename src/.pi/{__tests__/tui-lab-style.test.ts => extensions/__tests__/tui-lab.test.ts} (96%) rename src/.pi/extensions/{web-tools/web => __tests__}/web-tools.test.ts (99%) diff --git a/memory/SPEC.md b/memory/SPEC.md index 6fdd31ec6..7b5761879 100644 --- a/memory/SPEC.md +++ b/memory/SPEC.md @@ -72,11 +72,11 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c #### Elicitation product shape -16. Brunch must keep sessions elicitation-first and offer-first **by default**: under the structured elicitation strategies, at idle the user is responding to a system/assistant-originated elicitation prompt or structured offer rather than initiating ambient free chat. The `freestyle` strategy refines this (D66-L): it permits user-initiated ambient turns while keeping structured-exchange tools available and graph capture flowing, so offer-first is a property of the structured strategies, not a universal per-turn session invariant. Freestyle is at minimum a dev-facing affordance and tentatively a user-facing one. +16. Brunch SPEC-mode sessions are elicitation-first and offer-first **by default**: at idle, the user is normally responding to a system/assistant-originated elicitation prompt, structured offer, or agent-authored next move rather than driving an ambient chat. The product may still permit user-initiated turns, pasted material, and referenced-document/codebase acquisition, but those are inputs into the elicitor's capture/generate/project work rather than separate runtime strategy state. 17. Brunch must support action, radio (single-select), checkbox (multi-select), freeform, and freeform-plus-choice response surfaces as typed transcript-backed interactions. Every option-selection structured exchange must allow an optional user-authored `comment` as additional context separate from custom/Other answers. Multi-question/questionnaire surfaces are deferred; when a complex shape is needed before a bespoke UI lands, Brunch may use just-in-time schema-tagged JSON over `ctx.ui.editor` or an equivalent product relay. In TUI mode a pending response request may replace the default input surface with custom UI; in RPC/probe/web-relay contexts the same semantic interaction may travel through Brunch product handlers or Pi's supported extension UI dialogs. Brunch must be able to project session exchanges from Pi JSONL for post-exchange capture, including registered structured-exchange tool results whose `toolResult.details` is the self-contained structured response payload. 18. Brunch must support `#`-mentions of graph entities anchored to stable human reference codes (for example `#REQ3`), resolved internally to graph node IDs, with session-scoped staleness tracking that produces discretionary re-read hints during `prepareNextTurn`. 19. Brunch must enforce a workspace state hierarchy `workspace(cwd) → spec → session`, where the workspace is only the current working directory invocation root, the user explicitly picks or creates one spec within that workspace before any agent loop runs, and then picks or creates a session within that spec. Spec selection persists across `/new`, and each session binds to exactly one spec. -20. Brunch must support multiple elicitation lenses within the `elicitor` agent role, with the agent owning lens selection and offer through transcript-native structured exchanges; lens metadata is carried on elicitor-emitted structured-exchange payload facets for downstream routing. +20. Brunch must support the elicitor's full SPEC-mode capability spine — capture arbitrary unstructured material into graph truth, generate candidate intent/design/oracle graph concepts, and project existing graph subsets across planes — without exposing strategy/lens/method as user-changeable or transcript-backed runtime axes. Plane/lens/method language may remain as prompt-resource organization or internal reasoning vocabulary only where it demonstrably improves agent behavior. 21. Brunch must distinguish single-exchange elicitation flows from batch-proposal/review-set flows by capture and commitment mechanism: single-exchange answers are captured synchronously by the elicitor at turn boundaries, while batch proposals carry structured entity-draft payloads and are committed only through review-set approval. 22. Brunch must judge readiness just-in-time, per requested capability, against the `elicitation_gaps` relevant to it — not as a spec-owned stored grade (D45-L, D74-L). Grounding establishes the frame required for generative capabilities, but readiness never forbids earlier gathering or refinement: it proceeds, scales output epistemic status, or negotiates ("I can, but answer X and Y first"). A soft, derived readiness estimate may surface in the UI but gates nothing. 23. Brunch must support a review-cycle acceptance pattern for batch proposals and commitment review sets — approve / request changes (triggering regeneration) / reject — with batch acceptance committed atomically as one CommandExecutor call; partial acceptance is not representable. @@ -89,7 +89,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c #### Runtime profile & prompting 25. Brunch must run the embedded Pi harness through a sealed Brunch Pi Profile: programmatic settings, resource-loader, extension-factory, keybinding, tool, and prompt policy must determine product behavior; ambient user/project `.pi/` resources must not influence Brunch sessions unless Brunch deliberately imports them. -26. Brunch must distinguish transport modes from operational modes and agent roles: an operational mode is 1:1 with its foreground agent and is the single op-mode-keyed source of truth for that agent (D93-L) — `elicit`→`elicitor` and `execute`→`orchestrator` are live now; `code`→`pi-coder` remains the planned direct-coding mode. Each foreground agent carries its own model/thinking preset, skills grant (strategy/lens/method resources), tool policy, and the set of background agents it may delegate to (`canDelegate`); background agents share the same `AgentManifest` shape (D90-L). +26. Brunch must distinguish transport modes from operational modes and agent roles: operational mode is the only user-changeable session-agent state, exposed as `SPEC` or `CODE` (D98-L). `SPEC` runs the elicitor for specification-building work. `CODE` runs the executor: a Brunch-aware coding assistant that merges the prior `orchestrator` and `pi-coder` directions, can use Brunch graph/session context, and owns an `orchestrate` tool for plan execution rather than splitting planning orchestration from coding assistance. Background agents may still share the `AgentManifest` shape (D90-L), but strategy/lens/method are suspended as runtime state. ## Live Architecture Register @@ -120,7 +120,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c | A32-L | Fan-in across all generative planes collapses to the three-value `pick` / `synthesize` / `compose` mode carryable by `present_candidates` + the review-set path (D27-L), without a plane needing a fourth disposition or a bespoke commit path. | medium | partially validated | D96-L; I51-L | 2026-06-24: design-plane `synthesize` proved expressible as method conduct over `present_candidates` recognition followed by a synthesized `present_review_set` commit review, with no `fan_in_mode` field. Later 2026-06-24 the oracle-plane `compose` facet was expressible as additive ensemble composition in reasoning followed by `present_review_set → request_response → acceptReviewSet`, still with no `fan_in_mode`, multi-select affordance, or bespoke commit path. The promoted fan-out run proves the oracle branch reaches `present_candidates` and preserves I51-L before any pick; it deliberately does not prove same-turn `request_response → present_review_set` completion. Remaining proof: manual/live oracle-compose fan-in evidence; if it needs multi-select in practice, that falsifies the no-field claim and routes a schema/affordance slice. | | A33-L | The `project` capability (cross-plane derivation — requirements→design, design→oracles — with connecting edges) has a module shape distinct enough from `generate` to warrant its own `ln-design` pass before build; it is not merely `generate` with a graph-subset input. | medium | open | D95-L | The `project` `ln-design` pass: if it reduces to `generate` parameterized by an upstream-graph input, fold it in; if cross-plane edge derivation + provenance need their own surface, it stays distinct. | | A34-L | The acquisition-subagent arm of capture (explore-and-characterize / research delegated to background agents returning a digest, D82-L near-future) can ride the `subagent-reconciliation` foreground/background manifest (D90-L–D93-L) without a capture-specific agent model. | medium | open | D82-L, D90-L, D95-L | Once `subagent-reconciliation` lands the background roster, an acquisition agent + digest handback wires through `canDelegate` (I49-L) with no new agent substrate; needing one falsifies it. | -| A35-L | The `strategy` / `lens` / `method` skill axis model (D85-L) is the right framing to hold frozen while the capability spine (D95-L) builds on top of it; the capabilities need no re-axing or supersession of it. | low | open | D85-L, D95-L | Build the capability skills on the frozen axes: if capture/generate/project sit cleanly as method-routed conduct over `strategy` + `lens`, the axis model earns its keep; recurring friction (a capability inexpressible as method/lens) reopens the re-axis question. | +| A35-L | The `strategy` / `lens` / `method` skill axis model may still be useful as prompt-resource organization, but it is no longer trusted as user-changeable or transcript-backed runtime state. Operational mode should narrow to `SPEC` / `CODE` while the elicitor capability spine proves what guidance shape actually works. | medium | suspended | D85-L, D95-L, D98-L | Runtime-state use is suspended by D98-L. Validate only the narrower claim: prompt-resource modularization earns its keep if capture/generate/project behavior improves when agents load concise references/skills on demand; if not, collapse or reshape the resources without preserving the axis model. | ### Active Decisions @@ -130,7 +130,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c - **D2-L — Brunch is an opinionated product, not a pi platform shell.** The POC hardcodes its toolset, system prompt, and policy doctrine; scopes state to `.brunch/`; and hides pi's generic extension surface from end users. Depends on: A1-L. Supersedes: —. - **D39-L — Brunch owns sealed Pi settings plus an explicit Brunch extension bundle around the embedded harness.** Product behavior must come from Brunch-owned programmatic policy, not ambient Pi discovery. The settings layer must stay sealed (no ambient global/project `.pi` behavior shaping, no ambient resource discovery, offline-by-default for Brunch-launched Pi), and the product extension bundle must remain an explicit static registrar list rather than any filesystem discovery or runtime `import()` path. Current sealed-profile policy and bundle topology live in [`src/.pi/README.md`](src/.pi/README.md), [`src/.pi/extensions/README.md`](src/.pi/extensions/README.md), [`src/app/pi-settings.ts`](src/app/pi-settings.ts), [`src/app/pi-extensions.ts`](src/app/pi-extensions.ts), and [`src/dev/README.md`](src/dev/README.md). Depends on: D1-L, D2-L, A19-L. Supersedes: treating `noSkills: true` as full profile isolation, relying on user/project `.pi/` defaults to be harmless, nesting Brunch's product extension modules under `src/.pi/extensions/brunch/`, or replacing the explicit static extension list with a Brunch-internal filesystem-discovery / `brunchExtensionMeta` / `loadOrder` mechanism as the product runtime load path. - Tooling exception: the worktree helper extension now lives outside this repository under the user Pi agent tree (`~/.pi/agent/extensions/worktree/index.ts`) for direct Pi sessions only. It is not a Brunch product extension, is not imported by `src/.pi/brunch-pi-extensions.ts`, and does not weaken the sealed Brunch Pi settings/extensions boundary; Brunch-launched product sessions continue to disable ambient `.pi/` discovery unless deliberately imported. The extension may register direct-Pi `/worktree:switch` / `switch_worktree` and `/worktree:create` / `create_worktree` affordances, but Brunch does not test, package, or document it as a product extension. -- **D40-L — Runtime state is transcript-backed Brunch session-agent state, not hidden extension memory.** The architectural commitment is that Brunch session-agent posture remains transcript-backed Pi JSONL state rather than hidden extension memory: posture switches are user/system authority, the foreground session agent is derived from `op_mode`, child axes default back to `AUTO` when invalidated, and tool authority remains `op_mode`-gated rather than prompt-composition-owned. Runtime-state entries are Pi JSONL state-change facts, not assistant/user chat content: init and switch entries should render, when visible, as dim non-chat state rows analogous to Pi thinking/model-change rows, and must not enter LLM context as ordinary conversation. Current materialized state lives in [`src/session/README.md`](src/session/README.md), [`src/projections/README.md`](src/projections/README.md), [`src/.pi/README.md`](src/.pi/README.md), [`src/agents/README.md`](src/agents/README.md), [`src/agents/prompts/README.md`](src/agents/prompts/README.md), and [`src/agents/runtime/README.md`](src/agents/runtime/README.md). Depends on: D17-L, D23-L, D25-L, D39-L, D58-L, D59-L. Refined by: D85-L (the AUTO-able objective axes reduce to `strategy` + `lens`; `goal` is dropped from the runtime-state axis set and inlined into the agent role prompt). Supersedes: mode-only vocabulary, extension-local mutable state as authority, storing the foreground role as independent session state, the "runtime bundle / role preset" as one knob deriving model/thinking/resources, and binding prompt-resource location to `src/.pi/context/`. +- **D40-L — Runtime state is transcript-backed Brunch session-agent state, not hidden extension memory.** The architectural commitment is that Brunch session-agent posture remains transcript-backed Pi JSONL state rather than hidden extension memory: posture switches are user/system authority, the foreground session agent is derived from operational mode, and tool authority remains mode-gated rather than prompt-composition-owned. D98-L narrows the mutable runtime state to operational mode only: strategy/lens/method are suspended as runtime axes, so there are no child prompt-resource axes to persist, invalidate, or reset to `AUTO`. Runtime-state entries are Pi JSONL state-change facts, not assistant/user chat content: init and switch entries should render, when visible, as dim non-chat state rows analogous to Pi thinking/model-change rows, and must not enter LLM context as ordinary conversation. Current materialized state lives in [`src/session/README.md`](src/session/README.md), [`src/projections/README.md`](src/projections/README.md), [`src/.pi/README.md`](src/.pi/README.md), [`src/agents/README.md`](src/agents/README.md), [`src/agents/prompts/README.md`](src/agents/prompts/README.md), and [`src/agents/runtime/README.md`](src/agents/runtime/README.md). Depends on: D17-L, D23-L, D39-L, D58-L, D98-L. Supersedes: mode-only vocabulary, extension-local mutable state as authority, storing the foreground role as independent session state, the "runtime bundle / role preset" as one knob deriving model/thinking/resources, binding prompt-resource location to `src/.pi/context/`, and runtime persistence for strategy/lens/method axes. - **D34-L — Command containment separates visibility suppression from effect blocking.** Current Pi extension seams can hide unsupported slash suggestions with autocomplete wrapping and can cancel branch/session effects through lifecycle hooks, but they cannot strictly suppress exact interactive built-in commands before `InteractiveMode` dispatches them. Brunch-owned commands must use product-specific names and route writes through Brunch handlers/`CommandExecutor`; extension command collisions are not an override mechanism. Strict built-in command/keybinding policy is a Pi upstream/API ask, while POC safety relies on hiding generic affordances, blocking dangerous effects (`/fork`, `/clone`, raw session replacement), allowing native `/tree` as inspection/navigation, and failing fast on branched transcripts. Brunch's command-policy code should live in `src/.pi/extensions/commands/policy.ts`, merging branch/session-effect blocking with any product command allow/deny behavior instead of preserving a branch-only module. Depends on: D2-L, D24-L, A18-L. Supersedes: treating extension `input` handlers or command-name collisions as built-in command allowlisting. - **D35-L — Dynamic TUI chrome is a Brunch projection wrapper over Pi UI primitives.** The architectural commitment is that downstream TUI affordances call one Brunch-owned renderer (`renderBrunchChrome` or its successor) with a single activated product-state value rather than scattering raw `ctx.ui.setHeader` / `setFooter` / `setWidget` / title / working-indicator calls; the wrapper is stateless projection over canonical workspace/session/graph facts, never its own mutable state. Chrome is a project-first shell surface with selected-spec context — project name labels the cwd container, spec title labels the selected graph, session label distinguishes transcript instances — and a session label must never replace spec identity or graph truth. Chrome must not consume the status-key namespace for its own summary (`ctx.ui.setStatus` stays a lateral channel for other extensions), must not advertise unwired affordances, and RPC clients must rely only on surfaces Pi actually emits (header/footer/working-indicator are TUI-only in current Pi RPC mode). Current chrome state shape, render surfaces, telemetry/refresh, startup-header behavior, and status-key filtering live in [`src/.pi/extensions/chrome/README.md`](src/.pi/extensions/chrome/README.md); launch/activation wiring lives in [`src/.pi/extensions/README.md`](src/.pi/extensions/README.md). Depends on: D2-L, D21-L, D34-L, A18-L. Supersedes: treating Pi UI methods as direct downstream affordance APIs, rendering placeholder session state such as `unbound` after a session is activated, consuming the status-key namespace for chrome's own static summary, using spec title as the default session label, or allowing two unchanged Brunch-created default names to collide in one cwd, and the earlier resume/open-launches-stay-quiet clause (superseded 2026-06-11: the shipped, test-locked behavior headers every non-cancel activation). - **D52-L — Source topology targets `src/{app, workspace, scripts, agents, .pi, db, graph, session, projections, rpc, web}` with directed layer dependencies.** Reusable projection modules live in top-level `src/projections/`; human/product text rendering stays beside its app/session owner rather than a shallow shared layer; `src/agents/` is the Pi-independent owner for Brunch-authored LLM context ingress and foreground runtime policy (currently bundled agent prompt bodies, prompt-resource skills, foreground roster/tool policy, capability-readiness policy, prompt composition, prompt-resource/tool legality, seed context composition, reusable agent-visible renderers, adapter-local tool/session text promoted into contexts, and the central registry for prompt/skill file paths); domain layers (`graph/`, `session/`) and the reusable `projections` / `agents` layers must not import adapters, transports, app entrypoints, or web code; `graph/` is the only layer that imports `db/`, plus the single sanctioned `db/`→`graph/schema/kinds.ts` taxonomy edge (D73-L). The concrete per-directory ownership, layout sketch, and full import matrix are owned by [`src/README.md`](src/README.md). Depends on: D2-L, D4-L, D39-L, D40-L. Refined by: D73-L. Supersedes: scattering session domain files at `src/` root; treating Pi-only agents as a host-independent top-level `src/.pi/` layer; nesting prompt composition under `src/.pi/context/`; treating reusable `project` / `format` helpers as owned by whichever adapter first needed them; treating retired Pi-owned prompt/skill homes as the long-term conceptual owner for Brunch-authored model-facing content. @@ -187,7 +187,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c - **D17-L — Brunch semantics ride one transcript/event substrate, not parallel channels.** Pi JSONL transcript entries — ordinary messages, assistant tool-call/toolResult exchanges, and custom messages/entries — plus `deliverAs: "nextTurn" | "followUp"` and `prepareNextTurn` are the load-bearing mechanism for structured elicitation prompts/responses, `worldUpdate`, mention-staleness hints, and side-task-result delivery. New product semantics should compose onto this substrate before inventing a second event plane or a parallel chat/turn store. Depends on: D5-L, D6-L, D12-L, D15-L. Supersedes: custom-message-only interpretations of structured elicitation. - **D19-L — Keep product RPC/read architecture thin: named method families over projection handlers.** Brunch exposes concrete named methods, not vague feature buckets or generic records; each read projects from the canonical store that owns the fact (Pi JSONL, `.brunch/workspace.json`, or SQLite graph/change log), and each mutation routes to the Brunch-owned command/session layer. Brunch must not create a generic read-gateway platform, REST read model, DB-backed chat/turn projection, or canonical cross-store event spine merely to keep clients in sync; `brunch.updated` / `brunch.sessionEvent` are process-local invalidation/observer hints, not canonical truth (D84-L). The concrete method surface, notification payloads, `dev.*` gating, and reserved/retired names are owned by [`src/rpc/README.md`](src/rpc/README.md). Depends on: D5-L, D6-L, D10-L, D16-L, D84-L. Supersedes: the heavier “unified read gateway” mental model, vague `elicitation.*` / `command.*` public families, and any discovery/dispatch split where a surface describes methods it rejects. - **D84-L — `SessionEventRelay` is the process-local observer seam for live Pi session events.** The TUI-started web sidecar may stream the live in-process `AgentSession` by sharing a process-local relay: `runBrunchTui` creates one `SessionEventRelay`, `startWebHost`/`attachWebRpcTransport` subscribes to it, and `createBrunchAgentSessionRuntimeFactory` attaches `runtime.session` only after Pi creates the live `AgentSession`. The relay holds no store and crosses no process boundary; it forwards each `AgentSessionEvent` as a Brunch-owned JSON-RPC notification (`brunch.sessionEvent`, `{seq, event}`) multiplexed on the existing `/rpc` socket with `brunch.updated`. Pi event types inside `event` are payload data, not a second public method vocabulary. Browser clients may observe/render these frames, but canonical session truth remains the Pi JSONL transcript plus named `session.*` projections. Command-intake is the re-entry seam: `session.driveTurn` re-enters the live `AgentSession` for one plain prompt, and `session.answerExchange` resolves a Brunch-owned live-exchange broker awaited by Brunch-authored `request_answer` when no interactive TUI editor is authoritative; both let the resulting `AgentSessionEvent` stream fan out through the relay. Live driver authority is connection-scoped, not process-global: ordinary `/rpc` observer sockets always discover and dispatch the read-only sidecar registry, while the explicitly designated `/rpc/driver` socket discovers the live driver family when handles exist. A richer editor-or-broker race for future web-as-driver-with-TUI sessions is deferred until the live-exchange awaiter has a cancellation path; current POC posture keeps one driver and treats the TUI editor as the response surface when it exists. A driverless sidecar does not discover or dispatch these methods (`-32601`); an attached turn-driver handle that reports no current live session returns the named no-live-driver error (`-32010`), while an attached answer-broker handle with no matching pending exchange returns the named no-pending-exchange error (`-32008`) without string-matching thrown sentinels. Depends on: D5-L, D10-L, D19-L, D37-L, D49-L, A28-L. Supersedes: hand-built spike relays, a second WebSocket for session events, and any DB-backed chat/turn projection for live streaming. -- **D23-L — Transport modes, operational modes, agent roles, strategies, and lenses are separate axes.** TUI, RPC, print, and web are transport modes: ways of driving or observing the same Brunch host through Pi/Brunch harness seams. Operational modes are top-level authority/tooling postures such as live `elicit` and `execute`, plus planned `code`. Agent roles are active workers within an operational mode (`elicitor`, `orchestrator` (the live `execute` coordinator with direct writes blocked), future `pi-coder` (the `code` direct-coding mode), background `explorer`, `researcher`, `projector`, `reviewer`, and any deferred observer/auditor); each foreground role is 1:1 with its op_mode under the collapsed source of truth (D93-L). Strategies are interaction plans; lenses are narrower interpretive/extraction/review framings. M1 print mode is therefore only a transport proof-of-life: it boots through the same host/coordinator, renders current product-shaped state, and exits without running an agent turn. A future single-turn headless print run is deferred until runtime bundle selection/defaults are explicit. Depends on: D1-L, D5-L, D19-L, D21-L, D40-L. Supersedes: overloading “mode” to mean both transport and agent strategy, or using “agent mode” for role/preset/lens interchangeably. +- **D23-L — Transport modes, operational modes, and agent roles are separate; prompt-resource axes are no longer runtime state.** TUI, RPC, print, and web are transport modes: ways of driving or observing the same Brunch host through Pi/Brunch harness seams. Operational modes are top-level authority/tooling postures and are the only user-changeable session-agent state; D98-L names the product target as `SPEC` and `CODE`. Agent roles are active workers within an operational mode (`elicitor` for SPEC, `executor` for CODE, background `explorer`, `researcher`, `projector`, `reviewer`, and any deferred worker/auditor); each foreground role is 1:1 with its operational mode under the collapsed source of truth (D93-L/D98-L). Strategy/lens/method language may remain as prompt-resource or internal reasoning organization, but it is suspended as TUI affordance, runtime-state axis, and transcript-backed posture. M1 print mode is therefore only a transport proof-of-life: it boots through the same host/coordinator, renders current product-shaped state, and exits without running an agent turn. A future single-turn headless print run is deferred until runtime bundle selection/defaults are explicit. Depends on: D1-L, D5-L, D19-L, D21-L, D40-L, D98-L. Supersedes: overloading “mode” to mean both transport and agent strategy, using “agent mode” for role/preset/lens interchangeably, or exposing strategy/lens/method selection as product runtime state. - **D33-L — Transport connections are client attachments, not Brunch sessions.** A Brunch session is a durable linear Pi JSONL transcript bound to exactly one spec; WebSocket connections, stdio streams, TUI instances, and browser tabs are ephemeral presentation attachments to product resources. Session-specific RPC methods should name their target spec/session explicitly or operate through an explicit client attachment; they must not infer durable session identity merely from the transport connection. `.brunch/workspace.json` remains launch/default acceleration, not concurrency authority. During the POC, Brunch targets a one-driver/many-observer local model: one interactive driver at a time (typically TUI/agent, now also a web sidecar client through the narrow `session.driveTurn` re-entry method) may drive the live session while other web clients observe. No write-lease/concurrent-driver arbiter exists yet. Depends on: D5-L, D10-L, D11-L, D19-L, D21-L, D24-L. Supersedes: treating `/rpc`, a WebSocket, or workspace default state as the active session itself. - **D72-L — The web client's visual design system is ported from the prior trunk (`../brunch/src/client`), not freshly invented.** The browser surface should continue to inherit that earlier restrained design language rather than re-inventing an unrelated aesthetic, and web remains a read-only presentation surface in the current POC (no new primary data plane). Current tokens, primitives, grouped graph rendering, and sidecar query/subscription wiring live in [`src/web/README.md`](src/web/README.md), [`src/web/styles.css`](src/web/styles.css), [`src/web/components/drawer-card.tsx`](src/web/components/drawer-card.tsx), [`src/web/components/node-card.tsx`](src/web/components/node-card.tsx), [`src/web/features/graph/structured-list-view.tsx`](src/web/features/graph/structured-list-view.tsx), [`src/web/routes/root.tsx`](src/web/routes/root.tsx), [`src/web/queries/workspace.ts`](src/web/queries/workspace.ts), and [`src/web/subscriptions/brunch-updates.ts`](src/web/subscriptions/brunch-updates.ts). **(2026-06-15 forward note:)** read-only is POC staging, not a foreclosure — web-as-driver (command intake + streaming over the same transport) is owned by the `web-driver-streaming` frontier (topology A). Full first-trunk layout fidelity (phase-navigation rail, center acceptance-criteria column, three-pane spec workspace) is explicitly out of scope. Depends on: D10-L, D52-L, D62-L. Supersedes: the agent-invented "warm brunch" web aesthetic — paper/card warm palette, body radial/linear gradients, `backdrop-blur`, oversized radii (`rounded-[2rem]`) and shadows, translucent surfaces (`bg-white/45`), wide-tracked uppercase mono labels, hover-lift "Focus node" cards, and the invented "Edge categories" chip cluster. @@ -258,7 +258,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c - **D38-L — JSON-over-editor is the Pi-RPC compatibility seam for complex extension UI, not a second product API.** Pi RPC supports `ctx.ui.select`, `confirm`, `input`, and `editor`, but not `ctx.ui.custom()`. When a structured-exchange tool needs a complex shape (multi-select, review-style response, or a deferred multi-question/questionnaire shape) over raw Pi RPC, the tool may call `ctx.ui.editor()` with schema-tagged JSON prefill and validate the returned JSON before producing normal `toolResult.content` plus self-contained `toolResult.details`. A Brunch-aware adapter may render that JSON as a native product form and translate the user response back into Pi's documented `extension_ui_response`; public clients still speak Brunch RPC methods/events, not ad hoc raw Pi RPC extensions. Depends on: D5-L, D19-L, D33-L, D37-L. Supersedes: inventing unsupported Pi RPC command types for Brunch interactions or exposing raw editor JSON as the product UX. - **D13-L — Capture-aware session exchange projection.** Post-exchange capture consumes derived session exchanges: a prompt-side span (system/assistant/tool-side entries since the previous response, including structured/internal prompt content) plus a response-side span (user text and/or terminal structured-exchange `request_*` toolResults whose `details` encode the answer). Role/span alternation is the default projection in Brunch-supported linear sessions, but typed structured-exchange results override the naive "all toolResults are prompt side" rule where needed for deterministic replay. Depends on: D12-L, D24-L, D37-L. Supersedes: treating Pi message role alone as sufficient to classify structured elicitation response spans. - **D14-L — `#`-mentions are stable graph-code text references resolved by Brunch, with a session-scoped mention ledger.** Pi autocomplete persists only the inserted `AutocompleteItem.value` as ordinary transcript text; popup labels/descriptions are UI-only. Brunch autocomplete may search by title/description, but insertion must rewrite to a stable graph node code from D62-L (`#G1`, `#CON2`, `#REQ3`, `#AC4`, etc.) that Brunch can resolve to the graph entity id through a read-only lookup/re-read tool when the agent needs detail. Brunch prompt injection (`before_agent_start`) teaches agents how to interpret these handles; Brunch-owned parsing/indexing, not Pi autocomplete, creates mention-ledger state. The ledger stores internal `(entity_id, seen_lsn)` pairs, not titles or raw code strings alone, and drives discretionary `brunch.mention_staleness_hint` entries in `prepareNextTurn`. Depends on: D62-L, I4-L (validated A9-L). Supersedes: assuming Pi autocomplete persists hidden mention metadata or using raw DB ids as user-facing handles. -- **D25-L — Strategy and lens are two orthogonal session-agent axes within the `elicitor` role, not separate roles or operational modes.** *Strategies* describe interaction shape (`step-wise-decision-tree`, `step-wise-disambiguate`, `propose-graph`, `project-graph`); *lenses* describe topical focus (`intent`, `design`, `oracle`; future execute-mode `plan`, `sync`, `scope`). Both are optional, AUTO-able fields of the projected session-agent record (D40-L) and are stamped onto structured-exchange payload facets (for example establishment offers, intent hints, and review/proposal material) when those facets need downstream routing; capture/reviewer/audit routing may filter on lens. Strategy determines the commitment mechanism (D26-L); the catalogue is expected to grow. Depends on: D23-L, D40-L. Refined by: D85-L (`propose-graph` / `project-graph` move off the strategy axis to method-routed graph-write mechanisms; the strategy axis keeps the genuine interaction shapes `step-wise-decision-tree` / `step-wise-disambiguate` / `freestyle`). Supersedes: lens-as-role, strategy-as-mode, and standalone elicitor-intent/establishment/review custom-entry families as the default carrier. +- **D25-L — Suspended: strategy and lens are prompt-resource vocabulary, not session-agent axes.** The earlier decision modeled *strategies* as interaction shapes and *lenses* as topical focus within the `elicitor` role. D98-L suspends the runtime-state part of that model: strategy/lens values must not be optional AUTO-able fields of the projected session-agent record, TUI controls, tool gates, or required structured-exchange routing facets. The useful residue is narrower: strategy/lens words may name prompt resources, reference files, or reasoning frames when they improve the elicitor's capture/generate/project work, and structured-exchange payloads may carry plane/provenance metadata when a concrete downstream reader needs it. Depends on: D23-L, D40-L, D58-L, D85-L, D98-L. Supersedes: lens-as-role, strategy-as-mode, standalone elicitor-intent/establishment/review custom-entry families as the default carrier, and runtime persistence of strategy/lens axes. - **D26-L — Elicitation flows split by capture and commitment mechanism, not by a hard extractive/generative phase boundary.** Three commitment mechanisms: (1) Single-exchange flows (`step-wise-decision-tree`, `step-wise-disambiguate`, and ordinary structured questions) are captured synchronously by the elicitor post-exchange per D18-L; graph items directly stated by the user are written with `basis: explicit`. (2) Review-set flows (`project-graph` strategy) carry structured entity-draft payloads at proposal time and become durable only through review-set approval (D27-L); accepted exact items are written with `basis: explicit`. (3) Direct-commit flows (`propose-graph` strategy) present a concept to the user via structured exchange with rubric axes, choices, and a recommendation; when the user accepts a concept, the agent autonomously generates and persists the full subgraph through `mutateGraph` (D53-L) without intermediate entity-level user review — the user accepts a concept, not a graph shape — so those materialized nodes/edges are written with `basis: implicit` (D63-L). Design/oracle lenses may appear during ordinary elicitation; commitment (`commit-converge` goal and active review-set state, D59-L) changes what can be pinned, not what topics may be explored. Depends on: D18-L, D25-L, D45-L, D53-L, D63-L. Supersedes: a single uniform "agent asks questions" mental model, the observer-owned extractive vs elicitor-owned generative split as the primary architecture, and assuming all batch-graph writes require review-set approval. - **D30-L — Grounding advances readiness for main elicitation; strategies remain available with honest epistemic signaling.** A minimum grounding bundle — *domain anchor*, *protagonist anchor*, *pain/pull anchor*, *constraint anchor* — establishes the frame required before generative capabilities are worth attempting (the relevant grounding `elicitation_gaps`, D65-L). Lenses and strategies are not refused merely because grounding is thin, but their output resolution and epistemic load must honestly reflect what grounding supports: speculative outputs are visibly hedged and lower-authority, while grounded outputs may drive capture and later review-set projection. Grounding coverage should be explicit in offers/proposals where it affects confidence or a capability-readiness negotiation (D74-L). Depends on: D26-L, D45-L, D65-L, D74-L. Supersedes: gating-by-refusal as a UX move and over-focusing readiness on generative lenses alone. - **D32-L — Establishment offers are orientation artifacts, not a default next-action menu.** Establishment-offer material records the agent's current offer tree and recommended next move as durable structured-exchange payload state when it is part of an exchange, not as a mandatory standalone transcript entry family. Ambient chrome or web affordances may render the latest establishment-offer facet, and Brunch may expose a user-invoked orientation view summarizing what is established vs open, but Brunch does not surface an exhaustive lens/offer chooser by default; the agent still owns next-move selection unless the user explicitly asks to inspect alternatives. Depends on: D25-L, D30-L, A15-L. Supersedes: UI interpretations that turn establishment offers into a persistent strategy menu or separate transcript store. @@ -270,14 +270,14 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c - **D44-L — Subagents are main-agent-invoked, blocking Pi tool calls that gather data and propose variants through sealed SDK child sessions.** Brunch may register a single `subagent` Pi tool whose parameters are either `{ agent, task }` or `{ tasks: [] }` (parallel), never both. Each invocation runs an in-process SDK `AgentSession` built from explicit sealed services: in-memory settings/auth/session managers, no ambient resource discovery, a per-agent system prompt, the parent's model registry, and an explicit read-only/no-tool allowlist. The subagent has no inherited conversation context, so the task string must carry everything it needs. Background agent definitions are declarative `SYSTEM.md` bodies under the shared `src/agents/prompts//SYSTEM.md` convention with TypeBox-validated frontmatter (`name`, `description`, `tools`, `model`, `thinking`) plus a system-prompt body; duplicate frontmatter keys fail loud. Concurrency cap lives in [src/.pi/extensions/subagents/config.json](src/.pi/extensions/subagents/config.json) (default 4). The subagent's result text is returned directly to the main agent as tool result content; subagents do not append custom messages to the session log on their own behalf, do not invoke the `CommandExecutor`, and do not gain access to the parent's Brunch RPC handlers. Registration is opt-in: `src/app/pi-subagents.ts` can assemble deps for `createBrunchPiExtensions(...)`, but launch paths that omit those deps do not register or advertise the tool. POC starter agents split into two families: - **Data gatherers** — read-only context fetchers whose output grounds proposals: **explorer** (codebase + selected-spec graph recon: `read`, `grep`, `find`, `ls`, `read_graph`) and **researcher** (web research: `web_search`, `web_fetch`). `read_graph` is granted only when the app root injects parent graph readers; no write-capable graph child exists yet. - **Projectors/reviewers** — **projector** (no tools) emits one variant of a candidate proposal from a grounding bundle and lens frame; **reviewer** (no tools) checks supplied candidate material before main-agent presentation or commitment. The main agent achieves diversity by issuing parallel `tasks: []` invocations of `projector` with intentionally distinct framings — the subagent realization of the "design it twice" pattern from `ln-design` and the parallel fan-out anticipated by `ln-oracles`. Each `projector` invocation runs in its own isolated context so variants don't cross-contaminate; the main agent collects outputs and owns any product write. - This division mirrors the batch-proposal flow in D26-L. Worker-style write-capable subagents and nesting remain deferred beyond the initial `execute`/`orchestrator` standup. Cross-extension agent registration (Amos's `globalThis.__pi_subagents` bridge), raw `pi` subprocesses, and ambient `~/.pi` discovery are rejected for the POC because they conflict with profile sealing. Subagents remain an optional enhancement to candidate-proposal diversity and future delegated acquisition, not a load-bearing M0–M9 substrate. Depends on: D2-L, D26-L, D27-L, D30-L, D31-L, D39-L, D40-L, D41-L. Distinct from: D15-L Side task (non-blocking, status-via-custom-message), the deferred Side chat (user-invoked overlay; see Future Direction Register). Supersedes: subprocess/argv-shaped subagents and the `globalThis.__pi_subagents` bridge. Refined by: D90-L (shared foreground/background manifest + code-owned background discovery), D91-L (semi-permeable seal + assembled prompt + injected world), D92-L (sovereign tool grants + op_mode delegatable-set gate). + This division mirrors the batch-proposal flow in D26-L. Worker-style write-capable subagents and nesting remain deferred beyond the initial foreground-agent standup; D98-L moves the future write/execution surface under CODE/executor rather than a separate execute/orchestrator mode. Cross-extension agent registration (Amos's `globalThis.__pi_subagents` bridge), raw `pi` subprocesses, and ambient `~/.pi` discovery are rejected for the POC because they conflict with profile sealing. Subagents remain an optional enhancement to candidate-proposal diversity and future delegated acquisition, not a load-bearing M0–M9 substrate. Depends on: D2-L, D26-L, D27-L, D30-L, D31-L, D39-L, D40-L, D41-L. Distinct from: D15-L Side task (non-blocking, status-via-custom-message), the deferred Side chat (user-invoked overlay; see Future Direction Register). Supersedes: subprocess/argv-shaped subagents and the `globalThis.__pi_subagents` bridge. Refined by: D90-L (shared foreground/background manifest + code-owned background discovery), D91-L (semi-permeable seal + assembled prompt + injected world), D92-L (sovereign tool grants + op_mode delegatable-set gate). - **D90-L — Foreground and background agents share one manifest model; background discovery is code-owned (frontmatter is authoring DX, not a second agent model).** Agent definitions project into one `AgentManifest` (`id`, `kind`, `description`, `model`, `thinking`, body at the canonical `src/agents/prompts//SYSTEM.md` convention, a skills grant, a tools grant, and a `canDelegate` set naming the background agents it may spawn — D92-L/D93-L) discriminated by `kind: "foreground" | "background"` — the execution **lifecycle/host**, not a noun: a foreground agent is a live op_mode-derived Pi session; a background agent is a spawned-to-completion sealed child. The kinds keep **distinct authority sources**: a foreground agent's identity is derived from `op_mode` (D40-L) and its tool/skill legality is dynamic (op_mode policy + live gaps); a background agent's identity is caller-chosen (`{agent, task}`) and its skills/tools come from its authored manifest. DX-vs-strictness is reconciled by keeping **frontmatter as the authoring surface** for background agents while making **discovery code-owned**: the `readdir` scan over `agents/*.md` is retired for an explicit registry id list (mirroring how `state.ts` loads foreground bodies/skills through `loadSkills({ skillPaths, includeDefaults: false })`), so D39-L "no filesystem discovery" holds and frontmatter authoring survives. "subagent" stays the tool/UX noun (the main-agent tool call), not the kind name. Depends on: D39-L, D40-L, D44-L, D58-L. Refines: D44-L (the parallel frontmatter-discovered format collapses into the shared manifest; background agent bodies migrated from extension-local `.md` discovery onto the canonical `src/agents/prompts//SYSTEM.md` convention, so SPEC carries one agent-body layout — D44-L and the `src/.pi/extensions/subagents/README.md` topology notes reconcile to that path). Establishing frontier: `subagent-reconciliation`. Supersedes: `readdir` filesystem discovery of subagent definitions; the standalone subagent frontmatter format as a second, separate agent model. - **D91-L — Background subagents run a semi-permeable seal: explicitly-injected parent world handles plus an assembled (not verbatim) prompt; ambient leakage stays closed.** This deliberately reopens the D44-L/I29-L "no graph access, no Brunch RPC, no inherited context" clause. The seal stays closed against **ambient** leakage (in-memory auth/settings/session, no `~/.pi` discovery — D39-L intact) but opens to **explicitly injected** parent world handles the app root (`src/app/pi-subagents.ts`) supplies at spawn: the same `GraphReaders` the foreground uses scoped to the parent's `specId`, the spec/workspace context seed, and a bounded **session digest** (the parent branch flattened via `sessionManager.getBranch()`, the pattern in pi's `summarize.ts` example). The child's system prompt becomes **assembled, not verbatim**: body + a background control header (sealed child, delegated task, snapshot view) + world snapshot + a `` manifest built from the manifest's skills grant + router rules — reusing the foreground composer's extracted prompt-skill core (`renderBrunchSkills`, the skill-manifest loader) plus the selected workspace/spec seed renderer from `src/agents/contexts/seeds/turn-context.ts`, minus the foreground-only elicitation-recommendation block. World binding is **snapshot-at-spawn** (the child runs to completion against a fixed view) where the foreground is live-per-turn. Read access is asymmetric **by design**: the **session digest** is a snapshot block baked into the prompt (expensive, rarely re-pulled), while the **graph** is exposed as Brunch read tools (`read_graph` now; `read_session_context`, `read_elicitation_gaps`, … remain future grants) the child calls on demand (a recon agent iterates on graph). Return to the main agent is the ordinary tool-call result: findings re-enter main-agent context as the tool-result `content`; the structured `details` payload (`{ agent, status, text, … }`) is render-only via custom `renderCall`/`renderResult`, never model context. Write-capable children stay deferred (gated by D92-L); when they land, a `mutate_graph` against the parent's `specId` is a real side effect crossing back *outside* the tool result, and is named here so the write slice does not surprise. Depends on: D39-L, D43-L, D44-L, D58-L, D60-L, D82-L. Establishing frontier: `subagent-reconciliation`. Supersedes: the D44-L/I29-L clause that subagents have no graph access, no Brunch RPC/graph reads, no inherited world context, and a verbatim-body system prompt. -- **D92-L — Background tool grants are sovereign per-agent ceilings gated by a code-owned, op_mode-keyed delegatable-set allowlist — not parent-subset containment.** The earlier containment invariant (child tools ⊆ the parent's current legal set) is rejected: delegation is **capability-inverting on purpose** — a low-privilege orchestrator (the `execute`-mode foreground agent holding only data-layer tools) may spawn a narrow high-privilege child (e.g. a file-writing worker) so writes are quarantined in a child that does one job and exits. Each background agent's tool grant is therefore **sovereign** (authored in its manifest; may exceed the parent's). The surviving safety boundary is not a tool subset but **which background agents an op_mode may spawn**: a **code-owned, op_mode-keyed delegatable-set allowlist** living beside the op_mode policy, *not* authored in frontmatter (otherwise a manifest could self-advertise into a read-only mode). This lifts D40-L's registration ≠ advertisement from tools to agents: every background agent is registered; op_mode decides which are advertised as spawnable. A read-only `elicit` session is write-safe because elicit's delegatable set **excludes** write-capable agents, not because children are subset-bounded. Enabling write tools later = author the write-capable worker manifest + add it to the relevant op_mode's delegatable set (an advertisement change), not a re-derivation of parent authority. Depends on: D39-L, D40-L, D44-L. Establishing frontier: `subagent-reconciliation`. Refined by: D93-L (the delegatable-set allowlist becomes a per-agent `AgentManifest` `canDelegate` field; for a foreground mode it is that mode's code-owned delegatable set, and it generalizes to background→background nesting). Supersedes: the parent-subset tool-containment model for subagents; D44-L's "read-only/no-tool allowlist" as the only background tool posture; the framing that write-capable subagents wait on an execute mode raising both parent and child ceilings together. -- **D93-L — Operational mode and foreground agent collapse to one op-mode-keyed source of truth; the foreground roster is `elicit` / `execute` / `code`.** A foreground agent and its operational mode are 1:1 (D40-L: the foreground agent is derived from `op_mode`), so the prior **three-record fragmentation** — id enums in `src/session/schema/kinds.ts`, `OPERATIONAL_MODE_DEFINITIONS` + `AGENT_ROLE_DEFINITIONS` + `TOOL_POLICY_DEFINITIONS` in the former projections runtime-policy module, and `AGENT_PROMPT_DEFINITIONS` in `src/agents/runtime/state.ts` (which duplicated `model`/`thinking`/`allowedStrategies`/`allowedLenses` across two of them) — collapses to a **single op-mode-keyed record**. An operational mode IS `{ foreground AgentManifest (D90-L), tool policy, canDelegate set }`; background agents live in a sibling `AgentManifest` registry, and the per-agent **`canDelegate`** field (D92-L generalized from op_mode-keyed to a manifest field) links a foreground mode to the background agents it may spawn — **code-owned for foreground modes** so the write-safety boundary (I49-L) holds; it also generalizes to background→background nesting. The foreground roster is three modes: **`elicit` → `elicitor`** (current: read-only + graph-writes-via-tools; delegates to read-only gatherers); **`execute` → `orchestrator`** (live initial standup: direct shell/file writes blocked, no delegated workers yet, and one code-owned `orchestrator_stub` grant proves the non-dev runnable path; future worker delegation quarantines writes in spawned high-privilege workers per D92-L); and **`code` → `pi-coder`** (planned direct-coding assistant with direct `read`/`edit`/`write`/`bash`, **augmented to be Brunch-tooling-aware** — it also sees the graph read/capture tools). `execute` and `code` contrast deliberately: the orchestrator does not write directly, while the pi-coder will write directly (and does not delegate by default). `pi-coder` **instantiates** the augment case D58-L anticipated — its `SYSTEM.md` augments Pi's base coding-agent prompt rather than replacing it; whether other foreground roles should instead suppress/replace the base stays open per D58-L. (`code` is provisional naming for the pi-coder mode; rename is a one-token change. `code` remains in `PLANNED_OPERATIONAL_MODE_IDS` until built; `execute` is live.) Depends on: D23-L, D40-L, D58-L, D90-L, D92-L; I49-L. Establishing frontier: `subagent-reconciliation` (model + collapse land here; `execute` stood up here with the stub grant, while `code` remains declarative build-out — see PLAN). Supersedes: the three-record foreground-agent fragmentation as separate sources of truth; `defaultRole`/`allowedRoles` as a flexible many-roles-per-mode model (it is 1:1); the `executor` name for the execute-mode agent (now `orchestrator`). +- **D92-L — Background tool grants are sovereign per-agent ceilings gated by a code-owned, op_mode-keyed delegatable-set allowlist — not parent-subset containment.** The earlier containment invariant (child tools ⊆ the parent's current legal set) is rejected: delegation may be **capability-inverting on purpose** — a foreground agent may spawn a narrow higher-privilege child (e.g. a file-writing worker) so a risky write is quarantined in a child that does one job and exits. Each background agent's tool grant is therefore **sovereign** (authored in its manifest; may exceed the parent's). The surviving safety boundary is not a tool subset but **which background agents an op_mode may spawn**: a **code-owned, op_mode-keyed delegatable-set allowlist** living beside the op_mode policy, *not* authored in frontmatter (otherwise a manifest could self-advertise into a read-only mode). This lifts D40-L's registration ≠ advertisement from tools to agents: every background agent is registered; op_mode decides which are advertised as spawnable. A read-only `elicit` session is write-safe because elicit's delegatable set **excludes** write-capable agents, not because children are subset-bounded. Enabling write tools later = author the write-capable worker manifest + add it to the relevant op_mode's delegatable set (an advertisement change), not a re-derivation of parent authority. Depends on: D39-L, D40-L, D44-L. Establishing frontier: `subagent-reconciliation`. Refined by: D93-L (the delegatable-set allowlist becomes a per-agent `AgentManifest` `canDelegate` field; for a foreground mode it is that mode's code-owned delegatable set, and it generalizes to background→background nesting). Supersedes: the parent-subset tool-containment model for subagents; D44-L's "read-only/no-tool allowlist" as the only background tool posture; the framing that write-capable subagents wait on an execute mode raising both parent and child ceilings together. +- **D93-L — Operational mode and foreground agent collapse to one op-mode-keyed source of truth.** A foreground agent and its operational mode are 1:1 (D40-L: the foreground agent is derived from operational mode), so the prior **three-record fragmentation** — id enums in `src/session/schema/kinds.ts`, `OPERATIONAL_MODE_DEFINITIONS` + `AGENT_ROLE_DEFINITIONS` + `TOOL_POLICY_DEFINITIONS` in the former projections runtime-policy module, and `AGENT_PROMPT_DEFINITIONS` in `src/agents/runtime/state.ts` (which duplicated `model`/`thinking`/prompt-resource grants across two of them) — collapses to a **single op-mode-keyed record**. An operational mode IS `{ foreground AgentManifest (D90-L), tool policy, canDelegate set }`; background agents live in a sibling `AgentManifest` registry, and the per-agent **`canDelegate`** field (D92-L generalized from op_mode-keyed to a manifest field) links a foreground mode to the background agents it may spawn — **code-owned for foreground modes** so the write-safety boundary (I49-L) holds; it also generalizes to background→background nesting. D98-L refines the roster from the earlier `elicit` / `execute` / `code` split to the product target **`SPEC` → `elicitor`** and **`CODE` → `executor`**. The executor merges the prior `orchestrator` and `pi-coder` directions: it is Brunch-data-aware, can perform ordinary coding-assistant work under the CODE tool policy, and owns the plan-execution orchestration tool surface instead of forcing a separate execute coordinator. Depends on: D23-L, D40-L, D58-L, D90-L, D92-L, D98-L; I49-L. Establishing frontier: `subagent-reconciliation` established the shared manifest/collapse substrate; the SPEC/CODE roster correction is owned by the data-model-legibility / executor follow-on planning. Supersedes: the three-record foreground-agent fragmentation as separate sources of truth; `defaultRole`/`allowedRoles` as a flexible many-roles-per-mode model (it is 1:1); and the three-foreground-mode split where `execute`/`orchestrator` and `code`/`pi-coder` were separate product directions. - **D36-L — Spec/session selection is a reusable hierarchical decision model with transport-specific presentations.** Brunch owns a pure spec/session selection model that renders cwd-scoped inventory under the discovered project name without calling the user-created object a “workspace”. In TUI mode, the model may present a fast “continue last session” affordance when `.brunch/workspace.json` points to a valid spec+session; otherwise, or after “other spec/session”, the durable tree is: `create new spec → provide spec name → session created automatically`; `resume existing spec → choose existing spec → create a new session OR resume existing session → choose existing session`. The UI should not list every spec as a top-level action label; “resume existing spec” is the top-level intent, and the spec list is the next screen/scrollable selector. The model returns a product decision (`new spec`, `new session for spec`, `open session`, `continue selected session`, `cancel/quit`) without opening Pi sessions or mutating `.brunch/workspace.json` itself. The `WorkspaceSessionCoordinator` activates that decision and owns all persistence/session-binding effects. TUI startup and in-session paths share branded `pi-tui` components and colocated logo assets under `src/.pi/components/workspace-dialog`; adapters differ only in terminal lifecycle and Pi session-replacement mechanics (`ProcessTerminal`/`TUI.showOverlay` before Pi starts, `ctx.ui.custom(..., { overlay: true })` inside Pi), not in product semantics. RPC/headless transports must not invoke the TUI picker; they expose the same initial-selection requirement and activation decisions as JSON-RPC/product results so CLI JSON-RPC clients can select or create spec/session correctly. Depends on: D11-L, D21-L, D24-L, D33-L. Supersedes: implicit resume of `.brunch/workspace.json` on TUI launch, Pi `/resume`/`/new` as Brunch's product session chooser, one-off startup-only picker implementations, a flat action list that says “workspace” for specs, top-level `resume spec X` labels, and a separate intermediate action chooser for switching. - **D42-L — Session naming is Pi `session_info` presentation metadata, not spec identity.** Brunch-created sessions should be named at creation with neutral workspace-global defaults (`Untitled Session 1`, `Untitled Session 2`, …) so pickers/chrome never show an unnamed Brunch session and unchanged defaults do not collide across specs in the same cwd. These defaults are immediate lifecycle metadata, not LLM-generated summaries and not derived from the selected spec title. Brunch may later use Pi session lifecycle hooks to opportunistically replace a default with a short human-readable name that characterizes what happened in the transcript. The preferred generation trigger is `session_shutdown` for `quit`, `new`, and `resume` replacements because it sees the just-finished transcript and can name it before later picker lists need to distinguish sessions; `session_before_compact` or post-compaction (`session_compact`) may be used to refresh names after major summarization, and a manual/user rename command can force or override naming. The generation call should mirror the model-selection pattern in the local `summarize.ts` extension example: choose a cheap/fast authorized model, extract user/assistant text plus salient tool calls from the current branch, ask for a concise title, and append a Pi `session_info` entry through `SessionManager.appendSessionInfo`. Naming must be best-effort and non-blocking with a tight budget: failures, missing auth, empty transcripts, or shutdown aborts preserve the existing default/user label rather than blocking session replacement or exit. Session display names label sessions in pickers and chrome, but do not affect spec ids, session bindings, graph truth, or replay semantics. Depends on: D6-L, D17-L, D21-L, D35-L. Supersedes: using spec title or session UUID alone as the only durable display label once transcripts have meaningful content, leaving Brunch-created sessions unnamed, spec-local default numbering, or treating generated session names as canonical spec identity. -- **D58-L — Brunch prompt composition is a thin runtime header plus a gated prompt-resource manifest, not eager selection of every objective pack.** The architectural commitment is: composition stays a projection layer, not a behavioral state machine; detailed strategy/lens/method guidance lives in read-on-demand prompt resources rather than eager prompt-pack concatenation; runtime-state-gated availability is Brunch's sealed manifest, not ambient Pi discovery; pinned axes remain visible when role/mode-legal while readiness constrains AUTO menus and gated methods/tools; and the pushed-context slice stays compact, with deeper access governed by D60-L. Current prompt-resource topology, manifest emission, file-owned skill metadata, seed context composition, and ownership split across `agents/prompts/`, `agents/skills/`, `agents/runtime/`, `agents/contexts/`, and `.pi/extensions/agent-runtime/` live in [`src/agents/README.md`](src/agents/README.md), [`src/agents/prompts/README.md`](src/agents/prompts/README.md), [`src/agents/skills/README.md`](src/agents/skills/README.md), [`src/agents/runtime/README.md`](src/agents/runtime/README.md), [`src/agents/contexts/README.md`](src/agents/contexts/README.md), [`src/.pi/README.md`](src/.pi/README.md), [`src/.pi/extensions/README.md`](src/.pi/extensions/README.md), [`src/agents/runtime/compose.ts`](src/agents/runtime/compose.ts), [`src/agents/runtime/state.ts`](src/agents/runtime/state.ts), and [`src/agents/contexts/seeds/turn-context.ts`](src/agents/contexts/seeds/turn-context.ts). **Base-prompt relationship (validated 2026-06-18, slice 1):** the `before_agent_start` handler **appends** Brunch's composed block (now led by the agent `SYSTEM.md` body, then runtime header + manifests) to Pi's base system prompt (`${basePrompt}\n\n${composed}`), so a foreground agent currently *augments* Pi's base coding-agent prompt rather than replacing it. Whether a foreground role's `SYSTEM.md` body should suppress or replace that base is **open** and tied to the future `pi-coder` op-mode (which deliberately augments Pi's coding agent); the `elicitor` augmenting a coding base is a known follow-on question, not a settled choice. Refined by: D93-L (the `code`→`pi-coder` foreground mode instantiates the augment case; the replace option for other roles stays open). Composition is projection, not a behavioral state machine. Depends on: D23-L, D25-L, D39-L, D40-L, D52-L, D59-L, D60-L. Refined by: D85-L (implemented 2026-06-18/19: the manifest drops `` — two axes `strategy` + `lens` — and the `goal` body inlines into the `elicitor` role prompt) and by the 2026-06-22 prompt-skill-topology slice (all prompt resources adopt Agent Skills `SKILL.md` topology; `description` becomes file-owned frontmatter; the emitted wrapper becomes `` with per-skill ``). Supersedes: the flat "base + mode + role + strategy + lens + grade + …" layering; the fixed all-packs concatenation in `compose-brunch-prompt.ts`; "role preset / runtime bundle" as the composition unit; direct Layer-2 eager prompt-pack injection as the default mechanism; treating top-level `src/agents/` as Pi-only rather than Brunch LLM-context ingress; and `capability` as a parallel name for `method`. +- **D58-L — Brunch prompt composition is a thin runtime header plus load-on-demand prompt resources, not eager selection of every objective pack.** The architectural commitment is: composition stays a projection layer, not a behavioral state machine; detailed guidance lives in read-on-demand prompt resources and agent-readable references rather than eager prompt-pack concatenation; runtime availability is Brunch's sealed resource manifest, not ambient Pi discovery; D98-L suspends prompt-resource axes as runtime state, so composition may advertise resources/pointers without presenting strategy/lens/method as selected posture; and the pushed-context slice stays compact, with deeper access governed by D60-L. Current prompt-resource topology, manifest emission, file-owned skill metadata, seed context composition, and ownership split across `agents/prompts/`, `agents/skills/`, `agents/runtime/`, `agents/contexts/`, and `.pi/extensions/agent-runtime/` live in [`src/agents/README.md`](src/agents/README.md), [`src/agents/prompts/README.md`](src/agents/prompts/README.md), [`src/agents/skills/README.md`](src/agents/skills/README.md), [`src/agents/runtime/README.md`](src/agents/runtime/README.md), [`src/agents/contexts/README.md`](src/agents/contexts/README.md), [`src/.pi/README.md`](src/.pi/README.md), [`src/.pi/extensions/README.md`](src/.pi/extensions/README.md), [`src/agents/runtime/compose.ts`](src/agents/runtime/compose.ts), [`src/agents/runtime/state.ts`](src/agents/runtime/state.ts), and [`src/agents/contexts/seeds/turn-context.ts`](src/agents/contexts/seeds/turn-context.ts). **Base-prompt relationship (validated 2026-06-18, slice 1):** the `before_agent_start` handler **appends** Brunch's composed block (now led by the agent `SYSTEM.md` body, then runtime header + manifests) to Pi's base system prompt (`${basePrompt}\n\n${composed}`), so a foreground agent currently *augments* Pi's base coding-agent prompt rather than replacing it. Whether a foreground role's `SYSTEM.md` body should suppress or replace that base is **open** and tied to the future `pi-coder` op-mode (which deliberately augments Pi's coding agent); the `elicitor` augmenting a coding base is a known follow-on question, not a settled choice. Refined by: D93-L (the `code`→`pi-coder` foreground mode instantiates the augment case; the replace option for other roles stays open). Composition is projection, not a behavioral state machine. Depends on: D23-L, D25-L, D39-L, D40-L, D52-L, D59-L, D60-L. Refined by: D85-L (implemented 2026-06-18/19: the manifest drops `` — two axes `strategy` + `lens` — and the `goal` body inlines into the `elicitor` role prompt) and by the 2026-06-22 prompt-skill-topology slice (all prompt resources adopt Agent Skills `SKILL.md` topology; `description` becomes file-owned frontmatter; the emitted wrapper becomes `` with per-skill ``). Supersedes: the flat "base + mode + role + strategy + lens + grade + …" layering; the fixed all-packs concatenation in `compose-brunch-prompt.ts`; "role preset / runtime bundle" as the composition unit; direct Layer-2 eager prompt-pack injection as the default mechanism; treating top-level `src/agents/` as Pi-only rather than Brunch LLM-context ingress; and `capability` as a parallel name for `method`. #### Continuity & origination (turn-boundary choreography) @@ -294,7 +294,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c - **D71-L — One `BRUNCH_DEV` switch gates all dev affordances; the main CLI accepts `--cwd`; introspection is present-but-dead in prod.** The over-specific `BRUNCH_DEV_RPC` env var is generalized to a single `BRUNCH_DEV` switch that, when set, enables dev affordances together: dev RPC methods (`dev.*`), registration of the read-only introspection extension (D69-L), and routing of dev-loop artifacts to `.fixtures/scratch/` (D70-L). `runBrunchCli` parses a `--cwd ` flag (defaulting to `process.cwd()`) so a dev session can target a `.fixtures/workbenches/` workspace without `cd`. Two independent prod-safety gates hold: (1) `src/dev/**` is build-excluded by `tsconfig.build.json`, so launchers/harness/alias never ship; (2) the introspection extension, though compiled into `dist` under `src/.pi/`, only *registers* when `createBrunchPiExtensions(..., { introspection: { enabled } })` opts in — and the TUI call site sets `enabled` from `BRUNCH_DEV` only, so absent the switch it is present-but-dead, never wired, honoring D39-L explicit-opt-in sealing (no ambient discovery). Brunch-launched TUI sessions keep Pi startup update suppression on in both product and `BRUNCH_DEV` runs by scoping `PI_OFFLINE=1` through `InteractiveMode.run()` unless the user already set a value; prior `PI_OFFLINE` / `PI_SKIP_VERSION_CHECK` state is restored in `finally`, never as a leaked global `process.env` mutation. Depends on: D39-L, D67-L, D68-L, D69-L, D70-L. Supersedes: the `BRUNCH_DEV_RPC`-only dev gate; relying on the operating cwd to choose the dev workspace; the assumption that the introspection extension needs build-exclusion (runtime opt-in suffices); lifting Pi offline mode in `BRUNCH_DEV` TUI sessions merely to enable live-provider behavior. - **D79-L — Dev DB seeding is explicit, selected, and target-workspace-scoped; `npm run dev` never implies a seed.** A Brunch workspace DB is local runtime state under that launch cwd's `.brunch/`; running `npm run dev` against the repo root or a workbench may create/open that workspace, but it must not silently load reusable seed fixtures. Reusable graph seeds under `.fixtures/seeds//.json` are loaded only by an explicit seed command that names the target workspace and the seed set/slug (or an explicitly requested all-seeds batch); the loader remains a graph-domain utility over `seedFixture`/`CommandExecutor`, so seeded specs get normal `create_spec`/`mutate_graph` change-log entries, spec-local LSNs, elicitation-gap seeding, and structural validation. Workbenches under `.fixtures/workbenches//` are launchable cwd containers, not seed truth: their `.brunch/` may be reset or re-seeded locally, but tracked files must document which seed(s) a human or script should apply. Captured or newly-authored seed JSON is parked until it has at least one named consumer disposition (`test`, `preview`, `manual workbench`, `probe input`, or `parked`); existence under `seeds/` alone does not make it part of the default dev database. Depends on: D16-L, D20-L, D52-L, D70-L, D71-L. Supersedes: the catch-all `npm run seed` mental model that loads every seed into the current shell cwd; treating the repo-root `.brunch/` as canonical dev fixture state; auto-seeding because a dev host starts. - **D59-L — `goal` is a readiness-derived, AUTO-able objective axis, distinct from strategy.** A *goal* is what the session agent currently pursues; a *strategy* is the reusable interaction shape used to pursue it — a goal is pursued *via* a strategy *through* a lens (three orthogonal axes). The goal set is derived from readiness-band coverage (D64-L) rather than a stored grade: `grounding-advance` (fill grounding gaps and raise grounding coverage), `elicit-expand` (expand the elicited specification graph while ambiguity remains productive), `commit-converge` (reduce / lock down reviewable commitments), plus an always-on `capture-posture` (capture or confirm dev `posture`, D45-L). `goal` defaults to the readiness-derived objective (e.g. while grounding coverage is thin, `grounding-advance`), may be pinned, or left `AUTO`; in either case D58-L manifests advertise the legal resource(s) rather than injecting the whole goal body. For now `goal` is **internal/readiness-derived and not part of the user posture-change surface** (it is too contingent to expose as a user-mutable axis); the pin affordance is reserved for system/internal logic, and unlike `strategy`/`lens` the user does not switch it (D40-L, Q4). `elicit-expand` and `commit-converge` intentionally form the diverge/converge pair for the elicitation diamond; `elicit-I` / `elicit-II` are retired because they were phase-like labels, not objectives. "Advance grounding" is a goal, not a strategy — though the `grounding-advance` goal may carry a dedicated default interaction pattern. Depends on: D45-L, D57-L, D58-L, D64-L. Refined by: D85-L (`goal` is dropped as a manifest/runtime axis; the four postures inline into the `elicitor` role prompt, agent-selected by band — goal was internal/readiness-derived anyway). Supersedes: conflating the elicit-lifecycle objective with strategy selection, and deriving the goal set from a stored readiness grade. -- **D66-L — `freestyle` is a structure-optional elicitation strategy; it and generalized free-text capture are one slice.** The architectural commitment is that `freestyle` is an interaction-style strategy, not a new `op_mode` or authority posture: ordinary user-driven turns become allowed without banning structured exchanges, and AUTO must not select `freestyle` — it remains an explicit user/system pin so offer-first does not disappear silently. Current materialized state lives in [`src/agents/skills/strategies/README.md`](src/agents/skills/strategies/README.md), [`src/agents/skills/strategies/freestyle/SKILL.md`](src/agents/skills/strategies/freestyle/SKILL.md), [`src/agents/skills/methods/capture/SKILL.md`](src/agents/skills/methods/capture/SKILL.md), [`src/agents/runtime/policy.ts`](src/agents/runtime/policy.ts), [`src/.pi/extensions/commands/index.ts`](src/.pi/extensions/commands/index.ts), and [`src/session/README.md`](src/session/README.md). Depends on: D18-L, D25-L, D26-L, D40-L, D45-L, D49-L, D50-L, D59-L, D63-L, D65-L. Refines: R16. Refined by: D80-L, D81-L (2026-06-12 FE-861 grill: the capture half — submit-time capture wiring and the "directly-stated only" commitment line — is superseded by the banded capture sweep and the commitment gradient; capture runs on every elicitor turn over the un-swept tail, resolving the every-turn-vs-on-demand open question). Supersedes: treating offer-first (R16) as a universal per-turn session invariant; treating freestyle as a new operational mode or authority posture. +- **D66-L — Structure-optional user turns feed SPEC-mode capture; `freestyle` is no longer runtime strategy state.** The durable commitment is that ordinary user-driven turns, pasted material, and structure-optional conversation are allowed without banning structured exchanges. D98-L supersedes the `freestyle` runtime-strategy framing: this behavior is part of SPEC-mode elicitation/capture conduct, not a separate op_mode, authority posture, AUTO strategy, or explicit user/system runtime pin. Current materialized state lives in [`src/agents/skills/strategies/README.md`](src/agents/skills/strategies/README.md), [`src/agents/skills/strategies/freestyle/SKILL.md`](src/agents/skills/strategies/freestyle/SKILL.md), [`src/agents/skills/methods/capture/SKILL.md`](src/agents/skills/methods/capture/SKILL.md), [`src/agents/runtime/policy.ts`](src/agents/runtime/policy.ts), [`src/.pi/extensions/commands/index.ts`](src/.pi/extensions/commands/index.ts), and [`src/session/README.md`](src/session/README.md). Depends on: D18-L, D25-L, D26-L, D40-L, D45-L, D49-L, D50-L, D59-L, D63-L, D65-L. Refines: R16. Refined by: D80-L, D81-L (2026-06-12 FE-861 grill: the capture half — submit-time capture wiring and the "directly-stated only" commitment line — is superseded by the banded capture sweep and the commitment gradient; capture runs on every elicitor turn over the un-swept tail, resolving the every-turn-vs-on-demand open question). Supersedes: treating offer-first (R16) as a universal per-turn session invariant; treating freestyle as a new operational mode or authority posture. - **D80-L — Generalized capture is the elicitor's banded capture sweep: in-turn, synchronous, over the un-swept transcript tail.** Capture is conduct of the foreground elicitor, not product wiring: there is no observer/auditor queue on the primary path (D18-L, reaffirmed — the v1 observer failed on structure-dependence and context starvation), no product-side LLM extraction pass on the submit paths, no gateway/translation/judgment layer between the agent and graph truth, and no capture subagent in the current block. The **banded capture sweep** is one band-ordered pass that walks intent-kind groups (the same typology the `elicitation_gaps` register references via `refersTo: NodeKind`, D65-L/D75-L), committing through the real role-named `mutateGraph` grammar (D53-L/A14-L) and moving gap dispositions through `update_elicitation_gaps`. Its input window is the **un-swept transcript tail** — all conversational and digest content since the last sweep, tracked by a **sweep watermark** (prior art: the I45-L assistant-visible watermark and the own-mutation stamp) — so capture is robust to RPC-submitted messages, interruptions, and multi-message bursts, and probes get a crisp invariant: after any elicitor turn, nothing conversational remains behind the watermark. Default is a single pass; bulk material (pastes, document reads, exploration digests) may engage an **escalation valve** — chunked/iterated sweeping within the same turn — without changing window or watermark semantics. Choreography is **capture-then-ask**: the sweep commits facts and moves gaps *before* the elicitor composes its next question, so the question provably benefits from what was just captured. Consequence: the deterministic labeled-prefix capture core (`graph/capture/structured-response.ts`), its `session.submitMessage`/`session.submitExchangeResponse` wiring, and the `capture-response-to-graph` proof are retired fossils — capture becomes turn-coupled (same agent for RPC transport clients, different moment; no coverage loss). Depends on: A14-L, A22-L, D18-L, D49-L, D53-L, D63-L, D65-L, D66-L; I45-L. Supersedes: submit-time product-side capture (the D66-L "exactly as the structured-response capture tracer does" wiring), the labeled-prefix extraction core, and the capture-quality spike's product-side extraction-pass shape. - **D81-L — Capture commitment gradient: confidence, not directness; low-confidence noticings spawn elicitation gaps.** What the sweep commits is governed by confidence in grounding, not by whether the user uttered the exact words: directly-stated facts commit with `basis: explicit`; confidently-materialized items — including implied edges and structure soundly inferred from stated content — commit with `basis: implicit`, which D63-L already licenses (agent-materialized-from-user-input); low-confidence **noticings** are never committed — the sweep's prompt directs the elicitor to spawn an `elicitation_gap` instead (`basis: implicit`, rationale citing the noticing), so the false-commit guard's positive output *is* the capture-reflection behavior: one prompted discipline discharges both the guard and the gap-writeback obligation, the agenda durably carries what was noticed, and the anti-shadowing line (D65-L) holds because the gap carries question/rationale, never domain content as truth. There is **no structural gate**: the guard is commitment rules in the sweep prompting plus the false-commit scenario matrix re-aimed at the low-confidence line and run at probe tier (some spike implication rows become legitimate implicit commits under the gradient; expected gap-spawns become assertable probe outcomes); CI guards structural legality only at the `CommandExecutor` boundary. Refines: D18-L (low-confidence material now spawns gaps rather than only "folding into later questions"; preface, D47-L, remains the orientation carrier). Depends on: A22-L, D18-L, D47-L, D63-L, D65-L. Supersedes: "implications never become graph truth" as a *directness* rule, and the spike matrix's `shouldCommit` expectations as written. - **D82-L — Ground-material acquisition is a skill-structured layer in front of the sweep; bulk modes interpose a digest; a seeded situating gap routes modes.** Questions and answers are not the only way the graph gains ground material: the elicitor must also accept arbitrary pasted content, read user-referenced workspace documents, and explore-and-characterize a brownfield codebase. These are **acquisition modes** — elicit-by-question, ingest-paste, read-referenced-documents, explore-and-characterize — structured as Brunch prompt-resource skills (D58-L manifest world), each a distinct competence the elicitor reaches for; `read-referenced-documents` and `explore-and-characterize` may use Brunch-owned static `web_fetch`/`web_search` tools registered in the sealed Pi profile (D39-L/D40-L). Acquisition varies, capture stays uniform (`acquire → digest → sweep`): everything acquired lands in the transcript behind the sweep watermark. Bulk modes (exploration, research, large document reads) interpose a **digest** — an assistant-authored characterization of what was read/found (prior art: the v1 preface-of-exchange-tuple, which proved capture should run over the summary, not the raw bulk; D47-L) — and the sweep captures from digests plus conversational content while raw tool results pass behind the watermark as background. A **situating gap** is seeded at spec creation (orientation anchors: new-from-scratch / brownfield codebase / continuation of a prior thread — the grounding-advance anchors promoted from skill prose to agenda), so the opening elicitation itself routes the session into the right acquisition mode; the gap's discharge is what licenses, e.g., explore-and-characterize. Near-future direction (not current block): exploration/research acquisition delegated to **subagents** with the digest as the handback artifact — clean main-elicitor context without observer starvation, because the subagent owns its exploration context and returns only the digest. Depends on: D47-L, D57-L, D58-L, D65-L, D80-L. Supersedes: treating conversational answers as the only capture source. @@ -311,7 +311,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c - **Dependencies** — three small leaf libraries (md-pen, `@toon-format/toon`, stringify-tree), each *retiring owned format-generation code* (the md-pen/TOON wrapper seams are already stubbed; the tree library replaces a hand-built formatter), so net owned surface decreases — the trade that justifies them under a dependencies-resist posture. - **Rollout** — incremental: ``, ``, graph, session runtime-frame, and structured-exchange result renders now live under `src/agents/contexts/`; transcript debug/report rendering lives in `src/session/transcript-markdown.ts` as a human/product debug artifact. - **Closed audit** — per-session `turnCount` is derived once while inspecting canonical session files and counts only current Pi v3 JSONL message entries (`type: "message"` with `message.role: "user" | "assistant"`); tool/custom entries are excluded, and downstream workspace/specification overview renders reuse that inspected count rather than reparsing the file. -- **D85-L — Brunch prompt-resource axis model: two AUTO axes (`strategy`, `lens`); `goal` inlined into the agent role; graph-write mechanism is method-routed, not a strategy.** A 2026-06-18 grill consolidation of the `agents/skills/` topology and the D58-L manifest axes, implemented across FE-893, FE-861, and FE-898. Four locked moves: +- **D85-L — Suspended prompt-resource axis model: strategy/lens/method are no longer runtime state.** A 2026-06-18 grill consolidation of the `agents/skills/` topology and the D58-L manifest axes, implemented across FE-893, FE-861, and FE-898, produced useful prompt-resource content and path topology. D98-L suspends the runtime-axis claim: strategy/lens/method may remain as prompt-resource organization or internal agent reasoning vocabulary, but Brunch should not expose or persist them as changeable runtime state unless later evidence earns that surface. Historical moves from that pass, retained only where D98-L does not supersede them: 1. **Two AUTO objective axes, not three.** The runtime manifest advertises only `strategy` and `lens`; **`goal` is dropped as a manifest/runtime axis**. The four goal postures (`grounding-advance`, `elicit-expand`, `commit-converge`, always-on `capture-posture`) **inline into the `elicitor` agent role prompt** (`src/agents/prompts/elicitor/SYSTEM.md`), selected inline by the agent from the pushed readiness-band/posture context. Rationale: `goal` was already internal/readiness-derived and not user-mutable (D59-L), so advertising it as an AUTO-selectable axis was indirection over what is agent-directed-by-band anyway. Consequences for the build: `compose.ts` drops the `` family, `manifestsForState` drops `goals`, `runtime-state.ts` / `agents/runtime/policy.ts` drop the `goal` axis slot, and the runtime header drops the goal line. Capability-readiness (D74-L) is unaffected — it keys on gaps, not goal. 2. **Graph-write mechanism is method-routed, not a strategy-axis member.** `propose-graph` (direct-commit) and `project-graph` (review-set) describe the **graph-write capability ids** (the D26-L commitment mechanisms), not interaction shape; their strategy names are retired rather than rehomed. The existing methods absorb the mechanics: `commit-graph` carries direct-commit mechanics, and `generate-proposal` carries review-set mechanics. The offer→accept / derive→review choreography lives in the inlined `commit-converge` posture, not in method bodies. The graph-write readiness gate was originally placed on those method ids via capability-readiness (**removed by D86-L**: the graph-write methods are floor — readiness is advisory for them, never a tool gate), while the `strategy` axis keeps only genuine interaction shapes: `step-wise-decision-tree`, `step-wise-disambiguate`, and `freestyle` (AUTO-excluded, D66-L). 3. **Gap-reflection conduct belongs to the capture skill, not `review-for-gaps`.** D81-L spawn-on-noticing + close-on-answered is **always-on capture-sweep conduct** (every elicitor turn), so it lives with the D80-L capture skill, not an optionally-selected method. `review-for-gaps` is demoted to the **deliberate-audit sense only** (missing support, contradictions, verification debt). Read/interpret-gap semantics stay on the `read_elicitation_gaps` tool description (tool-local), not duplicated into a skill; the D81-L commitment gradient lives once, in the capture skill, with gap-spawn as its third outlet. @@ -320,9 +320,10 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c Prompt-shape closure (revised 2026-06-22): (a) **`SKILL.md` directory topology is adopted for every strategy/lens/method** because the Agent Skills standard is now the target prompt-resource format; `references/` remains deferred until a concrete skill needs progressive disclosure, and D39-L's code-owned path list remains the availability surface. (b) **`src/agents/prompts//SYSTEM.md`** is adopted for live and named future bodies; no flat agent-body shape remains open. (c) **`[sub]` sub-agent notation** is deferred until the first real delegated sub-agent lands; no empty sub-agent stubs are introduced. (d) **`_generated/` typed-vocab references** are **materialized** (first instance: the kind→band table at `src/graph/schema/_generated/ontology.md`, generated by `npm run generate:ontology` from the typed `graph/schema` sources and drift-checked by `npm run check:data-model`, wired into `npm run check`); they are read-only projections locked separately from authored prompt-resource bodies, and prompt resources cite them rather than restating vocabulary (the data-model-legibility frontier owns the expansion to further tables + the authored judgment layer). Current state: [`src/agents/skills/README.md`](src/agents/skills/README.md). Resolved 2026-06-18: the capture home is `methods/capture`, absorbing the former `infer-and-capture` method name; the full D80/D81/D82 conduct body remains FE-861. Depends on: D23-L, D25-L, D26-L, D39-L, D40-L, D58-L, D59-L, D65-L, D73-L, D80-L, D81-L. Refines: D25-L, D26-L, D40-L, D58-L, D59-L. Supersedes: `goal` as an AUTO-able manifest/runtime axis (the "objective axes `strategy`, `lens`, and `goal`" triple in D40-L/D58-L/D59-L → two axes, goal inlined); `propose-graph` / `project-graph` as `strategy`-axis members (D25-L/D26-L list them as strategies); treating gap spawn/close as a `review-for-gaps` method responsibility; `infer-and-capture` as a separate method; treating the `capture_*` / candidate / `tool_meta` prompt-resource references as removable fossils; and the 2026-06-19 deferral of Agent Skills `SKILL.md` topology. Depends on: D19-L, D52-L, D60-L, D62-L, D65-L, D75-L. Refines: D60-L (RENDER stage). Supersedes: the ad-hoc `[bracket]`-header + bullet-list render style as the house convention; hand-rolled markdown and tree string generation in the old renderer layer; carrying sessions in the `` cwd render. -- **D95-L — Elicitor capability spine: `capture` / `generate` / `project` are the three elicitor capabilities, named over the existing skill axes — not a fourth axis or a re-axis.** The elicitor's work decomposes into three capabilities by what each does to the graph: **capture** commits ground material already present in the transcript tail into graph truth (the D80-L banded sweep + D81-L commitment gradient + D82-L acquisition layer, already specced); **generate** proposes new typed graph expressions on a requested plane from grounding plus a lens frame, fanning candidates out and committing the chosen one through review (D96-L); **project** derives nodes on one plane from a subset/plane of the existing graph with connecting cross-plane edges (e.g. requirements→design, design→oracles, A33-L). This is a capability vocabulary layered over D85-L's `strategy` / `lens` / `method` axes (held frozen, A35-L), not a replacement topology: capture is always-on conduct of every elicitor turn (D85-L move 3), while generate and project are method-routed capabilities requested just-in-time and gated only advisorily by capability-readiness (D74-L/D86-L). Background acquisition subagents (D82-L near-future, A34-L) are the `acquire` arm feeding capture, not a fourth capability. Depends on: D74-L, D80-L, D81-L, D82-L, D85-L, D86-L. Supersedes: the proposed `grounding` / `elicitation` / `projection` lifecycle directories as a replacement skill topology. +- **D95-L — Elicitor capability spine: `capture` / `generate` / `project` are the three SPEC-mode capabilities.** The elicitor's work decomposes into three capabilities by what each does to the graph: **capture** commits ground material already present in the transcript tail into graph truth (the D80-L banded sweep + D81-L commitment gradient + D82-L acquisition layer, already specced); **generate** proposes new typed graph expressions on a requested plane from grounding plus a conceptual frame, fanning candidates out and committing the chosen one through review (D96-L); **project** derives nodes on one plane from a subset/plane of the existing graph with connecting cross-plane edges (e.g. requirements→design, design→oracles, A33-L). D98-L makes this a SPEC-mode capability vocabulary, not a runtime-axis topology: capture/generate/project are the elicitor's jobs, while strategy/lens/method files are optional prompt-resource organization if they improve behavior. Capture remains always-on conduct of every elicitor turn; generate and project are requested just-in-time and readiness remains advisory rather than a graph-write tool gate (D74-L/D86-L). Background acquisition subagents (D82-L near-future, A34-L) are the `acquire` arm feeding capture, not a fourth capability. Depends on: D74-L, D80-L, D81-L, D82-L, D85-L, D86-L, D98-L. Supersedes: the proposed `grounding` / `elicitation` / `projection` lifecycle directories as a replacement skill topology, and treating strategy/lens/method as the load-bearing runtime capability model. - **D96-L — `generate` is one deep plane-parameterized skill; fan-in is a three-value mode carried by `present_candidates` + the review-set path, not three skills.** Generative proposal across the intent, design, and oracle planes is **one** `generate` skill taking the target plane (and lens frame) as a parameter, not per-plane `propose-scenarios` / `propose-design-shapes` / `propose-oracle-ensembles` skills (the earlier per-plane sketch in [`docs/design/ELICITATION_LENSES.md`](docs/design/ELICITATION_LENSES.md)). The fan-out/fan-in shape is shared: the skill fans candidate expressions out, then **fan-in is a three-value mode** — `pick` (choose one), `synthesize` (merge candidates into one), `compose` (accept several) — expressed as plane-keyed method conduct over `present_candidates` plus the review-set path rather than branched per plane. Plane-specific judgment (the "design it twice" pattern for design, oracle-family selection for oracles, the kernel/lens heuristics in [`docs/design/BEHAVIORAL_KERNELS.md`](docs/design/BEHAVIORAL_KERNELS.md) / [`docs/design/ELICITATION_LENSES.md`](docs/design/ELICITATION_LENSES.md) for intent) lives in plane-keyed skill content read on demand (D58-L manifest world), not in separate skills. This **entailed un-stubbing the `present_candidates` topology** — the tool [`src/.pi/extensions/exchanges/present-candidates.ts`](src/.pi/extensions/exchanges/present-candidates.ts), the projection [`src/projections/exchanges/present-candidates.ts`](src/projections/exchanges/present-candidates.ts), and the renderer [`src/agents/contexts/exchanges/present-candidates.ts`](src/agents/contexts/exchanges/present-candidates.ts) — which D85-L move 4 confirmed as a live anticipated stub, not a fossil: candidate presentation gets its product owner here. The materialized tool remains **pick-only at the UI boundary**: intent uses the pick as recognition/provenance, while design-plane synthesize is performed by the method after the pick and then reviewed/committed through `present_review_set → request_response → acceptReviewSet`, so no `fan_in_mode` field is needed unless a later plane proves the UI itself must carry that mode. Commitment still flows through the review-set path (D27-L); `present_candidates` recognizes fan-out presentation and never commits graph truth itself (I51-L). Depends on: D26-L, D27-L, D30-L, D31-L, D58-L, D74-L, D85-L, D95-L; A31-L, A32-L. Supersedes: per-plane generative skills as the topology; treating `present_candidates` as a permanent stub without a product owner; prebuilding a fan-in schema field before a plane proves it necessary. - **D97-L — Skill ontology-heuristic provenance: three sources — consumed context renders, generated typed-vocab, hand-authored judgment — kept distinct.** Skill bodies that teach the agent how to think about the graph model draw ontology/heuristic content from three provenance classes that must not blur: (1) **dynamic instance context** rendered into the prompt by the context-render house style (D83-L, FE-870) — graph overviews, gap agendas, neighborhoods — consumed, never restated in skill prose; (2) **generated typed-vocab references** (`_generated/`, D85-L prompt-shape closure (d)) projected from the closed `kinds.ts` enums (D73-L) and drift-checked, for any skill that must enumerate node kinds / edge categories / bands / planes; and (3) **hand-authored judgment** — the irreducible "how to reason" content (kernels, lenses, oracle-family selection) that is neither instance data nor mechanical vocabulary. The rule: a skill cites the context renderer or the generated reference rather than copying its content, so ontology drift (D73-L renames, D94-L band changes) propagates through one canonical source. Depends on: D58-L, D73-L, D83-L, D85-L, D94-L; FE-870. Supersedes: hand-restating node-kind / band / edge-category / plane vocabulary inside skill bodies. +- **D98-L — Operational mode only: suspend strategy/lens/method runtime axes; target product modes are SPEC and CODE.** The architectural correction is that the `strategy` / `lens` / `method` model is not yet proven as the right product/runtime abstraction. It may still organize prompt-resource files and concise agent-readable references, but it must not be a user-facing TUI picker, transcript-backed posture field, AUTO axis, tool gate, or the source of foreground-agent identity until live elicitor behavior proves that shape earns its cost. Runtime state narrows to one mutable axis: operational mode. The target product mode labels are **`SPEC`** and **`CODE`**. `SPEC` runs the `elicitor`, whose job is to get a user from zero to a complete spec through three capabilities: (1) capture arbitrary unstructured material into graph truth with correct confidence/basis/gap handling; (2) generate candidate graph concepts on the intent, design, and oracle planes and commit coherent graph expressions through the appropriate review/commit path; and (3) project selected graph subsets or planes into downstream planes with connecting nodes and edges. `CODE` runs the **`executor`**, a Brunch-aware coding assistant that merges the prior `orchestrator` and `pi-coder` directions: it can read/use Brunch graph and session context, can act as a normal coding assistant under the CODE tool policy, and owns the plan-execution orchestration tool surface (the previously stubbed orchestrator tool) instead of requiring a separate execute-mode coordinator. The TUI should expose only `mode: SPEC | CODE`; prompt-resource/reference loading remains agent-internal and load-on-demand unless a narrow runtime moment proves eager injection necessary. Depends on: D23-L, D40-L, D58-L, D85-L, D90-L, D93-L, D95-L, D97-L. Establishing frontier: `data-model-legibility` for the SPEC-mode guidance/reference substrate, followed by executor/tool-port work for CODE. Supersedes: runtime persistence or UI exposure of strategy/lens/method axes; the `elicit` / `execute` / planned `code` three-mode foreground roster; the separation between `orchestrator` as execute coordinator and `pi-coder` as direct-coding mode; and treating method routing as the product-level capability model. ### Critical Invariants @@ -352,7 +353,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c | I22-L | Brunch TUI startup must not render prior session transcript entries or enter an agent loop until the user has explicitly activated a spec/session decision; creating a new spec implicitly creates its first session, creating a new session for an existing spec lands in a binding-only session, resuming a prior transcript is opt-in, and RPC/headless startup exposes structured initial-selection state rather than invoking TUI picker code. | covered (FE-744 coordinator tests; hierarchical spec/session picker model + component tests; `workspace.selectionState` / `workspace.activate` JSON-RPC contract tests with source assertion that RPC does not import TUI picker code; `src/probes/scripts/verify-startup-no-resume.sh` pty/ANSI-stripped TUI probe oracle proving stale transcript text is absent before explicit activation) | D11-L, D21-L, D22-L, D36-L | | I23-L | Every structured elicitation interaction that owns the response surface persists durable semantic display only through Pi `toolResult` rows rendered by `renderResult`; `renderCall` and live `ctx.ui.*` surfaces are transient. A structured-exchange tuple has a recoverable `present_*` result and, when required, exactly one terminal response result before the next agent turn consumes it; `present_question` derives free-text vs choice vs multi-choice from its own structure and `request_response` is the single terminal tool that routes every present's response (free-text/choice/multi-choice for `present_question`, review for `present_review_set`) from pending transcript state. The target details model is checked by `schema` + `v`, `exchange_id`, and `tool_meta`; request outcomes are an exactly-one property-presence union; user-authored text is `comment` and runtime-authored text is `message`; present-side status/kind/expected-request aliases and capture graph payloads are invalid in the Zod-authored schema layer. `toolResult.content` is rich markdown suitable for both TUI transcript display and model context; `toolResult.details` carries structured projection/recovery data. In TUI-driven sessions, `request_response` answers free-text prompts from the interactive editor when present; the live exchange broker is the headless/web-driver fallback, not an override of the TUI response surface. | covered for current structured-exchange tools (registered sequential `present_question`, `present_review_set`, and `request_response`; retired `present_options`, `request_answer`, `request_choice`, `request_choices`, and `request_review` tools are unregistered while their request result-detail discriminants are preserved; runtime details are emitted from canonical `schema`/`v`/snake_case Zod shapes; tests cover non-semantic `renderCall`, markdown `renderResult`, present/request details, unmatched-present recovery, active-vs-stub registry, JSON-editor fallback for multi-choice, TUI-editor precedence over an attached live broker for `request_response`, broker fallback when no interactive editor exists, `request_response` for `present_question` through the shared answer-source and choice-source dispatchers (free-text editor/broker/cancellation, TUI-only single-choice and multi-choice, headless choice unavailable, unknown diagnostics, and recovery continuation), `request_response` for `present_review_set` through the shared review source (approve/request-changes/reject with required change-request comment, cancellation, headless unavailable, emitting preserved `request_review` result details), terminal `answered`/`cancelled`/`unavailable` projection closure, option content/rationale parity, review-set `nodes`/`edges` details parity, invalid review proposal non-recovery, review pending-exchange recovery, public-RPC deterministic permutations, capture response-to-graph proof, and same-assistant-message `present_question → request_response` ordering over a real Pi RPC run. The Zod-authored schema layer is covered by JSON Schema export, drift-rejection, and source-boundary tests for present/request/capture details; `present_review_set.payload` imports the graph-owned boundary-teaching payload schema (not `z.unknown()`), so a JSON-string payload, the `mutate_graph` `{createBasis, ops}` shape, or malformed nested companions such as `grounding: string` are rejected at the param boundary rather than deep in the executor, while requiredness and field-level structural diagnostics stay owned by `graph/review-set.ts`. `present_question`'s params make the present-side choice structural: `options[]` presence selects choice mode and `multiple` selects multi-choice, so the model no longer chooses between separate question/options tools. `present_candidates` remains a named stub and intentionally unregistered.) | D12-L, D13-L, D17-L, D37-L, D38-L, D41-L | | I24-L | A Brunch-launched Pi runtime does not load ambient user/project Pi context files, extensions, skills, prompt templates, themes, or behavior-shaping settings unless Brunch's sealed Pi settings/extension boundary explicitly allows them; Brunch-owned extension-discovered resources are identified as intentional product resources. | covered for TUI-launch settings/extension boundary by contract tests: ambient resource flags and explicit extension factories are preserved; hostile ambient global/project settings are ignored by the in-memory Brunch settings policy before and after reload; audited Pi settings getters are tracked in `src/.pi/brunch-pi-settings.ts`. Subagent child-session sealing is covered separately under I29-L. | D2-L, D39-L | -| I25-L | The active `op_mode`, `strategy`, and `lens` are reconstructable from linear `brunch.agent_runtime_state` entries at turn start and through `session.runtimeState`; concrete axis ids stay separate from the `auto` selection sentinel; the foreground session-agent role is derived from `op_mode`, not separately stored; tool gating follows the reconstructed `op_mode` so `elicit` cannot use execute/dangerous tools such as raw `bash`/`write` unless explicitly permitted. Runtime-state projection remains transcript-backed and exposes empty/default mention, world-watermark, and lifecycle slots without inventing hidden extension memory; legal option/default affordances are pure agent-runtime policy derivations over resolved runtime state plus capability-readiness over gaps (D74-L), not persisted state. | covered (`src/session/runtime-state.test.ts` covers default state, cumulative last-writer-wins posture, mention/world/lifecycle slot projection, and non-linear rejection; `src/rpc/handlers.test.ts` covers explicit-target `session.runtimeState` discovery/params/spec validation; `src/.pi/__tests__/operational-mode.test.ts` covers append/project/switch helpers over the reconciled two-axis vocabulary, AUTO selection for every runtime axis, stale `agentGoal` tolerance on existing transcript entries, init idempotence, previous-state values, malformed/illegal tuple rejection, role derivation from `op_mode`, and Pi JSONL reload projection; `prompting.test.ts` covers prompt/tool-policy projection from the same transcript-backed runtime state, including selected-spec gap activation for `present_review_set` / `request_review` proposal tools; `src/.pi/extensions/agent-runtime/runtime/authority-matrix.test.ts` covers the current POC authority matrix for `elicit-read-only`, including base-allowed local/web read tools, blocking `bash`/`edit`/`write`, and structured `needs_human` result representability while leaving A18-L strict built-in suppression as residue; `src/agents/runtime/__tests__/policy.test.ts` covers shared strategy/lens legal options, defaults, AUTO freestyle exclusion, pinned freestyle, gap-driven gated legality, and loud missing-gap failures; `src/session/runtime-affordances-coverage.test.ts` guards the required-vs-deferred affordance ledger). | D17-L, D23-L, D40-L, D58-L, D59-L, D66-L, D85-L | +| I25-L | The active operational mode is reconstructable from linear `brunch.agent_runtime_state` entries at turn start and through `session.runtimeState`; the foreground session-agent role is derived from operational mode, not separately stored; tool gating follows the reconstructed mode so SPEC cannot use CODE/dangerous tools such as raw `bash`/`write` unless explicitly permitted, and CODE receives the executor tool policy. Strategy/lens/method are not persisted runtime axes, not `AUTO` selections, and not required TUI affordances. Runtime-state projection remains transcript-backed and exposes empty/default mention, world-watermark, and lifecycle slots without inventing hidden extension memory; legal mode affordances are pure agent-runtime policy derivations over resolved runtime state, not persisted prompt-resource selections. | planned/partially covered (existing tests cover transcript-backed runtime projection, role derivation from `op_mode`, authority-matrix blocking, and legacy strategy/lens affordances; D98-L requires a correction pass that removes strategy/lens/method runtime projection/oracles, renames product modes to SPEC/CODE, and proves executor tool-policy separation). | D17-L, D23-L, D40-L, D58-L, D85-L, D98-L | | I27-L | Session display names are presentation metadata only: every Brunch-created session gets a neutral workspace-global default `session_info` label (`Untitled Session N`) at creation, unchanged defaults do not collide across specs in one cwd, later user/generated names may replace the default, and no naming path mutates spec identity, session binding, or graph truth. | planned (creation/boundary tests for workspace-global default allocation across specs and replacement sessions; session-lifecycle naming tests with empty transcript/auth failure/success paths; picker/chrome projection tests read session names when present) | D6-L, D21-L, D35-L, D42-L | | I26-L | Runtime schema-library imports stay deliberately scoped: Zod may appear in D41-L-acknowledged product/protocol schema seams — the structured-exchange schemas (`src/.pi/extensions/exchanges/schemas/`), the graph-owned `present_review_set` payload teaching schema co-located with its deep validator (`src/graph/review-set.ts`), and the dev-gated query-tool params (`src/.pi/extensions/{session-query,introspect-query}/`), each converting to Pi `TSchema` only through a single per-plane `z.toJSONSchema(..., { unrepresentable: 'throw' })` cast adapter (`exchanges/pi-schema.ts`, `shared/pi-tool-schema.ts`); TypeBox remains valid for unrelated Pi tool parameters (e.g. graph tools), small config/frontmatter contracts, and Drizzle-derived row schemas; no boundary may hand-author parallel Zod and TypeBox sources for the same shape. Pi tool parameter schemas authored in Zod must export JSON Schema draft 2020-12 (Zod v4 default), so tuples emit `prefixItems` rather than the draft-07 array-`items`/`additionalItems` form that strict provider validators (Anthropic) reject. Drizzle row/insert/update schemas are not hand-authored alongside their target tables. | covered (structured-exchange schema tests prove Zod parse/export and assert semantic details contracts stay in `src/.pi/extensions/exchanges/schemas/` except for the graph-owned review-set payload teaching schema imported from `src/graph/review-set.ts`; the legacy `shared/model.ts` details interface is retired; structured-exchange TypeBox usage is quarantined to the single Pi `TSchema` cast adapter in `src/.pi/extensions/exchanges/pi-schema.ts`, and the dev query tools to `src/.pi/extensions/shared/pi-tool-schema.ts`; `session-query`/`introspect-query` tests assert the advertised parameter schema is draft 2020-12 with no draft-07 tuple form; the no-direct-`db/`-imports-outside-`graph/` boundary is enforced statically by oxlint `no-restricted-imports` (`.oxlintrc.json`), with the residual `architecture.test.ts` greps covering only the db→graph kinds-only edge and `db/schema.ts` enum-array ownership that lint cannot express; Drizzle derivation via `drizzle-typebox` in `row-schemas.ts`) | D41-L | | I28-L | Auto-compaction output preserves the configured anchor set byte-stable: every entry kind listed in [src/.pi/extensions/compaction/index.ts](src/.pi/extensions/compaction/index.ts) is reconstructable post-compaction according to its `select` rule (`first | latest | active-leaves | all-unresolved`); LLM-generated narrative summary never replaces or rephrases preserved-anchor content; extension failure falls through to Pi default compaction rather than dropping anchors silently. | planned (compaction round-trip property tests at M9 plus inner-loop anchor-rendering unit tests and TypeBox schema validation of the anchor contract) | D43-L; R15, R13; I3-L, I4-L, I8-L, I12-L | @@ -362,10 +363,10 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c | I32-L | Public RPC structured-exchange driving never requires a client to speak raw Pi RPC: after Brunch method discovery and workspace/spec/session activation, each pending assistant-originated exchange is answered exactly once through `session.submitExchangeResponse`, and the deterministic permutation run produces linear Pi JSONL whose structured exchange projection preserves the same prompt/answer/status/comment artifacts as the equivalent TUI structured-exchange path. | covered for deterministic FE-744 parity under canonical session method names (`session.triggerExchange`, `session.pendingExchange`, `session.submitExchangeResponse`, `session.exchanges`): `rpc.discover` contract tests, pending/respond lifecycle tests, current public-RPC structured-exchange permutations, terminal non-answered status handling, option content/rationale parity, no repeated deterministic prompts, and transcript/exchange parity assertions. | D5-L, D48-L, D49-L; I10-L, I13-L, I21-L, I23-L | | I33-L | `capture_*` analysis entries are transcript evidence only: they persist as Brunch structured-exchange `toolResult` rows, are included by Brunch-semantic transcript renderers, are hidden or collapsed in TUI display, and never mutate graph truth or bypass `CommandExecutor`. | partially covered (minimum capture details schemas parse/export and reject graph payload fields; future runtime capture-analysis schema/rendering tests plus transcript renderer fixtures still need to prove persisted result rendering and TUI hide/collapse behavior; later graph-capture fixtures compare analysis candidates against committed graph mutations) | D17-L, D18-L, D37-L, D47-L, D50-L; I2-L, I11-L, I23-L, I30-L | | I34-L | `mutateGraph` batch validation is all-or-nothing: if any node or edge in the batch is structurally illegal, the entire batch is rejected and no partial state is persisted; the agent receives diagnostics sufficient for bounded self-correction retry. | covered (`command-executor/commit-graph-batch.test.ts` and graph-tool adapter tests cover dry-run/commit diagnostic parity for invalid basis, missing refs/codes, invalid category/stance, self-loop, invalid node kind/detail shape, rollback of nodes/edges/change_log/counters, transaction-local planning before LSN allocation/writes, and structured adapter diagnostics without thrown projected-code errors or fake endpoint refs) | D53-L; I1-L, I11-L | -| I35-L | Graph context reads support multiple detail levels: a cursory/compact full-graph overview for orientation, detailed node-neighborhood context with configurable hop depth for focused work, bounded list slices by kind/readiness band, and related-node traversal by anchor and edge category. The `read_graph` parameter boundary teaches the mode-specific companion contract: `neighborhood` requires non-empty `nodeCode`, `related` requires non-empty `anchorCodes` plus `edgeCategory`, and list modes intentionally treat omitted/empty filters as unfiltered slices while unknown filters produce an empty slice. Context builders in `.pi/extensions/agent-runtime/system-prompts/seed/` (pushed seed contexts) and `.pi/extensions/brunch-data/context/` (pull tools) orchestrate which level to inject or advertise based on mode/goal/strategy/lens/readiness. | covered for current POC push and pull paths (`getGraphOverview` + `getNodeNeighborhood` in `queries.ts` with 10 tests; `src/.pi/extensions/agent-runtime/system-prompts/seed/{graph,workspace}.test.ts` covers lens-shaped overview rendering and selected-spec workspace/session/posture seed context, and `src/.pi/__tests__/context-tools.test.ts` covers the pulled context tools including bounded node-neighborhood rendering; `src/.pi/__tests__/prompting.test.ts` proves the explicit shell/product prompt path supplies selected-spec-bound graph context to `composeAgentPrompt()`). `src/graph/__tests__/observed-shapes-coverage.test.ts` guards the read mode ledger, and `src/.pi/__tests__/graph-tools.test.ts` covers `read_graph` mode-companion schema enforcement plus loud adapter diagnostics for malformed companion calls. Pulled context tools are part of the live read surface. | D52-L, D53-L, D58-L | +| I35-L | Graph context reads support multiple detail levels: a cursory/compact full-graph overview for orientation, detailed node-neighborhood context with configurable hop depth for focused work, bounded list slices by kind/readiness band, and related-node traversal by anchor and edge category. The `read_graph` parameter boundary teaches the mode-specific companion contract: `neighborhood` requires non-empty `nodeCode`, `related` requires non-empty `anchorCodes` plus `edgeCategory`, and list modes intentionally treat omitted/empty filters as unfiltered slices while unknown filters produce an empty slice. Context builders in `.pi/extensions/agent-runtime/system-prompts/seed/` (pushed seed contexts) and `.pi/extensions/brunch-data/context/` (pull tools) orchestrate which level to inject or advertise based on operational mode, selected-spec state, readiness, and the concrete task — not persisted strategy/lens selections. | covered for current POC push and pull paths (`getGraphOverview` + `getNodeNeighborhood` in `queries.ts` with 10 tests; `src/.pi/extensions/agent-runtime/system-prompts/seed/{graph,workspace}.test.ts` covers lens-shaped overview rendering and selected-spec workspace/session/posture seed context, and `src/.pi/__tests__/context-tools.test.ts` covers the pulled context tools including bounded node-neighborhood rendering; `src/.pi/__tests__/prompting.test.ts` proves the explicit shell/product prompt path supplies selected-spec-bound graph context to `composeAgentPrompt()`). `src/graph/__tests__/observed-shapes-coverage.test.ts` guards the read mode ledger, and `src/.pi/__tests__/graph-tools.test.ts` covers `read_graph` mode-companion schema enforcement plus loud adapter diagnostics for malformed companion calls. D98-L requires follow-up correction where tests still speak in lens-shaped terms. Pulled context tools are part of the live read surface. | D52-L, D53-L, D58-L, D98-L | | I36-L | Node `kind` is drawn from a per-plane closed enum structurally validated by the `CommandExecutor`. | covered (CommandExecutor rejects invalid kind-for-plane; tests in `command-executor.test.ts`) | D54-L, D56-L | | I37-L | `detail` is per-kind validated and boundary-advertised from the graph schema owner: `decision` and `term` nodes REQUIRE `detail` with their respective sub-schemas; the claim kinds `requirement`/`criterion`/`invariant` accept an OPTIONAL `form`-discriminated union (`plain`/`gherkin`/`formal`) and `context` accepts an OPTIONAL `form:"given"` payload (D88-L); all other kinds must omit `detail`; the `form` discriminant and any unknown per-form fields are rejected. `kind` drives behavior (band/edge-legality/source-question); `form` is inert payload plus a renderer hook. The agent/tool and dev-RPC mutation schemas expose the same per-kind companion shapes — including the claim/context form union — instead of accepting opaque `Unknown`. | covered (detail-required/prohibited/form-shape tests in `command-executor.test.ts`; boundary schema companion tests in `mutate-graph-edge-schema.test.ts`) | D54-L, D88-L | -| I38-L | Every Brunch prompt-resource manifest injected for an agent turn is generated from projected runtime state and spec/workspace gates: listed resources are Brunch-owned, readable under the active tool policy, legal for the current `(op_mode × strategy × lens)` tuple / capability-readiness / agent allow-list, and off-list resources are not advertised as available. AUTO axes never list readiness-illegal choices; pinned axes point to the pinned resource whenever the tuple is role/mode-legal, even if readiness negotiates. The shared affordance derivation and prompt manifest filtering use the same capability-readiness/AUTO legality source. | covered for current P0 manifest families (`src/agents/runtime/__tests__/compose.test.ts` covers default header/context/manifest output, two-AUTO-axis manifest output with no ``, AUTO capability-readiness filtering, pinned singleton resources, readiness-thin pin retention, role/mode-illegal pin rejection, and readable `src/agents/` locations; `src/.pi/__tests__/prompting.test.ts` covers the explicit shell `before_agent_start` product path appending `composeAgentPrompt()` output from transcript-projected runtime state and no legacy composer import/resource discovery; `src/.pi/extensions/agent-runtime/runtime/state.test.ts` plus `src/agents/runtime/__tests__/policy.test.ts` cover shared legality/default behavior, including AUTO excluding `freestyle` and gated methods staying withheld during negotiation). FE-825 added a dev-gated introspection loop (`src/.pi/extensions/dev-mode/introspection/` + `src/dev/introspection-launcher.ts`) that records final provider payloads and pairs them with subjective model answers under `.fixtures/scratch/introspection//`; `brunch_introspect_query` now makes the captured provider payload/tool schemas/base options queryable in-chat for the same diagnostic plane. Probe fitness may still track whether the agent reads selected resources before use. | D39-L, D40-L, D58-L, D59-L, D66-L, D69-L, D85-L | +| I38-L | Every Brunch prompt-resource/reference manifest injected for an agent turn is generated from projected runtime state and spec/workspace gates: listed resources are Brunch-owned, readable under the active tool policy, legal for the current operational mode / capability-readiness / agent allow-list, and off-list resources are not advertised as available. Strategy/lens/method are not runtime axes, so manifest filtering must not depend on pinned/AUTO prompt-resource selections. The shared affordance derivation and prompt manifest filtering use the same operational-mode and capability-readiness source. | planned/partially covered for the pre-D98 manifest families (`src/agents/runtime/__tests__/compose.test.ts`, `src/.pi/__tests__/prompting.test.ts`, and runtime policy tests cover sealed resource paths and provider-payload inspection but still encode legacy two-axis behavior). FE-825 added a dev-gated introspection loop (`src/.pi/extensions/dev-mode/introspection/` + `src/dev/introspection-launcher.ts`) that records final provider payloads and pairs them with subjective model answers under `.fixtures/scratch/introspection//`; `brunch_introspect_query` now makes the captured provider payload/tool schemas/base options queryable in-chat for the same diagnostic plane. D98-L requires a correction pass that removes AUTO/pinned-axis assumptions and verifies mode-only manifests plus load-on-demand references. Probe fitness may still track whether the agent reads selected resources before use. | D39-L, D40-L, D58-L, D69-L, D85-L, D98-L | | I39-L | Every graph node in a spec has exactly one stable projected human reference code derived from `kind` + `kind_ordinal`; `(spec_id, plane, kind, kind_ordinal)` is unique; ordinals are monotonic per `(spec_id, plane, kind)` and are not reused after deletion or supersession. | partially covered (`graph-tool-resilience` added `nodes.kind_ordinal`, `node_kind_counters`, DB uniqueness, CommandExecutor allocation for single-node/batch writes, rollback protection, `GraphNode.kindOrdinal` row mapping, globally unique 1–3 letter labels with readiness-band metadata, projected-code parsing, selected-spec adapter resolution before `CommandExecutor`, code-only `mutate_graph` / `read_graph` schemas, and code-primary prompt/tool rendering; `queries.test.ts` now pins the merged `NODE_KIND_METADATA` labels + D64-L readiness bands so schema/code drift fails loudly; remaining slice still needs deletion/supersession no-reuse coverage) | D54-L, D62-L; I1-L, I11-L | | I40-L | Accepted graph nodes and edges use only `basis ∈ explicit | implicit`; review-set approval and direct user statements produce `explicit`, `propose-graph` concept-level materialization produces `implicit`, and the mutation path is recoverable from `change_log` rather than from a persisted basis enum value such as `accepted_review_set`. | covered (`graph-tool-resilience` replaced the persisted basis enum with `explicit | implicit`, made `mutateGraph` apply one batch create-basis to all created nodes/edges, made single-node `createNode` reject retired basis values before LSN/counter/node/change-log allocation, made `propose-graph` adapter commits implicit, made review-set translation explicit, rejected retired `accepted_review_set`, and records `change_log.operation` independently; `capture-response-to-graph` proves direct structured text responses commit explicit-basis graph nodes through `CommandExecutor`; `.fixtures/runs/project-graph-review-cycle/2026-06-06-project-graph-review-cycle/` proves full review-cycle approval creates explicit-basis graph truth) | D26-L, D27-L, D53-L, D63-L | | I41-L | Same-spec `supersession` edges form an acyclic directed graph; every edge-creation path validates proposed supersession edges together with existing supersession edges before committing. | covered (`command-executor/commit-graph-batch.test.ts` rejects existing-cycle closure, intra-batch cycles, and mixed existing+batch cycles through the shared dry-run/commit planner before batch writes; rejected cycles roll back or avoid batch nodes/edges/change_log; acyclic supersession commits remain covered by query/CommandExecutor success paths) | D51-L, D53-L; I34-L | @@ -403,28 +404,27 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c ### Prompt/runtime profile architecture -- Brunch prompt composition is a **runtime-header + gated prompt-resource manifest** composed per agent by `composeAgentPrompt(...)` in `src/agents/runtime/compose.ts` (D58-L). The direct injection is intentionally small: agent control summary, two-axis runtime state (`strategy` + `lens`), a legal `` resource manifest with per-skill `kind`, `name`, `description`, and `location`, router rules for pinned/AUTO axes, and compact context handles/rendered context blocks. Detailed strategy/lens/method bodies are Brunch-owned markdown resources the agent loads with `read` when needed, while the elicitor objective postures are inline in `src/agents/prompts/elicitor/SYSTEM.md`. The old `src/.pi/context/` prompt-pack layout is retired; top-level `src/agents/` is now the Brunch-owned LLM-context ingress home, not a Pi-only agent tree. +- Brunch prompt composition is a **runtime-header + sealed resource/reference manifest** composed per agent by `composeAgentPrompt(...)` in `src/agents/runtime/compose.ts` (D58-L, D98-L). The direct injection is intentionally small: agent control summary, selected operational mode, a legal `` / reference manifest with per-resource `kind`, `name`, `description`, and `location`, and compact context handles/rendered context blocks. Detailed guidance bodies and canonical references are Brunch-owned markdown resources the agent loads with `read` when needed; they are not selected runtime axes. The old `src/.pi/context/` prompt-pack layout is retired; top-level `src/agents/` is now the Brunch-owned LLM-context ingress home, not a Pi-only agent tree. - Concrete `agents/prompts` + `agents/skills` + `agents/runtime` topology (D52-L). The markdown/code boundary falls on the control-plane/behavior split: enforcement and projection are TypeScript under `agents/runtime/`; `.pi/extensions/agent-runtime/` is the hook/tool adapter. Semantic prompting material is markdown under `agents/prompts/{agent-name}/SYSTEM.md` for live agent bodies and `agents/skills/`. ```text src/agents/ prompts/ README.md [md] ownership + migration note - elicitor/SYSTEM.md [md+] live foreground elicit-mode body + elicitor/SYSTEM.md [md+] live foreground SPEC-mode body reviewer/SYSTEM.md [md] background proposal/commitment review body - pi-coder/SYSTEM.md [md] unwired Pi coding-agent baseline / future augmentation body + executor/SYSTEM.md [md] future CODE-mode Brunch-aware coding/execution body skills/ README.md [md] ownership + body-lock ledger - strategies/*/SKILL.md [md] step-wise-decision-tree, step-wise-disambiguate, freestyle - lenses/*/SKILL.md [md] intent, design, oracle (future execute: plan, sync, scope) - methods/*/SKILL.md [md] run-structured-exchange, capture, generate-proposal, - read-context, commit-graph, review-for-gaps, - acquisition/readback methods + strategies/*/SKILL.md [md] legacy/suspended interaction-shape resources; prune or fold as evidence dictates + lenses/*/SKILL.md [md] legacy/suspended topical resources; prune or fold as evidence dictates + methods/*/SKILL.md [md] capture/generate/project/read/write/acquisition guidance resources + contexts/references/* [md] runtime-eligible canonical graph/ontology/heuristic references runtime/ - compose.ts [ts] projection -> runtime header + gated manifest (not a state machine) + compose.ts [ts] projection -> runtime header + sealed resource manifest (not a state machine) prompt-skills.ts [ts] SKILL.md manifest loader/renderer - state.ts [ts] legal (op_mode × strategy × lens) tuple table; code-owned - SKILL.md path list + family/kind/legality metadata + state.ts [ts] op_mode -> foreground agent/tool/resource policy; code-owned + resource path list + family/kind/legality metadata src/.pi/ extensions/ agent-runtime/system-prompts/ [ts] before_agent_start hook adapter + world-read cache @@ -432,16 +432,16 @@ src/.pi/ brunch-data/context/*.ts [ts] D60-L pull-tool context surface (read_workspace_context, read_session_context) ``` -- Manifest availability is code-owned, not filesystem-discovered: `agents/runtime/state.ts` binds each legal axis value to an explicit `src/agents/skills///SKILL.md` path and each live agent role to its `src/agents/prompts//SYSTEM.md` location. It loads prompt-resource `name` and `description` from `SKILL.md` frontmatter through pi's loader with `includeDefaults: false` and an explicit `skillPaths` list. `composeAgentPrompt()` emits legal resource bindings; the prompt extension reads the selected agent body explicitly and passes it into the pure composer. This keeps the legal set sealed while making the file body/frontmatter the description source of truth. +- Manifest availability is code-owned, not filesystem-discovered: `agents/runtime/state.ts` binds each legal operational mode/agent policy to explicit resource paths and each live agent role to its `src/agents/prompts//SYSTEM.md` location. It loads prompt-resource `name` and `description` from `SKILL.md` frontmatter through pi's loader with `includeDefaults: false` and an explicit `skillPaths` list where skills remain useful; generated/authored context references are likewise explicit Brunch resources, not ambient files. `composeAgentPrompt()` emits legal resource bindings; the prompt extension reads the selected agent body explicitly and passes it into the pure composer. This keeps the legal set sealed while making the file body/frontmatter/reference file the description source of truth. - The D60-L agent-context orchestration layer (TypeScript) lives in `src/agents/contexts/`: `seeds/` owns compact pushed/origination context, while `workspace/`, `specification/`, `session/`, `graph/`, `elicitation.ts`, and `exchanges/` own provider-visible context-tool and tool-result text. `.pi/extensions/agent-runtime/system-prompts/` and `.pi/extensions/brunch-data/context/` are adapters that gather data and call those renderers. Contexts are not part of the `read`-on-demand resource manifest and carry no `` family. - Workspace **posture** is workspace-scoped product state persisted in `.brunch/workspace.json`, not spec state, session state, or graph truth. D57-L keeps it off the spec row and graph; D58-L composition injects known posture values into the runtime header as an axis of agent influence, and the `capture-posture` goal (D59-L) can confirm or refine those values conversationally. - Readiness is judged just-in-time per requested capability, not as a user-facing workflow stepper, a stored grade, a session-local phase, or a graph-node-kind whitelist. There is no `readiness_grade` on the spec row (D45-L); capability-readiness (D74-L) is evaluated over the relevant `elicitation_gaps`, and D64-L readiness bands describe non-exclusive evidence groupings feeding the readiness-estimate rollup, goal selection, and context filtering. The soft readiness estimate may surface in UI but gates nothing. A future structural milestone gate for export/plan/execute op-modes is deferred until such an op-mode exists; before readiness grows beyond the current tracer, Brunch still needs a real evaluator path for `manual` gaps and a more differentiated per-capability map than the shared grounding floor (A27-L). -- Prompt resources and Pi skills are both progressive-disclosure mechanisms, but they are not authority. Brunch code owns runtime-state projection, legal tuple filtering, capability-readiness/allow-list gating, tool activation, and tool-call blocking. Explicit user/system pins remain visible when readiness negotiates; negotiation changes AUTO choices, method/tool availability, and response posture rather than authority. Pi-native skills may be used for startup-scoped capabilities; runtime-state-specific objective/method availability is advertised through Brunch's per-turn manifest so ambient user/project resources cannot leak into product behavior. +- Prompt resources, context references, and Pi skills are progressive-disclosure mechanisms, but they are not authority. Brunch code owns runtime-state projection, mode filtering, capability-readiness/allow-list gating, tool activation, and tool-call blocking. D98-L removes strategy/lens/method pins and AUTO choices from product runtime state; readiness negotiation changes response posture and advisory context, not authority. Pi-native skills may be used for startup-scoped capabilities; Brunch-owned resource availability is advertised through the sealed per-turn manifest so ambient user/project resources cannot leak into product behavior. ### Coherence and readiness semantics - Coherence must remain bounded for the POC: a visible verdict tied to structural legality and actionable reconciliation needs, not a vague promise that the specification “makes sense.” M8 owns the sharper rubric and adversarial examples. -- Avoid phase/stage/maturity language for the elicit lifecycle except when referring to legacy docs. The canonical internal model is capability-readiness over `elicitation_gaps` plus the session-agent `goal` / `strategy` / `lens` axes and active review-set state; the readiness estimate is a soft UI projection, not a stage. PLAN/frontier text should describe concrete capability-readiness gates rather than imply a user-facing phase machine. +- Avoid phase/stage/maturity language for the SPEC lifecycle except when referring to legacy docs. The canonical internal model is capability-readiness over `elicitation_gaps`, the SPEC-mode capability spine (`capture` / `generate` / `project`), and active review-set state; the readiness estimate is a soft UI projection, not a stage. PLAN/frontier text should describe concrete capability-readiness gates rather than imply a user-facing phase machine or strategy/lens/method runtime machine. - **Readiness-band four-band model — MATERIALIZED (D94-L).** The `readiness-bands-interrogation` pass resolved the candidate and the code now carries the derived four-band ladder `grounding → elicitation → projection → commitment`: `READINESS_BANDS` owns the enum order, `bandsForKind(kind)` in `src/graph/schema/nodes.ts` derives node membership from plane + intent bisection + explicit band-less set, graph filters/renderers read that function, and the soft estimate/prompt previews render the four-band order. The two-carrier guard remains I50-L: elicitation agenda readers must continue to read `gap.band`, while node-band readers use derived node membership. Coordinate future renderer-golden work (FE-870) against this four-band order. **Still provisional — REQ/AC plane relocation:** moving `requirement`/`criterion` from the intent plane to the plan plane (or a new `commit` plane) would make the intent band table a clean grounding|elicitation bisection and align plane↔super-type, but it is **not** load-bearing for D94-L (REQ/AC are `commitment` band either way) and is held open because the planning *process* is undefined — requirement-projection may precede planning while acceptance criteria may become slice-connected plan nodes. Tripwire to decide: when the planning-process model lands and settles whether REQ-projection precedes or is part of planning. Until then REQ/AC stay intent-plane. ### Vocabulary evolution @@ -495,19 +495,20 @@ src/.pi/ | --- | --- | | **Brunch host** | The local process-level authority. Owns `.brunch/` resolution, agent session lifecycle, mode dispatch, and event fanout. | | **Transport mode** | One of TUI, web, RPC, print. All four drive the same host; they are presentation/protocol surfaces, not separate products or agent strategies. | -| **Operational mode** | A top-level Brunch authority/tooling posture — `elicit` now, with planned `execute` / `code` (D93-L). 1:1 with its foreground agent (the op-mode-keyed source of truth). It determines what kind of work is allowed and which tools/prompt posture are available. Distinct from Pi's transport mode concept. | -| **Agent role** | A worker identity. The **foreground session-agent role** (`elicitor` now, future `orchestrator`/`pi-coder`) drives the main turn and is *derived* from `op_mode` 1:1 (D93-L), not stored as session state. **Side/sub-agent roles** (`reconciler`, background `explorer`/`researcher`/`projector`/`reviewer`) run async/advisory or delegated work out-of-band and are never part of the session state machine. | -| **Agent definition** | Composition control unit (D58-L): a keyed agent's identity/system prompt, model/thinking preset, mode-gated tool authority summary, and applicability allow-lists (`strategies`/`lenses`/`methods`). A keyed registry covers the foreground session agent plus side/sub-agents. Replaces the prior "runtime bundle / role preset" framing. | -| **Session agent** | The main-thread agent that drives the session forward — `elicitor` now, future `orchestrator` (`execute`) / `pi-coder` (`code`) — resolved 1:1 from `op_mode` (D93-L). It is the only agent represented in session state (D40-L); side/sub-agents are out-of-band. | +| **Operational mode** | The only user-changeable Brunch session-agent posture, exposed as `SPEC` or `CODE` (D98-L). It is 1:1 with its foreground agent (the op-mode-keyed source of truth), determines what kind of work is allowed, and owns tool/resource policy. Distinct from Pi's transport mode concept. | +| **Agent role** | A worker identity. The **foreground session-agent role** (`elicitor` for SPEC, `executor` for CODE) drives the main turn and is *derived* from operational mode 1:1 (D93-L/D98-L), not stored as independent session state. **Side/sub-agent roles** (background `explorer`/`researcher`/`projector`/`reviewer` and future workers/auditors) run delegated work out-of-band and are never part of the session state machine. | +| **Agent definition** | Composition control unit (D58-L/D90-L): a keyed agent's identity/system prompt, model/thinking preset, mode-gated tool authority summary, resource grants, and delegation allow-list. A keyed registry covers the foreground session agent plus background agents. Replaces the prior "runtime bundle / role preset" framing and no longer treats strategy/lens/method as runtime axes. | +| **Session agent** | The main-thread agent that drives the session forward — `elicitor` in SPEC mode, `executor` in CODE mode — resolved 1:1 from operational mode (D93-L/D98-L). It is the only agent represented in session state (D40-L); side/sub-agents are out-of-band. | | **Subagent** | A main-agent-invoked, blocking background child session (D44-L/D91-L): caller chooses a background `AgentManifest`, Brunch starts a sealed in-process SDK `AgentSession`, injects only explicit parent-world snapshot/read handles, and returns the child's assistant text as ordinary tool-result content. Ambient `.pi` discovery, parent `CommandExecutor` access, and inherited conversation context remain sealed out. | -| **Strategy** | An optional, AUTO-able session-agent axis (D25-L/D85-L) describing interaction shape: `step-wise-decision-tree` (single-exchange Q&A), `step-wise-disambiguate` (contrastive examples), and `freestyle` (structure-optional, user-driven turns; D66-L). Strategy no longer determines graph-write mechanism; direct commit and review-set mechanics are method-routed through `commit-graph` and `generate-proposal`. Detailed strategy behavior lives in a Brunch prompt resource advertised through D58-L manifests. | -| **Lens** | An optional, AUTO-able session-agent axis (D25-L) describing topical focus: `intent`, `design`, `oracle` for elicit mode; future execute-mode `plan`, `sync`, `scope`. Orthogonal to strategy; stamped onto elicitor-emitted entries as provenance (I18-L). Detailed lens behavior lives in a Brunch prompt resource advertised through D58-L manifests. | -| **Goal posture** | Retired as a runtime/manifest axis by D85-L. The former postures — `grounding-advance`, `elicit-expand`, `commit-converge`, plus always-on `capture-posture` — are inline objective guidance in `elicitor/SYSTEM.md`, selected by the agent from readiness bands, open gaps, and workspace posture. Distinct from graph `goal` node kind. | -| **AUTO** | The unpinned state of a runtime prompt-resource axis (`strategy` / `lens`): composition advertises the legal choices in the current prompt-resource manifest and instructs the agent to self-select from that manifest only, reading the selected resource when detail matters (D58-L). | +| **Strategy** | Suspended as runtime state by D98-L. The term may survive only as prompt-resource or reference vocabulary for interaction shapes if a concrete agent behavior proves it useful; it is not a user-changeable axis, AUTO selection, or transcript-backed posture. | +| **Lens** | Suspended as runtime state by D98-L. The term may survive only as prompt-resource or reference vocabulary for topical/plane framing (`intent`, `design`, `oracle`) if a concrete agent behavior proves it useful; payloads should carry explicit plane/provenance fields only when a downstream reader needs them. | +| **Goal posture** | Retired as a runtime/manifest axis by D85-L/D98-L. The former postures — `grounding-advance`, `elicit-expand`, `commit-converge`, plus always-on `capture-posture` — are inline objective guidance in `elicitor/SYSTEM.md`, selected by the agent from readiness bands, open gaps, and workspace posture. Distinct from graph `goal` node kind. | +| **AUTO** | Retired for prompt-resource axes by D98-L. Operational mode has explicit product choices (`SPEC` / `CODE`); prompt resources and context references are available for load-on-demand reading, not selected through persisted AUTO strategy/lens state. | | **Brunch Pi Profile** | The sealed programmatic wrapper around embedded Pi: settings policy, resource-loader policy, extension factories, keybinding/command policy, tool policy, and prompt policy. It allows Brunch-owned resources while suppressing ambient `.pi/` behavior. | -| **Prompt resource** | A Brunch-owned markdown file under `src/.pi/` containing detailed strategy, lens, method, or agent-definition guidance. Prompt resources are loaded by the agent with `read` when needed; they are product control-plane assets, not ambient Pi prompt templates. | -| **Prompt-resource manifest** | The small per-turn D58-L `` block injected into the system prompt, listing only runtime-legal Brunch resources with `kind`, `name`, `description`, and `location`. The legal set and locations are code-owned in `agents/runtime/state.ts` (not filesystem-discovered); `name` and `description` are file-owned `SKILL.md` frontmatter read via pi's loader over the explicit path list. The seed-context and `.pi/extensions/brunch-data/context/` context renderers are not manifest resources. It mirrors Pi's skill-list element structure but is filtered by Brunch runtime state, capability-readiness, and allow-lists. | -| **Method** | A tool-usage or workflow competence advertised as a Brunch prompt resource (`agents/skills/methods//SKILL.md`): run structured exchanges, capture (the D80/D81/D82 home; full conduct in FE-861), generate proposals/projections, read context, mutate the graph, review for gaps. Method resources explain when to use a tool family and how to sequence it with other tools; executable tool definitions should stay focused on schemas, authority, and runtime behavior. A method may also be backed by a Pi-native skill, but actual tool authority remains code-owned through `op_mode` policy and active-tool gating. `capability` is retired as a synonym — use `method` and `method` in ``. | +| **Prompt resource** | A Brunch-owned markdown file under `src/agents/` containing detailed agent guidance. Prompt resources are loaded by the agent with `read` when needed; they are product control-plane assets, not ambient Pi prompt templates and not runtime state. | +| **Context reference** | A runtime-eligible, agent-optimized markdown reference under `src/agents/contexts/references/` (D97-L/D98-L). Generated references project code-owned vocabulary; authored references carry irreducible reasoning heuristics. All are concise, load-on-demand, and eligible for packaging as agent-readable context. | +| **Prompt-resource manifest** | The small per-turn D58-L `` / resource-reference block injected into the system prompt, listing only Brunch-owned resources with `kind`, `name`, `description`, and `location`. The legal set and locations are code-owned in `agents/runtime/state.ts` (not filesystem-discovered); `name` and `description` are file-owned frontmatter or explicit metadata read over the explicit path list. The seed-context and `.pi/extensions/brunch-data/context/` context renderers are not manifest resources. It mirrors Pi's skill-list element structure but is filtered by Brunch operational mode and allow-lists, not by strategy/lens/method runtime selections. | +| **Method** | A tool-usage or workflow competence that may be documented as a Brunch prompt resource (`agents/skills/methods//SKILL.md`), e.g. structured exchanges, capture, generate proposals, project graph material, read context, mutate the graph, or review for gaps. D98-L suspends `method` as a product runtime axis; executable tool authority remains code-owned through operational-mode policy and active-tool gating. | | **Agent context** | The content the agent reasons over — `cwd`, `graph`, or `node` (D60-L): pulled (typed, read-only) from `graph/`/`session/`, optionally projected when a reusable DTO helps, rendered to LLM-string or JSON, surfaced pushed (compose) or pulled (`read_graph` / `read_workspace_context` / `read_session_context`). Graph context explicitly chooses graph-truth vs active-context reads and may filter by node kind, readiness band, edge category/direction, or absence of an edge category (gap query). Distinct from the **workspace projection** (`workspace.state`), which is product/UI state, not agent content. | | **Context-render house style** | The RENDER-stage convention (D83-L) for LLM-facing agent context: a markdown frame (md-pen) with uniform record sets as TOON (`@toon-format/toon`) and file hierarchy as a fenced ASCII tree (stringify-tree over Brunch's gitignore-aware walk), each top-level block wrapped in an XML-style `
` tag. Format follows reader legibility, not internal shape (prose where structure misleads). Agent context clusters into three scopes mirroring `workspace → spec → session` (D19-L): `` (project / documents / spec-roster, no sessions), `` (spec header / graph / ranked gaps / sessions), `` (runtime posture / mentions / transcript). It is the agent-context dialect within `agents/contexts/`; human-facing renders (print/evidence/debug) are local and do not use the `
` clustering. Distinct from the `workspace.state` product-state projection (D60-L). | | **Readiness estimate** | A soft, derived, live per-band coverage projection over `elicitation_gaps`, for UI surfacing only (D45-L). It is *not* stored, *not* authority, and gates nothing — it may regress honestly. Replaces the retired stored `readiness_grade`. | diff --git a/src/.pi/__tests__/runtime-axis-picker.harness.test.ts b/src/.pi/components/__tests__/runtime-posture-axis-picker.harness.test.ts similarity index 94% rename from src/.pi/__tests__/runtime-axis-picker.harness.test.ts rename to src/.pi/components/__tests__/runtime-posture-axis-picker.harness.test.ts index 73b084c27..d4b00a687 100644 --- a/src/.pi/__tests__/runtime-axis-picker.harness.test.ts +++ b/src/.pi/components/__tests__/runtime-posture-axis-picker.harness.test.ts @@ -1,10 +1,10 @@ import { TUI } from '@earendil-works/pi-tui'; import { describe, expect, it } from 'vitest'; -import { AGENT_STRATEGY_IDS, type AgentStrategySelection } from '../../session/schema/kinds.js'; -import { createRuntimeStrategyPickerComponent } from '../components/runtime-posture/axis-picker.js'; -import { createTestLabTheme } from './support/tui-theme.js'; -import { VirtualTerminal } from './support/virtual-terminal.js'; +import { AGENT_STRATEGY_IDS, type AgentStrategySelection } from '../../../session/schema/kinds.js'; +import { createTestLabTheme } from '../../__tests__/support/tui-theme.js'; +import { VirtualTerminal } from '../../__tests__/support/virtual-terminal.js'; +import { createRuntimeStrategyPickerComponent } from '../runtime-posture/axis-picker.js'; const theme = createTestLabTheme(); diff --git a/src/.pi/__tests__/runtime-axis-picker.test.ts b/src/.pi/components/__tests__/runtime-posture-axis-picker.test.ts similarity index 97% rename from src/.pi/__tests__/runtime-axis-picker.test.ts rename to src/.pi/components/__tests__/runtime-posture-axis-picker.test.ts index dc9dab998..86cfe36bc 100644 --- a/src/.pi/__tests__/runtime-axis-picker.test.ts +++ b/src/.pi/components/__tests__/runtime-posture-axis-picker.test.ts @@ -5,13 +5,13 @@ import { AGENT_STRATEGY_IDS, type AgentLensSelection, type AgentStrategySelection, -} from '../../session/schema/kinds.js'; +} from '../../../session/schema/kinds.js'; +import { createTestLabTheme } from '../../__tests__/support/tui-theme.js'; import { createRuntimeLensPickerComponent, createRuntimeModePickerComponent, createRuntimeStrategyPickerComponent, -} from '../components/runtime-posture/axis-picker.js'; -import { createTestLabTheme } from './support/tui-theme.js'; +} from '../runtime-posture/axis-picker.js'; const theme = createTestLabTheme(); diff --git a/src/.pi/__tests__/tui-lab-cycle.test.ts b/src/.pi/components/__tests__/tui-lab-cycle.test.ts similarity index 96% rename from src/.pi/__tests__/tui-lab-cycle.test.ts rename to src/.pi/components/__tests__/tui-lab-cycle.test.ts index 319a84172..a90c4868e 100644 --- a/src/.pi/__tests__/tui-lab-cycle.test.ts +++ b/src/.pi/components/__tests__/tui-lab-cycle.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from 'vitest'; +import { TuiStyleLabComponent } from '../../extensions/tui-lab/index.js'; import { DEMO_MODEL_SEGMENTS, nextSegmentIndex, @@ -8,8 +9,7 @@ import { renderSegmentTrack, trackVisibleWidth, type LabTheme, -} from '../components/tui-lab/index.js'; -import { TuiStyleLabComponent } from '../extensions/tui-lab/index.js'; +} from '../tui-lab/index.js'; const theme = createTheme(); diff --git a/src/.pi/__tests__/workspace-dialog-preflight.harness.test.ts b/src/.pi/components/__tests__/workspace-dialog-preflight.harness.test.ts similarity index 90% rename from src/.pi/__tests__/workspace-dialog-preflight.harness.test.ts rename to src/.pi/components/__tests__/workspace-dialog-preflight.harness.test.ts index ac98428e3..c41a0fd4c 100644 --- a/src/.pi/__tests__/workspace-dialog-preflight.harness.test.ts +++ b/src/.pi/components/__tests__/workspace-dialog-preflight.harness.test.ts @@ -3,9 +3,9 @@ import { describe, expect, it } from 'vitest'; import type { WorkspaceLaunchInventory, SpecSessionActivationDecision, -} from '../../session/workspace-session-coordinator.js'; -import { runWorkspaceDialogPreflight } from '../components/workspace-dialog/preflight.js'; -import { VirtualTerminal } from './support/virtual-terminal.js'; +} from '../../../session/workspace-session-coordinator.js'; +import { VirtualTerminal } from '../../__tests__/support/virtual-terminal.js'; +import { runWorkspaceDialogPreflight } from '../workspace-dialog/preflight.js'; describe('workspace dialog preflight harness', () => { it('renders the spec/session picker home screen and resolves on Enter', async () => { diff --git a/src/.pi/__tests__/workspace-dialog.test.ts b/src/.pi/components/__tests__/workspace-dialog.test.ts similarity index 97% rename from src/.pi/__tests__/workspace-dialog.test.ts rename to src/.pi/components/__tests__/workspace-dialog.test.ts index 5b05892cc..21cc6bc9e 100644 --- a/src/.pi/__tests__/workspace-dialog.test.ts +++ b/src/.pi/components/__tests__/workspace-dialog.test.ts @@ -3,14 +3,14 @@ import { readFile } from 'node:fs/promises'; import { type Terminal } from '@earendil-works/pi-tui'; import { describe, expect, it } from 'vitest'; -import type { WorkspaceLaunchInventory } from '../../session/workspace-session-coordinator.js'; -import { formatBrunchProductIdentity, readBrunchAnsiLogo } from '../components/brunch-identity.js'; +import type { WorkspaceLaunchInventory } from '../../../session/workspace-session-coordinator.js'; +import { formatBrunchProductIdentity, readBrunchAnsiLogo } from '../brunch-identity.js'; import { buildWorkspaceSelectionView, createWorkspaceDialogComponent, selectWorkspaceSelectionOption, runWorkspaceDialogPreflight, -} from '../components/workspace-dialog/index.js'; +} from '../workspace-dialog/index.js'; describe('spec/session picker', () => { it('builds a hierarchical spec/session selection home without per-spec top-level actions', () => { @@ -300,7 +300,7 @@ describe('spec/session picker', () => { }); it('provides deterministic shared Brunch identity primitives', async () => { - const assetUrl = new URL('../components/workspace-dialog/assets/', import.meta.url); + const assetUrl = new URL('../workspace-dialog/assets/', import.meta.url); expect(readBrunchAnsiLogo({ assetUrl, truecolor: false }).join('\n')).toContain('\x1B['); expect( @@ -339,7 +339,7 @@ describe('spec/session picker', () => { it('keeps logo assets colocated with the private picker component', async () => { const source = await readFile( - new URL('../components/workspace-dialog/assets/brunch-logo-quad-56x18.ansi', import.meta.url), + new URL('../workspace-dialog/assets/brunch-logo-quad-56x18.ansi', import.meta.url), 'utf8', ); @@ -348,7 +348,7 @@ describe('spec/session picker', () => { it('declares pi-tui as a direct dependency', async () => { const manifest = JSON.parse( - await readFile(new URL('../../../package.json', import.meta.url), 'utf8'), + await readFile(new URL('../../../../package.json', import.meta.url), 'utf8'), ) as { dependencies?: Record }; expect(manifest.dependencies).toHaveProperty('@earendil-works/pi-tui'); diff --git a/src/.pi/extensions/agent-runtime/runtime/authority-matrix.test.ts b/src/.pi/extensions/__tests__/agent-runtime-authority-matrix.test.ts similarity index 88% rename from src/.pi/extensions/agent-runtime/runtime/authority-matrix.test.ts rename to src/.pi/extensions/__tests__/agent-runtime-authority-matrix.test.ts index fed88bea5..7ee62cab3 100644 --- a/src/.pi/extensions/agent-runtime/runtime/authority-matrix.test.ts +++ b/src/.pi/extensions/__tests__/agent-runtime-authority-matrix.test.ts @@ -1,11 +1,14 @@ import type { ExtensionAPI } from '@earendil-works/pi-coding-agent'; import { describe, expect, it } from 'vitest'; -import { isToolBlockedForRuntimeState } from '../../../../agents/runtime/policy.js'; -import type { CommandResult } from '../../../../graph/command-executor.js'; -import { groundingFloorGaps } from '../../../../graph/schema/elicitation-gap-fixtures.js'; -import { DEFAULT_BRUNCH_AGENT_STATE } from '../../../../session/runtime-state.js'; -import { activeToolNamesForBrunchAgentState, projectBrunchAgentState } from './index.js'; +import { isToolBlockedForRuntimeState } from '../../../agents/runtime/policy.js'; +import type { CommandResult } from '../../../graph/command-executor.js'; +import { groundingFloorGaps } from '../../../graph/schema/elicitation-gap-fixtures.js'; +import { DEFAULT_BRUNCH_AGENT_STATE } from '../../../session/runtime-state.js'; +import { + activeToolNamesForBrunchAgentState, + projectBrunchAgentState, +} from '../agent-runtime/runtime/index.js'; const SIDE_EFFECTING_POC_TOOLS = ['bash', 'edit', 'write'] as const; const REGISTERED_POC_TOOLS = [ diff --git a/src/.pi/__tests__/operational-mode.test.ts b/src/.pi/extensions/__tests__/agent-runtime-runtime.test.ts similarity index 99% rename from src/.pi/__tests__/operational-mode.test.ts rename to src/.pi/extensions/__tests__/agent-runtime-runtime.test.ts index 3c0ec104d..3cc470eac 100644 --- a/src/.pi/__tests__/operational-mode.test.ts +++ b/src/.pi/extensions/__tests__/agent-runtime-runtime.test.ts @@ -16,7 +16,7 @@ import { registerBrunchOperationalModePolicy, type BrunchAgentState, type BrunchAgentStateEntryData, -} from '../extensions/agent-runtime/runtime/index.js'; +} from '../agent-runtime/runtime/index.js'; function runtimeEntry(state: BrunchAgentState, data: Record = {}) { return { diff --git a/src/.pi/__tests__/prompting.test.ts b/src/.pi/extensions/__tests__/agent-runtime-system-prompts.test.ts similarity index 96% rename from src/.pi/__tests__/prompting.test.ts rename to src/.pi/extensions/__tests__/agent-runtime-system-prompts.test.ts index feb110ef3..8ef117f62 100644 --- a/src/.pi/__tests__/prompting.test.ts +++ b/src/.pi/extensions/__tests__/agent-runtime-system-prompts.test.ts @@ -4,11 +4,11 @@ import { fileURLToPath } from 'node:url'; import { describe, expect, it } from 'vitest'; -import { composeAgentPrompt } from '../../agents/runtime/compose.js'; -import { createBrunchPiExtensions } from '../../app/pi-extensions.js'; -import { groundingFloorGaps } from '../../graph/schema/elicitation-gap-fixtures.js'; -import type { ElicitationGap } from '../../graph/schema/elicitation-gaps.js'; -import type { WorkspacePostureState } from '../../session/workspace-session-coordinator.js'; +import { composeAgentPrompt } from '../../../agents/runtime/compose.js'; +import { createBrunchPiExtensions } from '../../../app/pi-extensions.js'; +import { groundingFloorGaps } from '../../../graph/schema/elicitation-gap-fixtures.js'; +import type { ElicitationGap } from '../../../graph/schema/elicitation-gaps.js'; +import type { WorkspacePostureState } from '../../../session/workspace-session-coordinator.js'; import { BRUNCH_AGENT_RUNTIME_STATE_CUSTOM_TYPE, DEFAULT_BRUNCH_AGENT_STATE, @@ -17,11 +17,11 @@ import { type BrunchAgentState, type BrunchAgentStateEntryData, registerBrunchOperationalModePolicy, -} from '../extensions/agent-runtime/runtime/index.js'; -import { registerBrunchPrompting } from '../extensions/agent-runtime/system-prompts/index.js'; -import { BRUNCH_INTROSPECT_QUERY_TOOL } from '../extensions/dev-mode/introspect-query/index.js'; -import { createInMemoryBrunchIntrospectionStore } from '../extensions/dev-mode/introspection/index.js'; -import { BRUNCH_SESSION_QUERY_TOOL } from '../extensions/dev-mode/session-query/index.js'; +} from '../agent-runtime/runtime/index.js'; +import { registerBrunchPrompting } from '../agent-runtime/system-prompts/index.js'; +import { BRUNCH_INTROSPECT_QUERY_TOOL } from '../dev-mode/introspect-query/index.js'; +import { createInMemoryBrunchIntrospectionStore } from '../dev-mode/introspection/index.js'; +import { BRUNCH_SESSION_QUERY_TOOL } from '../dev-mode/session-query/index.js'; function runtimeEntry(state: BrunchAgentState) { return { @@ -674,5 +674,5 @@ describe('Brunch prompt-pack topology', () => { }); function projectRoot(): string { - return dirname(dirname(dirname(dirname(fileURLToPath(import.meta.url))))); + return dirname(dirname(dirname(dirname(dirname(fileURLToPath(import.meta.url)))))); } diff --git a/src/.pi/__tests__/context-tools.test.ts b/src/.pi/extensions/__tests__/brunch-data-context.test.ts similarity index 97% rename from src/.pi/__tests__/context-tools.test.ts rename to src/.pi/extensions/__tests__/brunch-data-context.test.ts index ef7bae76d..907081045 100644 --- a/src/.pi/__tests__/context-tools.test.ts +++ b/src/.pi/extensions/__tests__/brunch-data-context.test.ts @@ -5,11 +5,11 @@ import { fileURLToPath } from 'node:url'; import { describe, expect, it } from 'vitest'; -import { createBrunchFauxHarness } from '../../dev/index.js'; -import { openWorkspaceCommandExecutor } from '../../graph/index.js'; -import { seedFixture, type SeedFixture } from '../../graph/seed-fixtures.js'; -import { createSessionBindingData, SESSION_BINDING_TYPE } from '../../session/session-binding.js'; -import { registerBrunchContext } from '../extensions/brunch-data/context/index.js'; +import { createBrunchFauxHarness } from '../../../dev/index.js'; +import { openWorkspaceCommandExecutor } from '../../../graph/index.js'; +import { seedFixture, type SeedFixture } from '../../../graph/seed-fixtures.js'; +import { createSessionBindingData, SESSION_BINDING_TYPE } from '../../../session/session-binding.js'; +import { registerBrunchContext } from '../brunch-data/context/index.js'; function collectContextTools() { const tools = new Map Promise }>(); @@ -347,7 +347,9 @@ describe('context tools', () => { }); async function loadFixture(slug: string, set = 'bilal-port'): Promise { - const fixturePath = fileURLToPath(new URL(`../../../.fixtures/seeds/${set}/${slug}.json`, import.meta.url)); + const fixturePath = fileURLToPath( + new URL(`../../../../.fixtures/seeds/${set}/${slug}.json`, import.meta.url), + ); return JSON.parse(await import('node:fs/promises').then(({ readFile }) => readFile(fixturePath, 'utf8'))); } diff --git a/src/.pi/extensions/brunch-data/elicitation/index.test.ts b/src/.pi/extensions/__tests__/brunch-data-elicitation.test.ts similarity index 96% rename from src/.pi/extensions/brunch-data/elicitation/index.test.ts rename to src/.pi/extensions/__tests__/brunch-data-elicitation.test.ts index 5b90bcd09..6bd156ca7 100644 --- a/src/.pi/extensions/brunch-data/elicitation/index.test.ts +++ b/src/.pi/extensions/__tests__/brunch-data-elicitation.test.ts @@ -1,16 +1,16 @@ import { eq } from 'drizzle-orm'; import { describe, expect, it } from 'vitest'; -import { createDb } from '../../../../db/connection.js'; -import * as schema from '../../../../db/schema.js'; -import { sortElicitationGapsForAsking } from '../../../../graph/elicitation-driver.js'; -import type { ElicitationGap } from '../../../../graph/index.js'; -import { CommandExecutor, getElicitationGaps } from '../../../../graph/index.js'; +import { createDb } from '../../../db/connection.js'; +import * as schema from '../../../db/schema.js'; +import { sortElicitationGapsForAsking } from '../../../graph/elicitation-driver.js'; +import type { ElicitationGap } from '../../../graph/index.js'; +import { CommandExecutor, getElicitationGaps } from '../../../graph/index.js'; import { READ_ELICITATION_GAPS_TOOL, registerBrunchElicitation, UPDATE_ELICITATION_GAPS_TOOL, -} from './index.js'; +} from '../brunch-data/elicitation/index.js'; function gap(overrides: Partial & { id: string }): ElicitationGap { return { diff --git a/src/.pi/__tests__/graph-tools.test.ts b/src/.pi/extensions/__tests__/brunch-data-graph.test.ts similarity index 92% rename from src/.pi/__tests__/graph-tools.test.ts rename to src/.pi/extensions/__tests__/brunch-data-graph.test.ts index ff806d998..fe97fb502 100644 --- a/src/.pi/__tests__/graph-tools.test.ts +++ b/src/.pi/extensions/__tests__/brunch-data-graph.test.ts @@ -1,10 +1,10 @@ import { Value } from 'typebox/value'; import { describe, expect, it } from 'vitest'; -import { formatMutateGraphResult } from '../../agents/contexts/graph/commit-result.js'; -import { formatGraphOverview } from '../../agents/contexts/graph/graph-slice.js'; -import { createDb, type BrunchDb } from '../../db/connection.js'; -import { CommandExecutor } from '../../graph/command-executor.js'; +import { formatMutateGraphResult } from '../../../agents/contexts/graph/commit-result.js'; +import { formatGraphOverview } from '../../../agents/contexts/graph/graph-slice.js'; +import { createDb, type BrunchDb } from '../../../db/connection.js'; +import { CommandExecutor } from '../../../graph/command-executor.js'; import { getElicitationGaps, getNodes, @@ -14,11 +14,11 @@ import { resolveGraphNodeCode, type GraphFilter, type GraphVisibility, -} from '../../graph/queries.js'; -import { READINESS_BANDS } from '../../graph/schema/kinds.js'; -import { translateMutateGraph } from '../extensions/brunch-data/graph/command-adapter.js'; -import { registerBrunchGraph, type GraphReaders } from '../extensions/brunch-data/graph/index.js'; -import { ReadGraphParams } from '../extensions/brunch-data/graph/tool-schemas.js'; +} from '../../../graph/queries.js'; +import { READINESS_BANDS } from '../../../graph/schema/kinds.js'; +import { translateMutateGraph } from '../brunch-data/graph/command-adapter.js'; +import { registerBrunchGraph, type GraphReaders } from '../brunch-data/graph/index.js'; +import { ReadGraphParams } from '../brunch-data/graph/tool-schemas.js'; let nextSpecSlug = 0; diff --git a/src/.pi/extensions/brunch-data/reconciliation/index.test.ts b/src/.pi/extensions/__tests__/brunch-data-reconciliation.test.ts similarity index 93% rename from src/.pi/extensions/brunch-data/reconciliation/index.test.ts rename to src/.pi/extensions/__tests__/brunch-data-reconciliation.test.ts index fa0415d51..1c34642cc 100644 --- a/src/.pi/extensions/brunch-data/reconciliation/index.test.ts +++ b/src/.pi/extensions/__tests__/brunch-data-reconciliation.test.ts @@ -1,21 +1,21 @@ import { eq } from 'drizzle-orm'; import { describe, expect, it } from 'vitest'; -import { activeToolNamesForPosture } from '../../../../agents/runtime/state.js'; -import { createDb } from '../../../../db/connection.js'; -import * as schema from '../../../../db/schema.js'; +import { activeToolNamesForPosture } from '../../../agents/runtime/state.js'; +import { createDb } from '../../../db/connection.js'; +import * as schema from '../../../db/schema.js'; import { CommandExecutor, getOpenReconciliationNeeds, type ReconciliationNeed, -} from '../../../../graph/index.js'; -import { groundingFloorGaps } from '../../../../graph/schema/elicitation-gap-fixtures.js'; -import { projectBrunchAgentState } from '../../agent-runtime/runtime/index.js'; +} from '../../../graph/index.js'; +import { groundingFloorGaps } from '../../../graph/schema/elicitation-gap-fixtures.js'; +import { projectBrunchAgentState } from '../agent-runtime/runtime/index.js'; import { READ_RECONCILIATION_NEEDS_TOOL, registerBrunchReconciliation, UPDATE_RECONCILIATION_NEEDS_TOOL, -} from './index.js'; +} from '../brunch-data/reconciliation/index.js'; interface ToolResult { content: Array<{ type: 'text'; text: string }>; diff --git a/src/.pi/__tests__/chrome.test.ts b/src/.pi/extensions/__tests__/chrome.test.ts similarity index 98% rename from src/.pi/__tests__/chrome.test.ts rename to src/.pi/extensions/__tests__/chrome.test.ts index 49c5a166e..5fd410f84 100644 --- a/src/.pi/__tests__/chrome.test.ts +++ b/src/.pi/extensions/__tests__/chrome.test.ts @@ -2,13 +2,13 @@ import type { ExtensionUIContext } from '@earendil-works/pi-coding-agent'; import { visibleWidth } from '@earendil-works/pi-tui'; import { describe, expect, it } from 'vitest'; -import type { WorkspaceSessionReadyState } from '../../session/workspace-session-coordinator.js'; -import { BrunchStartupHeader } from '../components/chrome-header.js'; +import type { WorkspaceSessionReadyState } from '../../../session/workspace-session-coordinator.js'; +import { BrunchStartupHeader } from '../../components/chrome-header.js'; import chromeExtension, { chromeStateForWorkspace, projectBrunchChromeFooterLines, renderBrunchChrome, -} from '../extensions/chrome/index.js'; +} from '../chrome/index.js'; describe('Brunch chrome projection', () => { it('uses activated session state instead of fabricating unbound', async () => { diff --git a/src/.pi/__tests__/runtime-switch-command.test.ts b/src/.pi/extensions/__tests__/commands-runtime-switch.test.ts similarity index 97% rename from src/.pi/__tests__/runtime-switch-command.test.ts rename to src/.pi/extensions/__tests__/commands-runtime-switch.test.ts index a53e2874e..3443cc281 100644 --- a/src/.pi/__tests__/runtime-switch-command.test.ts +++ b/src/.pi/extensions/__tests__/commands-runtime-switch.test.ts @@ -1,19 +1,19 @@ import { describe, expect, it } from 'vitest'; -import { groundingFloorGaps } from '../../graph/schema/elicitation-gap-fixtures.js'; -import { projectBrunchAgentState } from '../../projections/session/runtime-state.js'; +import { groundingFloorGaps } from '../../../graph/schema/elicitation-gap-fixtures.js'; +import { projectBrunchAgentState } from '../../../projections/session/runtime-state.js'; import { BRUNCH_AGENT_RUNTIME_STATE_CUSTOM_TYPE, DEFAULT_BRUNCH_AGENT_STATE, type BrunchAgentStateEntryData, -} from '../../session/runtime-state.js'; +} from '../../../session/runtime-state.js'; +import { createTestLabTheme } from '../../__tests__/support/tui-theme.js'; import { BRUNCH_LENS_COMMAND, BRUNCH_MODE_COMMAND, BRUNCH_STRATEGY_COMMAND, registerBrunchCommands, -} from '../extensions/commands/index.js'; -import { createTestLabTheme } from './support/tui-theme.js'; +} from '../commands/index.js'; interface RegisteredCommand { description?: string; diff --git a/src/.pi/extensions/dev-mode/introspect-query/index.test.ts b/src/.pi/extensions/__tests__/dev-mode-introspect-query.test.ts similarity index 98% rename from src/.pi/extensions/dev-mode/introspect-query/index.test.ts rename to src/.pi/extensions/__tests__/dev-mode-introspect-query.test.ts index a7ce86d4e..5ec91d4e5 100644 --- a/src/.pi/extensions/dev-mode/introspect-query/index.test.ts +++ b/src/.pi/extensions/__tests__/dev-mode-introspect-query.test.ts @@ -2,17 +2,17 @@ import { readFile } from 'node:fs/promises'; import { describe, expect, it } from 'vitest'; -import { - type BrunchIntrospectionStore, - createInMemoryBrunchIntrospectionStore, - registerBrunchIntrospection, -} from '../introspection/index.js'; import { BRUNCH_INTROSPECT_QUERY_TOOL, createBrunchIntrospectQueryTool, queryIntrospectionCaptures, registerBrunchIntrospectQuery, -} from './index.js'; +} from '../dev-mode/introspect-query/index.js'; +import { + type BrunchIntrospectionStore, + createInMemoryBrunchIntrospectionStore, + registerBrunchIntrospection, +} from '../dev-mode/introspection/index.js'; describe('brunch_introspect_query', () => { it('returns the latest capture and projects payload and baseOptions paths', () => { diff --git a/src/.pi/__tests__/introspection.test.ts b/src/.pi/extensions/__tests__/dev-mode-introspection.test.ts similarity index 97% rename from src/.pi/__tests__/introspection.test.ts rename to src/.pi/extensions/__tests__/dev-mode-introspection.test.ts index e8f4bf815..e6d0a683f 100644 --- a/src/.pi/__tests__/introspection.test.ts +++ b/src/.pi/extensions/__tests__/dev-mode-introspection.test.ts @@ -4,8 +4,8 @@ import { join } from 'node:path'; import { describe, expect, it } from 'vitest'; -import { createBrunchPiExtensions } from '../../app/pi-extensions.js'; -import { BRUNCH_INTROSPECT_QUERY_TOOL } from '../extensions/dev-mode/introspect-query/index.js'; +import { createBrunchPiExtensions } from '../../../app/pi-extensions.js'; +import { BRUNCH_INTROSPECT_QUERY_TOOL } from '../dev-mode/introspect-query/index.js'; import { appendEntryContentToDebugCache, appendOriginationRecordToDebugCache, @@ -13,8 +13,8 @@ import { createInMemoryBrunchIntrospectionStore, mirrorSystemPromptToDebugCache, registerBrunchIntrospection, -} from '../extensions/dev-mode/introspection/index.js'; -import { BRUNCH_SESSION_QUERY_TOOL } from '../extensions/dev-mode/session-query/index.js'; +} from '../dev-mode/introspection/index.js'; +import { BRUNCH_SESSION_QUERY_TOOL } from '../dev-mode/session-query/index.js'; interface FakeCommandContext { readonly ui: { notify(message: string, type?: 'info' | 'warning' | 'error'): void }; diff --git a/src/.pi/extensions/dev-mode/session-query/index.test.ts b/src/.pi/extensions/__tests__/dev-mode-session-query.test.ts similarity index 98% rename from src/.pi/extensions/dev-mode/session-query/index.test.ts rename to src/.pi/extensions/__tests__/dev-mode-session-query.test.ts index 0793d116d..cfb6d3b9e 100644 --- a/src/.pi/extensions/dev-mode/session-query/index.test.ts +++ b/src/.pi/extensions/__tests__/dev-mode-session-query.test.ts @@ -4,13 +4,13 @@ import { fauxAssistantMessage, fauxToolCall } from '@earendil-works/pi-ai'; import type { SessionEntry } from '@earendil-works/pi-coding-agent'; import { describe, expect, it } from 'vitest'; -import { createBrunchFauxHarness } from '../../../../dev/index.js'; +import { createBrunchFauxHarness } from '../../../dev/index.js'; import { BRUNCH_SESSION_QUERY_TOOL, createBrunchSessionQueryTool, querySessionBranch, registerBrunchSessionQuery, -} from './index.js'; +} from '../dev-mode/session-query/index.js'; const branch = [ messageEntry('u1', { role: 'user', content: 'show me the graph summary' }), diff --git a/src/.pi/__tests__/structured-exchange-boundaries.test.ts b/src/.pi/extensions/__tests__/exchanges-boundaries.test.ts similarity index 100% rename from src/.pi/__tests__/structured-exchange-boundaries.test.ts rename to src/.pi/extensions/__tests__/exchanges-boundaries.test.ts diff --git a/src/.pi/__tests__/structured-exchange-editor-envelope.test.ts b/src/.pi/extensions/__tests__/exchanges-editor-envelope.test.ts similarity index 91% rename from src/.pi/__tests__/structured-exchange-editor-envelope.test.ts rename to src/.pi/extensions/__tests__/exchanges-editor-envelope.test.ts index 895db53ad..0f5693014 100644 --- a/src/.pi/__tests__/structured-exchange-editor-envelope.test.ts +++ b/src/.pi/extensions/__tests__/exchanges-editor-envelope.test.ts @@ -1,11 +1,11 @@ import { describe, expect, it } from 'vitest'; -import { projectRequestChoices } from '../../projections/exchanges/request-choices.js'; -import { zRequestChoicesEditorEnvelope } from '../extensions/exchanges/schemas/index.js'; +import { projectRequestChoices } from '../../../projections/exchanges/request-choices.js'; +import { zRequestChoicesEditorEnvelope } from '../exchanges/schemas/index.js'; import { buildRequestChoicesEditorPrefill, parseRequestChoicesEditorResponse, -} from '../extensions/exchanges/shared/choices-editor.js'; +} from '../exchanges/shared/choices-editor.js'; describe('request_choices editor envelope', () => { it('round-trips prefill, edited response, parse, and projection through the one schema', () => { diff --git a/src/.pi/__tests__/structured-exchange-extension.test.ts b/src/.pi/extensions/__tests__/exchanges-extension.test.ts similarity index 98% rename from src/.pi/__tests__/structured-exchange-extension.test.ts rename to src/.pi/extensions/__tests__/exchanges-extension.test.ts index c16af61ff..1ead26171 100644 --- a/src/.pi/__tests__/structured-exchange-extension.test.ts +++ b/src/.pi/extensions/__tests__/exchanges-extension.test.ts @@ -5,7 +5,7 @@ import { PRESENT_QUESTION_TOOL, REQUEST_RESPONSE_TOOL, registerStructuredExchange, -} from '../extensions/exchanges/index.js'; +} from '../exchanges/index.js'; const ansiPattern = new RegExp(`${String.fromCharCode(27)}\\[[0-?]*[ -/]*[@-~]`, 'g'); diff --git a/src/.pi/__tests__/structured-exchange-present-request.test.ts b/src/.pi/extensions/__tests__/exchanges-present-request.test.ts similarity index 99% rename from src/.pi/__tests__/structured-exchange-present-request.test.ts rename to src/.pi/extensions/__tests__/exchanges-present-request.test.ts index 85050fba2..c1d73c340 100644 --- a/src/.pi/__tests__/structured-exchange-present-request.test.ts +++ b/src/.pi/extensions/__tests__/exchanges-present-request.test.ts @@ -1,19 +1,19 @@ import { describe, expect, it, vi } from 'vitest'; -import { createDb } from '../../db/connection.js'; -import { CommandExecutor } from '../../graph/command-executor.js'; +import { createDb } from '../../../db/connection.js'; +import { CommandExecutor } from '../../../graph/command-executor.js'; import { PRESENT_CANDIDATES_TOOL, PRESENT_QUESTION_TOOL, PRESENT_REVIEW_SET_TOOL, REQUEST_RESPONSE_TOOL, registerStructuredExchange, -} from '../extensions/exchanges/index.js'; +} from '../exchanges/index.js'; import { findIncompleteStructuredExchangePresents, isStructuredExchangePresentDetails, isStructuredExchangeRequestDetails, -} from '../extensions/exchanges/shared/recovery.js'; +} from '../exchanges/shared/recovery.js'; interface ToolTextContent { type: 'text'; diff --git a/src/.pi/__tests__/structured-exchange-schemas.test.ts b/src/.pi/extensions/__tests__/exchanges-schemas.test.ts similarity index 99% rename from src/.pi/__tests__/structured-exchange-schemas.test.ts rename to src/.pi/extensions/__tests__/exchanges-schemas.test.ts index b6cc94862..0c02c8330 100644 --- a/src/.pi/__tests__/structured-exchange-schemas.test.ts +++ b/src/.pi/extensions/__tests__/exchanges-schemas.test.ts @@ -30,7 +30,7 @@ import { zRequestReviewDetails, zRequestToolMeta, zRequestResponseParams, -} from '../extensions/exchanges/schemas/index.js'; +} from '../exchanges/schemas/index.js'; function expectJsonSchemaExport(schema: z.ZodType) { expect(() => z.toJSONSchema(schema, { unrepresentable: 'throw' })).not.toThrow(); diff --git a/src/.pi/__tests__/mention-autocomplete.test.ts b/src/.pi/extensions/__tests__/mentions.test.ts similarity index 98% rename from src/.pi/__tests__/mention-autocomplete.test.ts rename to src/.pi/extensions/__tests__/mentions.test.ts index c36d24a41..4b90c6f33 100644 --- a/src/.pi/__tests__/mention-autocomplete.test.ts +++ b/src/.pi/extensions/__tests__/mentions.test.ts @@ -5,7 +5,7 @@ import { extractHashPrefix, registerBrunchMentionAutocomplete, type GraphMentionSource, -} from '../extensions/mentions/index.js'; +} from '../mentions/index.js'; describe('Brunch mention autocomplete', () => { it('adds graph mention prompt guidance', async () => { diff --git a/src/.pi/__tests__/extension-registry.test.ts b/src/.pi/extensions/__tests__/registry.test.ts similarity index 95% rename from src/.pi/__tests__/extension-registry.test.ts rename to src/.pi/extensions/__tests__/registry.test.ts index 3c4d40ea8..d0637c1ed 100644 --- a/src/.pi/__tests__/extension-registry.test.ts +++ b/src/.pi/extensions/__tests__/registry.test.ts @@ -4,30 +4,30 @@ import { fileURLToPath } from 'node:url'; import { describe, expect, it } from 'vitest'; -import { createBrunchPiExtensions } from '../../app/pi-extensions.js'; -import { registerBrunchAlternatives as alternatives } from '../components/alternatives.js'; -import { BRUNCH_ORCHESTRATOR_STUB_TOOL } from '../extensions/agent-runtime/orchestrator-stub/index.js'; -import { registerBrunchOperationalModePolicy as operationalMode } from '../extensions/agent-runtime/runtime/index.js'; -import { registerBrunchPrompting as prompting } from '../extensions/agent-runtime/system-prompts/index.js'; -import { registerBrunchContext as context } from '../extensions/brunch-data/context/index.js'; -import chrome from '../extensions/chrome/index.js'; +import { createBrunchPiExtensions } from '../../../app/pi-extensions.js'; +import { registerBrunchAlternatives as alternatives } from '../../components/alternatives.js'; +import { BRUNCH_ORCHESTRATOR_STUB_TOOL } from '../agent-runtime/orchestrator-stub/index.js'; +import { registerBrunchOperationalModePolicy as operationalMode } from '../agent-runtime/runtime/index.js'; +import { registerBrunchPrompting as prompting } from '../agent-runtime/system-prompts/index.js'; +import { registerBrunchContext as context } from '../brunch-data/context/index.js'; +import chrome from '../chrome/index.js'; import { BRUNCH_LENS_COMMAND, BRUNCH_MODE_COMMAND, BRUNCH_STRATEGY_COMMAND, BRUNCH_SWITCH_COMMAND, registerBrunchCommands as commands, -} from '../extensions/commands/index.js'; -import { registerBrunchBranchPolicyHandlers as commandPolicy } from '../extensions/commands/policy.js'; +} from '../commands/index.js'; +import { registerBrunchBranchPolicyHandlers as commandPolicy } from '../commands/policy.js'; import { PRESENT_CANDIDATES_TOOL, PRESENT_QUESTION_TOOL, PRESENT_REVIEW_SET_TOOL, REQUEST_RESPONSE_TOOL, registerStructuredExchange as structuredExchange, -} from '../extensions/exchanges/index.js'; -import { registerBrunchMentionAutocomplete as mentionAutocomplete } from '../extensions/mentions/index.js'; -import { registerBrunchSessionBoundary as sessionLifecycle } from '../extensions/session-hooks/session/lifecycle.js'; +} from '../exchanges/index.js'; +import { registerBrunchMentionAutocomplete as mentionAutocomplete } from '../mentions/index.js'; +import { registerBrunchSessionBoundary as sessionLifecycle } from '../session-hooks/session/lifecycle.js'; const extensionDefaults = { 'components/alternatives.ts': alternatives, @@ -531,5 +531,5 @@ async function fileExists(file: string): Promise { } function projectRoot(): string { - return dirname(dirname(dirname(dirname(fileURLToPath(import.meta.url))))); + return dirname(dirname(dirname(dirname(dirname(fileURLToPath(import.meta.url)))))); } diff --git a/src/.pi/extensions/session-hooks/session/lifecycle.test.ts b/src/.pi/extensions/__tests__/session-hooks-lifecycle.test.ts similarity index 98% rename from src/.pi/extensions/session-hooks/session/lifecycle.test.ts rename to src/.pi/extensions/__tests__/session-hooks-lifecycle.test.ts index de1b5845d..5fbc7f9f3 100644 --- a/src/.pi/extensions/session-hooks/session/lifecycle.test.ts +++ b/src/.pi/extensions/__tests__/session-hooks-lifecycle.test.ts @@ -5,7 +5,7 @@ import { registerBrunchSessionBoundary, runBrunchSessionBoundaryPipeline, type BrunchSessionBoundaryPipelineStep, -} from './lifecycle.js'; +} from '../session-hooks/session/lifecycle.js'; describe('Brunch session-boundary lifecycle', () => { it('runs workspace rebinding and continuity steps through one ordered boundary pipeline', async () => { diff --git a/src/.pi/extensions/subagents/subagents.test.ts b/src/.pi/extensions/__tests__/subagents.test.ts similarity index 99% rename from src/.pi/extensions/subagents/subagents.test.ts rename to src/.pi/extensions/__tests__/subagents.test.ts index 96cb461fb..ecad1e6f8 100644 --- a/src/.pi/extensions/subagents/subagents.test.ts +++ b/src/.pi/extensions/__tests__/subagents.test.ts @@ -30,14 +30,14 @@ import { parseSubagentMarkdown, subagentAgentsDir, type SubagentDefinition, -} from './agents.js'; -import { loadSubagentConfig, parseSubagentConfig, subagentConfigPath } from './config.js'; +} from '../subagents/agents.js'; +import { loadSubagentConfig, parseSubagentConfig, subagentConfigPath } from '../subagents/config.js'; import { BRUNCH_SUBAGENT_TOOL, createSemaphore, registerBrunchSubagents, type BrunchSubagentsDeps, -} from './index.js'; +} from '../subagents/index.js'; import { createSubagentToolCatalog, planSubagentTools, @@ -46,7 +46,7 @@ import { type SubagentResult, type SubagentRunContext, type SubagentSealedDeps, -} from './session.js'; +} from '../subagents/session.js'; const EXPLORER_MD = `--- name: explorer diff --git a/src/.pi/__tests__/tui-lab-style.test.ts b/src/.pi/extensions/__tests__/tui-lab.test.ts similarity index 96% rename from src/.pi/__tests__/tui-lab-style.test.ts rename to src/.pi/extensions/__tests__/tui-lab.test.ts index d926597e9..a2ab7fff5 100644 --- a/src/.pi/__tests__/tui-lab-style.test.ts +++ b/src/.pi/extensions/__tests__/tui-lab.test.ts @@ -1,14 +1,14 @@ import { visibleWidth } from '@earendil-works/pi-tui'; import { describe, expect, it } from 'vitest'; -import { createBrunchPiExtensions } from '../../app/pi-extensions.js'; +import { createBrunchPiExtensions } from '../../../app/pi-extensions.js'; import { lineVisibleWidths, makeSolidBadge, renderStylePalettePreview, type LabTheme, -} from '../components/tui-lab/index.js'; -import { BRUNCH_TUI_STYLE_LAB_COMMAND, registerBrunchTuiLab } from '../extensions/tui-lab/index.js'; +} from '../../components/tui-lab/index.js'; +import { BRUNCH_TUI_STYLE_LAB_COMMAND, registerBrunchTuiLab } from '../tui-lab/index.js'; const theme = createTheme(); diff --git a/src/.pi/extensions/web-tools/web/web-tools.test.ts b/src/.pi/extensions/__tests__/web-tools.test.ts similarity index 99% rename from src/.pi/extensions/web-tools/web/web-tools.test.ts rename to src/.pi/extensions/__tests__/web-tools.test.ts index 43d5b3812..bd5e583ce 100644 --- a/src/.pi/extensions/web-tools/web/web-tools.test.ts +++ b/src/.pi/extensions/__tests__/web-tools.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from 'vitest'; -import { registerBrunchWebTools } from './index.js'; +import { registerBrunchWebTools } from '../web-tools/web/index.js'; interface RegisteredTool { name: string; From 48bd64f456f62e2c7aadb720179f6e67683d5ab5 Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Fri, 26 Jun 2026 13:45:41 +0200 Subject: [PATCH 43/54] first move for generative src/agents/contexts/references/ --- memory/PLAN.md | 28 +++++----- memory/SPEC.md | 34 ++++++------ package.json | 2 +- src/agents/contexts/README.md | 5 +- .../contexts/references/graph-ontology.md} | 13 ++--- src/agents/docs/README.md | 20 +++++++ src/agents/skills/README.md | 2 +- .../skills/__tests__/prompt-resources.test.ts | 13 ++++- src/agents/skills/methods/capture/SKILL.md | 2 +- src/graph/README.md | 16 +++--- .../__tests__/generate-ontology-ref.test.ts | 14 +++++ src/graph/schema/generate-ontology-ref.ts | 52 ++++++++----------- src/treedocs.yaml | 6 ++- 13 files changed, 123 insertions(+), 84 deletions(-) rename src/{graph/schema/_generated/ontology.md => agents/contexts/references/graph-ontology.md} (76%) create mode 100644 src/agents/docs/README.md diff --git a/memory/PLAN.md b/memory/PLAN.md index 69c5a2b15..ab3f6bee7 100644 --- a/memory/PLAN.md +++ b/memory/PLAN.md @@ -80,9 +80,9 @@ context-pipeline/ ### Next -- `orchestrator-tool-port` (FE-1087) — **scoped.** Port the external `brunch cook` orchestrator into execute-mode tools without granting the foreground orchestrator direct shell/file-write authority. First active scope: `memory/cards/orchestrator-tool-port--plan-check-tool.md`. +- `orchestrator-tool-port` (FE-1087) — **scoped but D98-sensitive.** Port the external `brunch cook` orchestrator into the future CODE/executor tool surface rather than preserving a separate execute/orchestrator product mode. First active scope: `memory/cards/orchestrator-tool-port--plan-check-tool.md`; reconcile that scope against D98-L before build. - `elicitor-project` (FE-1085) — **design-gated.** Cross-plane derivation (requirements -> design, design -> oracles) remains undesigned under A33-L; run `ln-design` before any scope/build. -- `data-model-legibility` — **active.** Single canonical home for data-model meta-guidance, with closed-vocabulary tables generated from the typed `graph/schema` sources (D97-L). Design verdict landed (Shape C); first tracer landed (generated kind→band table + `check:data-model` drift guard, cited by `methods/capture`). Remaining: edge-category + detail-form tables, the authored judgment layer, and the subtypes→`detail` remodel. +- `data-model-legibility` — **active.** Single active frontier for the SPEC-mode data-model/reference substrate and pattern-establishment sweep (D97-L/D98-L): generated graph-ontology references, authored graph-authoring/checkability/projection guidance, skill citation/pruning, and stale-doc disposition under `src/agents/`. Design verdict landed (Shape C); first tracer landed and its topology correction moved the generated kind→band table + `check:data-model` drift guard into `src/agents/contexts/references/graph-ontology.md`, cited by `methods/capture`. Remaining rows are evaluated one at a time: generated edge-category + detail-form tables, authored judgment layer, and subtypes/checkability guidance. - `renderer-golden-coverage` — **active parallel coverage track.** Remaining RENDER work lives by audience: model-facing context surfaces under `agents/contexts/`, human/product text beside its app/session owner. Remaining rows need fresh scoping against `src/agents/contexts/README.md`, `src/app/README.md`, and `src/session/README.md`. - `exchange-symmetry-audit` — **earned cleanup.** Delete-oriented audit of the exchange projection/renderer split; not a capability blocker. @@ -107,19 +107,19 @@ context-pipeline/ ### orchestrator-tool-port -- **Name:** Port cook orchestrator into execute-mode tools +- **Name:** Port cook orchestration into CODE/executor tools - **Linear:** [FE-1087](https://linear.app/hash/issue/FE-1087/port-cook-orchestrator-into-execute-mode-tools) - **Branch:** tbd - **Kind:** structural / execute-mode tool boundary - **Status:** scoped; first scope file active. - **Certainty:** proving. - **Current execution pointer:** `memory/cards/orchestrator-tool-port--plan-check-tool.md`. -- **Objective:** Replace the execute-mode standup stub with real orchestrator tooling by porting reusable `brunch cook` core logic into product-owned modules and exposing it through thin `.pi/extensions` adapters, while preserving the orchestrator foreground agent's no-direct-`bash` / no-direct-`edit` / no-direct-`write` authority. +- **Objective:** Replace the old execute-mode standup stub direction with CODE/executor tooling by porting reusable `brunch cook` core logic into product-owned modules and exposing it through thin `.pi/extensions` adapters. D98-L changes the target agent from a separate no-write orchestrator to the Brunch-aware executor; the first read-only plan-check tool can still establish the tool seam, but the frontier must not preserve the old orchestrator/pi-coder split as product architecture. - **Acceptance:** - - First tracer replaces `orchestrator_stub` with a read-only `cook_plan_check` tool that validates a cook plan and returns typed plan shape/findings without creating a run sandbox. - - Later `cook_run` tooling is bounded behind orchestrator-owned sandbox/worktree machinery; write-capable worker sessions, if any, are code-owned child execution boundaries, not foreground-agent direct tools. + - First tracer replaces the old standup stub with a read-only `cook_plan_check` tool that validates a cook plan and returns typed plan shape/findings without creating a run sandbox. + - Later `cook_run` tooling is bounded behind executor-owned sandbox/worktree machinery; write-capable worker sessions, if any, are code-owned child execution boundaries. - External `../brunch` CLI behavior is ported as reusable product core plus Pi adapter, not wrapped as a shell command. -- **Traceability:** D39-L, D40-L, D90-L, D91-L, D92-L, D93-L / I49-L; `src/.pi/extensions/README.md`. +- **Traceability:** D39-L, D40-L, D90-L, D91-L, D92-L, D93-L, D98-L / I49-L; `src/.pi/extensions/README.md`. ### elicitor-project @@ -143,19 +143,19 @@ context-pipeline/ - **Linear:** tbd - **Branch:** tbd - **Kind:** structural / design + build -- **Status:** active; design verdict landed (`ln-design`: Shape C — two layers behind one index). First tracer-bullet **landed**: generated kind→band table at `src/graph/schema/_generated/ontology.md` + `check:data-model` drift guard (wired into `npm run check`), cited by `methods/capture`. Load-bearing claim 1 (typed `graph/schema` sources are the closed, importable vocabulary set — D73-L) validated by the landed generator. Remaining: edge-category + detail-form tables, the authored judgment layer (heuristics / promotion / checkability ladder / subtypes verdict), and the subtypes→`detail` remodel review. +- **Status:** active; design verdict landed (`ln-design`: Shape C — two layers behind one index). First tracer-bullet **landed and topology-corrected**: generated kind→band table at `src/agents/contexts/references/graph-ontology.md` + `check:data-model` drift guard (wired into `npm run check`), cited by `methods/capture`, with packaged runtime asset copy for `contexts/references/`. Load-bearing claim 1 (typed `graph/schema` sources are the closed, importable vocabulary set — D73-L) validated by the generator while `src/agents/contexts/references/` is now the runtime-eligible reference home. Remaining: edge-category + detail-form tables, the authored judgment layer (heuristics / promotion / checkability ladder / subtypes verdict), and the subtypes→`detail` remodel review. - **Certainty:** proving. - **Current execution pointer:** none active — re-scope the next slice (authored judgment layer, or further generated tables). -- **Objective:** Recover + reconcile the retired `INTENT_GRAPH_SEMANTICS` content into one canonical data-model meta-guidance home; generate the closed-vocabulary tables (planes / kinds / bands / edge-category policy / `detail` schemas) from the typed `graph/schema` sources (un-defers `_generated/`) so heuristics are **cited** (D97-L), not inlined and duplicated across skill bodies. +- **Objective:** Recover + reconcile the retired `INTENT_GRAPH_SEMANTICS` content and adjacent heuristic docs into one SPEC-mode data-model reasoning substrate under `src/agents/`: runtime-eligible references in `src/agents/contexts/references/`, backstage curation notes in `src/agents/docs/`, and pruned/cited skill bodies. Generate the closed-vocabulary tables (planes / kinds / bands / edge-category policy / `detail` schemas) from typed graph sources so heuristics are **cited** (D97-L), not inlined and duplicated across skill bodies; align the result with D98-L's mode-only runtime posture. - **Acceptance:** - ✓ `ln-design` produced ≥3 module shapes for the home + generation seam with a recommendation (Shape C), before any doc/script. - - The canonical-truth boundary is decided: what is generated from `kinds.ts` / `nodes.ts` / `category-policy.ts` vs authored judgment. (Direction set by Shape C; kind→band table materialized, remaining tables pending.) + - The canonical-truth boundary is decided: what is generated from `kinds.ts` / `nodes.ts` / `category-policy.ts` vs authored judgment. (Direction set by Shape C; kind→band table materialized in `src/agents/contexts/references/graph-ontology.md`, remaining tables pending.) - Subtypes/`detail` modelling review: each retired subtype family sorted into `kind` (behavior-bearing), `detail` facet (inert classification), or already-covered; decide whether an inert `detail` facet dimension earns its carrying cost given the kind/band/form machinery already discriminates. - The two capture gaps are explicitly ruled in or out: constraint/invariant subtype enums; the 8-rung checkability ladder + `strength`. - Skill bodies cite the new home (D97-L); inlined heuristic copies collapse to one cite-target. - ✓ A drift guard (`check:data-model`, mirroring `check:skills`, wired into `npm run check`) fails if the generated reference diverges from the typed sources. - If `ln-design` splits this into recover-doc / build-generator / subtypes-remodel frontiers, create a `data-model-legibility` arc per §Initiatives. -- **Traceability:** D73-L (domain owns vocabulary), D88-L (`detail` form union), D97-L (heuristic provenance); un-defers the `_generated/` deferral in [`src/agents/skills/README.md`](src/agents/skills/README.md); relates to `elicitor-project` (A33-L, shared D97-L rule). +- **Traceability:** D73-L (domain owns vocabulary), D88-L (`detail` form union), D97-L (heuristic provenance), D98-L (SPEC/CODE mode-only runtime posture); un-defers and relocates the generated-reference pattern into `src/agents/contexts/references/`; relates to `elicitor-project` (A33-L, shared D97-L rule). ### renderer-golden-coverage @@ -197,9 +197,9 @@ frontiers: depends_on: elicitor-generate, D95-L, D96-L, I51-L data-model-legibility - status: active (design landed Shape C; first tracer landed) - depends_on: graph/schema typed sources (kinds.ts, nodes.ts, category-policy.ts), D73-L, D88-L, D97-L - materialized: _generated/ontology.md (src/graph/schema) + check:data-model + status: active (design landed Shape C; first tracer landed and topology-corrected) + depends_on: graph/schema typed sources (kinds.ts, nodes.ts, category-policy.ts), D73-L, D88-L, D97-L, D98-L + materialized: src/agents/contexts/references/graph-ontology.md + check:data-model renderer-golden-coverage status: active parallel coverage diff --git a/memory/SPEC.md b/memory/SPEC.md index 7b5761879..2b28741c0 100644 --- a/memory/SPEC.md +++ b/memory/SPEC.md @@ -152,9 +152,9 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c - **D63-L — Graph `basis` records item-level approval strength, not the mutation pathway.** Accepted nodes and edges use `basis ∈ explicit | implicit`. `explicit` means the user directly stated the graph item or approved the exact node/edge in a review set; `implicit` means the user accepted a concept/proposal and the agent materialized specific graph items to match it without per-item review (the `propose-graph` direct-commit path). The mutation pathway lives in `change_log.operation` and payload (`mutate_graph`, `accept_review_set`, post-exchange capture, etc.), while epistemic attribution lives in `Node.source` and proposal UI metadata may still carry `epistemic_status`. Low-confidence inferred material is still not graph truth; it remains in preface/capture analysis/review drafts/reconciliation needs until clarified or accepted. More abstractly, `basis` is a *provenance-directness* marker — directly from the user (`explicit`) versus agent-materialized from user input (`implicit`) — of which item-level approval strength is the claim-flavored reading; this lets the same `explicit | implicit` distinction apply to non-claim registers such as `elicitation_gaps` (user-raised vs agent-inferred, D65-L). Depends on: D26-L, D27-L, D53-L, D54-L, D55-L. Supersedes: `basis = accepted_review_set` as a persisted graph enum value and any interpretation of `basis` as a provenance/path field. - **D64-L — Readiness bands are the coarse advisory coverage axis; D94-L materializes the current four-band derived model.** Bands are `grounding`, `elicitation`, `projection`, and `commitment`; they are non-exclusive node-kind groupings derived by `bandsForKind(kind)` in `src/graph/schema/nodes.ts`, not stored per-kind metadata and not structural legality gates. The derivation is `design` + `oracle` → `projection`, `plan` → `commitment`, and intent-plane kinds via a hand-maintained bisection: `goal`/`thesis` → `grounding`, `story`/`unknown`/`assumption`/`invariant`/`decision` → `elicitation`, `requirement`/`criterion` → `commitment`, `context` + `constraint` → dual `grounding` + `elicitation`, with `example`/`sketch`/`term` explicitly band-less. Two carriers must stay separate (I50-L): the asking agenda and soft readiness estimate read `gap.band`, while graph filters/rendering/thresholds read derived node band membership. Bands guide what the elicitor is trying to complete, what graph filters and rendered context show, the per-band **readiness estimate** rollup (D45-L), and which gaps a capability-readiness judgment weighs (D74-L). The `CommandExecutor` must not reject a clear later-band kind merely because of band; readiness controls objectives and capability judgment, not what graph truth may contain (I31-L). Depends on: D45-L, D56-L, D57-L, D59-L, D60-L, D65-L. Refined by: D94-L (four bands with two super-types; node band membership is derived from `plane` rather than declared per-kind; the asking-agenda reader reads `gap.band`, not the node-kind table). Supersedes: treating the intent `basic | structural | reasoning` category as the readiness taxonomy, treating readiness as a per-kind creation whitelist, treating bands as a grade rubric for a stored grade, or treating the earlier design doc's duplicated readiness table as the source of truth. - **D65-L — `elicitation_gaps` are typed coverage *obligations* (typologies) — the elicitor's prospective-memory agenda and the substrate of capability-readiness judgment; they guide and modulate, they never hard-gate.** Renamed and reconceived from `elicitation_backlog`. A gap is an obligation register entry, not domain content: the anti-shadowing line remains that the table holds obligation / disposition / meta only, never graph truth. Importance remains pre-answer weight and coverage remains post-answer derived strength; they must not collapse into one ambiguous field. `basis` still follows provenance-directness (D63-L), and `not_applicable` / `irrelevant` / `reopened` remain legitimate disposition semantics. The process-vs-domain split also remains: these are elicitation-process gaps, not domain-gap graph nodes, and an open grounding gap must never wall the candidate-proposal / disambiguation UX that fills it. Current materialized register shape, ownership, seeding, and predicate/disposition mechanics live in [`src/graph/README.md`](src/graph/README.md) and [`src/graph/schema/elicitation-gaps.ts`](src/graph/schema/elicitation-gaps.ts). Still open: whether the register eventually thins the `goal` axis (D59-L); capture-reflection writeback is now *designed* (D81-L: low-confidence noticings spawn gaps; the sweep closes answered gaps) with implementation pending in FE-861. Depends on: D8-L, D30-L, D45-L, D57-L, D59-L, D60-L, D63-L, D64-L, D74-L. Refined by: D75-L (gaps reference graph node kinds via `refersTo: NodeKind`; the parallel grounding-typology catalog and the closed gap-`name` enum are retired — substrate, predicate union, disposition, and anti-shadowing line are unchanged); D81-L (noticings-spawn-gaps is the committed capture-reflection writeback); D82-L (seeding gains the situating gap — orientation anchors routing acquisition modes). Supersedes: the `elicitation_backlog` name and its question-instance / `open | closed`-status model, treating `unknown` as a graph node kind, and any readiness-grade-projection-over-open-counts as authority. -- **D74-L — Capability-readiness is a just-in-time, capability-relative judgment over relevant gaps — it replaces the standing grade gate.** When a capability is requested (a generative lens, `propose-graph`, `project-graph`, commitment review, eventual export), the agent evaluates readiness *for that capability* against the `elicitation_gaps` (D65-L) declared relevant to it. The `capability → relevant gaps` map is **explicit** and subsumes the retired `STRATEGY_MIN_GRADE` / `GOAL_MIN_GRADE` / `LENS_MIN_GRADE` thresholds in `agents/runtime/policy.ts` plus the retired prompt-manifest/tool `METHOD_MIN_GRADE` thresholds in `agents/runtime/state.ts`, which were lossy grade-proxies for "enough grounding". Structurally-obvious relevant gaps (`presence` / `field` / `coverage`) are checked **mechanically** (cheap, no LLM); non-obvious (`manual`) ones consume an **LLM satisficiency judgment** (D57-L). The outcome is one of **proceed**, **proceed at low epistemic status** (density-scaled, D30-L), or **negotiate** — surface an `establishment_offer` ("I can, but answer X and Y first", D32-L). Explicit user/system pins stay legible when they are role/mode-legal; readiness negotiation narrows AUTO choices and gated methods/tools rather than erasing the pin or crashing prompt composition. Capability-readiness fires **on request, reactive-primary** (proactive nudges are a separate later concern) and is the **only readiness gate**: it never bars attempting work, it scales/negotiates. This resolves the prior "lens is never gated" (`ELICITATION_LENSES.md`) vs `LENS_MIN_GRADE` contradiction (lenses are not grade-gated; readiness is JIT-judged) and dissolves the grade-ratchet / two-value problem (the soft `readiness estimate`, D45-L, gates nothing and may regress honestly). A future structural milestone gate for export/plan/execute op-modes is deferred (D45-L) until such an op-mode exists. Depends on: D25-L, D26-L, D30-L, D32-L, D45-L, D57-L, D59-L, D65-L. Refined by: D75-L (the `capability → relevant gaps` map references node kinds, not a closed typology-name enum); D86-L (the "narrows … gated methods/tools" clause no longer applies to graph-write tools — `mutate_graph` and review-set tools are floor; readiness is advisory for them). Supersedes: `GRADE_RANK`-based `MIN_GRADE` hard gating of goal/strategy/lens/method prompt resources and method-coupled tools, and a standing readiness scalar as the authority for capability availability. +- **D74-L — Capability-readiness is a just-in-time, capability-relative judgment over relevant gaps — it replaces the standing grade gate.** When a capability is requested (a generative lens, `propose-graph`, `project-graph`, commitment review, eventual export), the agent evaluates readiness *for that capability* against the `elicitation_gaps` (D65-L) declared relevant to it. The `capability → relevant gaps` map is **explicit** and subsumes the retired `STRATEGY_MIN_GRADE` / `GOAL_MIN_GRADE` / `LENS_MIN_GRADE` thresholds in `agents/runtime/policy.ts` plus the retired prompt-manifest/tool `METHOD_MIN_GRADE` thresholds in `agents/runtime/state.ts`, which were lossy grade-proxies for "enough grounding". Structurally-obvious relevant gaps (`presence` / `field` / `coverage`) are checked **mechanically** (cheap, no LLM); non-obvious (`manual`) ones consume an **LLM satisficiency judgment** (D57-L). The outcome is one of **proceed**, **proceed at low epistemic status** (density-scaled, D30-L), or **negotiate** — surface an `establishment_offer` ("I can, but answer X and Y first", D32-L). Readiness negotiation changes epistemic posture and recommended next moves rather than crashing prompt composition or withholding graph truth. Capability-readiness fires **on request, reactive-primary** (proactive nudges are a separate later concern) and is the **only readiness gate**: it never bars attempting work, it scales/negotiates. This resolves the prior "lens is never gated" (`ELICITATION_LENSES.md`) vs `LENS_MIN_GRADE` contradiction (lenses are not grade-gated; readiness is JIT-judged) and dissolves the grade-ratchet / two-value problem (the soft `readiness estimate`, D45-L, gates nothing and may regress honestly). A future structural milestone gate for export/plan/CODE work is deferred (D45-L) until that product mode is implemented. Depends on: D25-L, D26-L, D30-L, D32-L, D45-L, D57-L, D59-L, D65-L. Refined by: D75-L (the `capability → relevant gaps` map references node kinds, not a closed typology-name enum); D86-L (the "narrows … gated methods/tools" clause no longer applies to graph-write tools — `mutate_graph` and review-set tools are floor; readiness is advisory for them). Supersedes: `GRADE_RANK`-based `MIN_GRADE` hard gating of goal/strategy/lens/method prompt resources and method-coupled tools, and a standing readiness scalar as the authority for capability availability. - **D75-L — `elicitation_gaps` reference graph node kinds; the parallel grounding-typology vocabulary is retired.** The commitment is architectural: Brunch has one closed ontology here (`NodeKind`), not a second closed grounding-typology vocabulary; gap naming must stay on the kind layer, while question phrasing remains open and situated. This retires the denormalized grounding catalog and the closed gap-name vocabulary, preserves the anti-shadowing line from D65-L, and keeps example question phrasing as priming rather than schema. Current node-kind-keyed gap shape, grounding-floor seeding, capability-readiness mapping, and priming catalogs live in [`src/graph/README.md`](src/graph/README.md), [`src/graph/schema/elicitation-gaps.ts`](src/graph/schema/elicitation-gaps.ts), [`src/projections/README.md`](src/projections/README.md), [`src/db/README.md`](src/db/README.md), and [`docs/design/ELICITATION_QUESTIONS.md`](docs/design/ELICITATION_QUESTIONS.md). Depends on: D54-L, D56-L, D57-L, D64-L, D65-L, D73-L, D74-L; A27-L (and validated A24-L). Refines: D30-L, D65-L, D74-L. Supersedes: the grounding typology catalog as a parallel closed gap vocabulary; the closed gap-`name` typology enum and the `RelevantGapName` union; and the retired refactor plan to enshrine `GROUNDING_GAP_TYPOLOGIES` as a canonical const. -- **D86-L — Capability-readiness never withholds a graph-write tool; `negotiate` is advisory, not a tool gate.** Readiness modulates: it scales epistemic status (D30-L), narrows AUTO *axis* menus (`strategy`/`lens`), and surfaces the `establishment_offer` — but it must never remove a graph-write tool from the active set. `mutate_graph` (direct commit) and the review-set tools (`present_review_set` / `request_response`) are **floor** capabilities in `elicit` mode whenever gaps exist; the agent always retains the means to commit graph truth and may proceed at low epistemic status when grounding is thin. This re-asserts I31-L ("readiness never bars graph truth or work") against the contrary reading. Motivating reductio: gating `mutate_graph` behind `propose-graph` readiness created a **bootstrap deadlock** — a fresh or foundation-light spec can never establish its `context`/`thesis`/`goal`/`constraint` frame, because the only tool that can write those nodes was gated on those nodes already existing (a developed but foundation-light spec such as `beta-commitments` was likewise unwritable). The `establishment_offer` is the correct *soft* mechanism; hard tool-withholding was over-anticipation (the same over-gating smell as a method withholding its own answer surface). Structural legality at the `CommandExecutor` (D64-L) is unchanged — illegal writes still fail loud; only the readiness-based *tool* withholding is removed. Implementation decouples the graph-write methods (`commit-graph`, `generate-proposal`) from the `propose-graph` / `project-graph` capability gate; current tool-legality and capability-readiness policy live in [`src/projections/README.md`](src/projections/README.md) (`session/capability-readiness`) and [`src/agents/runtime/README.md`](src/agents/runtime/README.md) (`policy.ts`, `state.ts`, `activeToolNamesForPosture`, `METHOD_CAPABILITY`, `METHOD_TOOL_NAMES`). Depends on: D30-L, D32-L, D74-L, D81-L, D85-L; I31-L. Refines: D74-L, D85-L. Supersedes: D85-L move 2's "the graph-write readiness gate lives on those method ids via capability-readiness"; the D74-L clause "readiness negotiation narrows … gated methods/tools" insofar as it withholds *graph-write* tools (AUTO axis-menu narrowing for `strategy`/`lens` is unaffected). +- **D86-L — Capability-readiness never withholds a graph-write tool; `negotiate` is advisory, not a tool gate.** Readiness modulates: it scales epistemic status (D30-L) and surfaces the `establishment_offer` — but it must never remove a graph-write tool from the active set. `mutate_graph` (direct commit) and the review-set tools (`present_review_set` / `request_response`) are **floor** capabilities in SPEC mode whenever gaps exist; the agent always retains the means to commit graph truth and may proceed at low epistemic status when grounding is thin. This re-asserts I31-L ("readiness never bars graph truth or work") against the contrary reading. Motivating reductio: gating `mutate_graph` behind `propose-graph` readiness created a **bootstrap deadlock** — a fresh or foundation-light spec can never establish its `context`/`thesis`/`goal`/`constraint` frame, because the only tool that can write those nodes was gated on those nodes already existing (a developed but foundation-light spec such as `beta-commitments` was likewise unwritable). The `establishment_offer` is the correct *soft* mechanism; hard tool-withholding was over-anticipation (the same over-gating smell as a method withholding its own answer surface). Structural legality at the `CommandExecutor` (D64-L) is unchanged — illegal writes still fail loud; only the readiness-based *tool* withholding is removed. Implementation decouples the graph-write methods (`commit-graph`, `generate-proposal`) from the `propose-graph` / `project-graph` capability gate; current tool-legality and capability-readiness policy live in [`src/projections/README.md`](src/projections/README.md) (`session/capability-readiness`) and [`src/agents/runtime/README.md`](src/agents/runtime/README.md) (`policy.ts`, `state.ts`, `activeToolNamesForPosture`, `METHOD_CAPABILITY`, `METHOD_TOOL_NAMES`). Depends on: D30-L, D32-L, D74-L, D81-L, D85-L; I31-L. Refines: D74-L, D85-L. Supersedes: D85-L move 2's "the graph-write readiness gate lives on those method ids via capability-readiness" and the D74-L clause "readiness negotiation narrows … gated methods/tools" insofar as it withholds graph-write tools or presumes runtime strategy/lens axes. - **D87-L — Multi-method ontology revision: methods are validation lenses, not sources of kinds; the locked kind set reopens once for a small batch.** The ontology must host BDD, EDD, and formal-spec/verification flows on one model, cheapest to establish now before change costs rise. The governing result — validated against BDD/Gherkin and formal verification in [`docs/design/ONTOLOGY_REVIEW_PROTOCOL.md`](docs/design/ONTOLOGY_REVIEW_PROTOCOL.md) — is the **closure rule**: a method = `spec.kind` (D89-L) + `detail.form` (D88-L) + a renderer + a heuristic-set; no method earns its own node/edge kind, and a method term with no clean mapping is a *finding about our model*, not a licence to add a kind. This reopens the D54-L/D56-L node lock and the D51-L edge set once, deliberately, for one batch (implemented in the FE-1052 frontier; the schema enums changed during that build and `GRAPH_MODEL.md` was retired): - **Edges 8 → 9** (renames preserve behavior incl. stance): `proof → witness`, `support → rationale`, `boundary → exclusion`, `association → cross_reference`; **add `refinement`** (generality → specificity; present reader is formal refinement, abstract model ⊑ concrete implementation, distinct from `realization`). `stance ∈ for | against` stays valid only on the renamed `witness`/`rationale`; a counterexample is `witness:against`. The *edge* `proof` becomes `witness` while the *node* `evidence` is unchanged (renaming the edge to `evidence` would collide with the node; the relation reads as a verb). @@ -283,7 +283,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c - **D76-L — Session continuity state is a projected assistant-visible watermark carried by transcript custom entries, never stored mutable state.** The architectural commitment is that session staleness is defined only by what the assistant has actually been shown, as a `{specId, lsn}` watermark projected from transcript-native continuity carriers; bare LSNs are invalid across sibling specs. `worldUpdate` remains a strict `current_lsn > watermark` reconciliation surface, own assistant-visible mutations must not be re-announced through `worldUpdate`, narrow reads must not advance the global watermark, continuity must persist through Brunch custom transcript entries rather than synthetic `toolCall`s or prompt-only injection, and any process-local cache is optimization only, never product state. Current carrier taxonomy and turn-boundary choreography live in [`src/session/README.md`](src/session/README.md), [`src/projections/README.md`](src/projections/README.md), [`src/projections/session/assistant-visible-watermark.ts`](src/projections/session/assistant-visible-watermark.ts), [`src/projections/session/continuity-entry-classifier.ts`](src/projections/session/continuity-entry-classifier.ts), [`src/session/prepare-next-turn.ts`](src/session/prepare-next-turn.ts), [`src/agents/contexts/seeds/origination.ts`](src/agents/contexts/seeds/origination.ts), and [`src/.pi/README.md`](src/.pi/README.md). Depends on: D14-L, D17-L, D37-L, D43-L, I1-L, I4-L. Supersedes: a stored/mutable `agent_visible_lsn` / `lastSeenLsn` field; defining the watermark as "the app sampled the graph clock" (which would let staleness vanish before the agent is shown the change); carrying continuity via synthetic tool calls or prompt-only injection. - **D77-L — Turn-boundary reconciliation is one writer seam plus two auxiliary seams and a guard, not four co-equal insertion points.** The write-side of continuity is owned by a single **pre-assistant-turn reconciler** (canonically `prepareNextTurn`; `before_agent_start` is the temporary adapter until that seam is wired): it computes the projected watermark, samples `current_lsn`, and inserts `worldUpdate` (naming only items with LSN strictly greater than the pre-update watermark, I4-L), mention-staleness hints, side-task/reviewer drains (D15-L), and any boot/resume seed or kick decision (D78-L). Two auxiliary seams write continuity outside that reconciler: **submit-time mention resolution** at user-message ingestion (`session.submitMessage`, D49-L) resolves `#` handles to stable graph ids and appends `brunch.mention` ledger facts — independent of autocomplete freshness, which is advisory UI only; and **tool-result watermark stamping** at the graph read/mutation adapters records the LSN at which a graph fact became assistant-visible — but only the session's own mutations and **whole-spec snapshot reads** (full graph overview) advance the **global** assistant-visible watermark (D76-L), while narrow `getNodes` / `queryNodes` reads update **per-entity read ledgers** (the D14-L mention ledger now; an optional direct-read ledger if later built) and must not touch the global watermark, so a narrow read cannot mask unrelated staleness. `before_provider_request` is a **guard only** (assert no stale unresolved continuity remains), never the normal writer, because writing there risks double-writes against the reconciler; on detecting post-prepare drift (a write landed between `prepareNextTurn` and the provider call) it **re-runs turn preparation once** (abort/retry) rather than patching the transcript itself. The reconciler must run **before prompt composition** so its inserted continuity is visible to the same turn. Depends on: D14-L, D15-L, D17-L, D49-L, D76-L. Supersedes: four co-equal insertion points each owning overlapping continuity writes; tying mention resolution to autocomplete-time state; using `before_provider_request` as a primary continuity writer. -- **D78-L — Session origination ("kick" + context seeding) is honest assistant-origination behind `session.triggerExchange`, gated by transcript-tail policy, never a fabricated user turn.** The architectural commitment is the guardrail: origination is a product seam that seeds context and decides whether the assistant owes a turn, but it must never fabricate a user entry or a deterministic product-authored `present_*` offer. New-session seeding, full-seed payload composition, kick triggering, resume-debt classification, continuity-only-tail ignoring, and the public kick surface live in [`src/session/README.md`](src/session/README.md), [`src/agents/contexts/seeds/origination.ts`](src/agents/contexts/seeds/origination.ts), [`src/session/originate-assistant-turn.ts`](src/session/originate-assistant-turn.ts), [`src/session/start-assistant-turn.ts`](src/session/start-assistant-turn.ts), [`src/projections/session/continuity-entry-classifier.ts`](src/projections/session/continuity-entry-classifier.ts), [`src/rpc/README.md`](src/rpc/README.md), and [`src/rpc/methods/session.ts`](src/rpc/methods/session.ts). **(Revised 2026-06-12, `origination-native-elicitation`):** the product never fabricates a deterministic `present_*` exchange at origination — the canned offer predates the `elicitation_gaps` mechanism and is superseded by it; its pragmatic ground (new-from-scratch / existing-codebase / relates-to-prior-spec situating) migrates into elicitor prompt guidance. The deterministic exchange generator survives only as probe/dev machinery for the R24 permutation evidence, outside product origination. The "kick unless freestyle" gate maps onto D66-L: because AUTO never selects `freestyle`, AUTO always originates offer-first, and only an explicit `freestyle` pin yields a wait-for-user idle. This is **product behavior on the non-D39-L-seal side**, not a `BRUNCH_DEV` affordance. Context seeding for new specs may draw on the `elicitation_gaps` grounding floor (D75-L) to shape the opening offer, but the seeded overview itself is read context, not graph truth. Depends on: D12-L, D37-L, D49-L, D66-L, D75-L, D76-L; R16. Supersedes: faking a user message to start the agent; treating "originate the first turn" as a dev-harness concern; an unconditional resume-kick that re-asks when the tail already awaits the user; **the product-fabricated deterministic `present_*` offer at origination and its LSN-only seed stamp (2026-06-12: superseded by the elicitation-gaps-grounded assistant-authored opening over a content-rich seed)**. +- **D78-L — Session origination ("kick" + context seeding) is honest assistant-origination behind `session.triggerExchange`, gated by transcript-tail policy, never a fabricated user turn.** The architectural commitment is the guardrail: origination is a product seam that seeds context and decides whether the assistant owes a turn, but it must never fabricate a user entry or a deterministic product-authored `present_*` offer. New-session seeding, full-seed payload composition, kick triggering, resume-debt classification, continuity-only-tail ignoring, and the public kick surface live in [`src/session/README.md`](src/session/README.md), [`src/agents/contexts/seeds/origination.ts`](src/agents/contexts/seeds/origination.ts), [`src/session/originate-assistant-turn.ts`](src/session/originate-assistant-turn.ts), [`src/session/start-assistant-turn.ts`](src/session/start-assistant-turn.ts), [`src/projections/session/continuity-entry-classifier.ts`](src/projections/session/continuity-entry-classifier.ts), [`src/rpc/README.md`](src/rpc/README.md), and [`src/rpc/methods/session.ts`](src/rpc/methods/session.ts). **(Revised 2026-06-12, `origination-native-elicitation`):** the product never fabricates a deterministic `present_*` exchange at origination — the canned offer predates the `elicitation_gaps` mechanism and is superseded by it; its pragmatic ground (new-from-scratch / existing-codebase / relates-to-prior-spec situating) migrates into elicitor prompt guidance. The deterministic exchange generator survives only as probe/dev machinery for the R24 permutation evidence, outside product origination. D98-L supersedes the old "kick unless freestyle" runtime-axis gate: SPEC mode stays offer-first by default, while structure-optional user turns are allowed as ordinary SPEC-mode input rather than through an explicit `freestyle` pin. This is **product behavior on the non-D39-L-seal side**, not a `BRUNCH_DEV` affordance. Context seeding for new specs may draw on the `elicitation_gaps` grounding floor (D75-L) to shape the opening offer, but the seeded overview itself is read context, not graph truth. Depends on: D12-L, D37-L, D49-L, D66-L, D75-L, D76-L; R16. Supersedes: faking a user message to start the agent; treating "originate the first turn" as a dev-harness concern; an unconditional resume-kick that re-asks when the tail already awaits the user; **the product-fabricated deterministic `present_*` offer at origination and its LSN-only seed stamp (2026-06-12: superseded by the elicitation-gaps-grounded assistant-authored opening over a content-rich seed)**. #### Development experience (DX) @@ -293,7 +293,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c - **D70-L — `.fixtures/` is a four-role tree (seeds / workbenches / runs / scratch); dev-loop artifacts decouple operating-cwd from artifact-root.** `.fixtures/` separates four lifecycles, each with its own git policy: **`seeds/`** — tracked, reusable explicit-basis starting truth consumed by the seed loader (INPUT), never local runtime DB state; **`workbenches/`** — launchable Brunch workspaces whose `.brunch/` is gitignored local state (the directories a dev `--cwd` targets, D71-L); **`runs/`** — tracked, *curated/promoted* probe evidence under `//`, probe-first per D68-L (EVIDENCE); **`scratch/`** — gitignored, ephemeral live dev-loop output under `//` (SCRATCH). Dev launchers (faux/introspection) must resolve their artifact root to the package-relative repo `.fixtures/scratch/`, **not** to the operating `cwd` — the same operating-cwd-vs-`fixtureRoot` decoupling the probe layer already uses (`mkdtemp` ephemeral cwd + repo-resolved `fixtureRoot`). This removes the `join(cwd, '.fixtures', …)` nesting defect where launching against a workbench would write `/.fixtures/…`. An exploratory scratch run becomes durable evidence only by explicit promotion (move `scratch///` → `runs///`, then track it), keeping curated `runs/` clean. `.fixtures/scratch/` is the chosen scratch home (over reusing `tmp/`) so promotion is a move within one tree. Depends on: D52-L, D68-L; the probe/transcript model. Supersedes: pinning dev-run artifacts to the operating cwd; treating all `.fixtures/runs/` output as tracked evidence; leaving the `workbenches/` role undocumented. - **D71-L — One `BRUNCH_DEV` switch gates all dev affordances; the main CLI accepts `--cwd`; introspection is present-but-dead in prod.** The over-specific `BRUNCH_DEV_RPC` env var is generalized to a single `BRUNCH_DEV` switch that, when set, enables dev affordances together: dev RPC methods (`dev.*`), registration of the read-only introspection extension (D69-L), and routing of dev-loop artifacts to `.fixtures/scratch/` (D70-L). `runBrunchCli` parses a `--cwd ` flag (defaulting to `process.cwd()`) so a dev session can target a `.fixtures/workbenches/` workspace without `cd`. Two independent prod-safety gates hold: (1) `src/dev/**` is build-excluded by `tsconfig.build.json`, so launchers/harness/alias never ship; (2) the introspection extension, though compiled into `dist` under `src/.pi/`, only *registers* when `createBrunchPiExtensions(..., { introspection: { enabled } })` opts in — and the TUI call site sets `enabled` from `BRUNCH_DEV` only, so absent the switch it is present-but-dead, never wired, honoring D39-L explicit-opt-in sealing (no ambient discovery). Brunch-launched TUI sessions keep Pi startup update suppression on in both product and `BRUNCH_DEV` runs by scoping `PI_OFFLINE=1` through `InteractiveMode.run()` unless the user already set a value; prior `PI_OFFLINE` / `PI_SKIP_VERSION_CHECK` state is restored in `finally`, never as a leaked global `process.env` mutation. Depends on: D39-L, D67-L, D68-L, D69-L, D70-L. Supersedes: the `BRUNCH_DEV_RPC`-only dev gate; relying on the operating cwd to choose the dev workspace; the assumption that the introspection extension needs build-exclusion (runtime opt-in suffices); lifting Pi offline mode in `BRUNCH_DEV` TUI sessions merely to enable live-provider behavior. - **D79-L — Dev DB seeding is explicit, selected, and target-workspace-scoped; `npm run dev` never implies a seed.** A Brunch workspace DB is local runtime state under that launch cwd's `.brunch/`; running `npm run dev` against the repo root or a workbench may create/open that workspace, but it must not silently load reusable seed fixtures. Reusable graph seeds under `.fixtures/seeds//.json` are loaded only by an explicit seed command that names the target workspace and the seed set/slug (or an explicitly requested all-seeds batch); the loader remains a graph-domain utility over `seedFixture`/`CommandExecutor`, so seeded specs get normal `create_spec`/`mutate_graph` change-log entries, spec-local LSNs, elicitation-gap seeding, and structural validation. Workbenches under `.fixtures/workbenches//` are launchable cwd containers, not seed truth: their `.brunch/` may be reset or re-seeded locally, but tracked files must document which seed(s) a human or script should apply. Captured or newly-authored seed JSON is parked until it has at least one named consumer disposition (`test`, `preview`, `manual workbench`, `probe input`, or `parked`); existence under `seeds/` alone does not make it part of the default dev database. Depends on: D16-L, D20-L, D52-L, D70-L, D71-L. Supersedes: the catch-all `npm run seed` mental model that loads every seed into the current shell cwd; treating the repo-root `.brunch/` as canonical dev fixture state; auto-seeding because a dev host starts. -- **D59-L — `goal` is a readiness-derived, AUTO-able objective axis, distinct from strategy.** A *goal* is what the session agent currently pursues; a *strategy* is the reusable interaction shape used to pursue it — a goal is pursued *via* a strategy *through* a lens (three orthogonal axes). The goal set is derived from readiness-band coverage (D64-L) rather than a stored grade: `grounding-advance` (fill grounding gaps and raise grounding coverage), `elicit-expand` (expand the elicited specification graph while ambiguity remains productive), `commit-converge` (reduce / lock down reviewable commitments), plus an always-on `capture-posture` (capture or confirm dev `posture`, D45-L). `goal` defaults to the readiness-derived objective (e.g. while grounding coverage is thin, `grounding-advance`), may be pinned, or left `AUTO`; in either case D58-L manifests advertise the legal resource(s) rather than injecting the whole goal body. For now `goal` is **internal/readiness-derived and not part of the user posture-change surface** (it is too contingent to expose as a user-mutable axis); the pin affordance is reserved for system/internal logic, and unlike `strategy`/`lens` the user does not switch it (D40-L, Q4). `elicit-expand` and `commit-converge` intentionally form the diverge/converge pair for the elicitation diamond; `elicit-I` / `elicit-II` are retired because they were phase-like labels, not objectives. "Advance grounding" is a goal, not a strategy — though the `grounding-advance` goal may carry a dedicated default interaction pattern. Depends on: D45-L, D57-L, D58-L, D64-L. Refined by: D85-L (`goal` is dropped as a manifest/runtime axis; the four postures inline into the `elicitor` role prompt, agent-selected by band — goal was internal/readiness-derived anyway). Supersedes: conflating the elicit-lifecycle objective with strategy selection, and deriving the goal set from a stored readiness grade. +- **D59-L — Suspended/retired: `goal` is not a runtime objective axis.** The earlier model treated a *goal* as what the session agent pursues via a strategy through a lens. D85-L first inlined the useful objective postures into the elicitor role prompt; D98-L now suspends the broader runtime-axis model. The useful residue is prompt guidance derived from readiness-band coverage (D64-L) rather than a stored grade: `grounding-advance` (fill grounding gaps and raise grounding coverage), `elicit-expand` (expand the elicited specification graph while ambiguity remains productive), `commit-converge` (reduce / lock down reviewable commitments), plus an always-on `capture-posture` (capture or confirm dev `posture`, D45-L). These are not pinned, AUTO-selected, or persisted; the SPEC-mode elicitor chooses its next move from pushed context, graph/gap state, and loaded references. `elicit-expand` and `commit-converge` intentionally form the diverge/converge pair for the elicitation diamond; `elicit-I` / `elicit-II` are retired because they were phase-like labels, not objectives. Depends on: D45-L, D57-L, D58-L, D64-L, D98-L. Refined by: D85-L and D98-L. Supersedes: conflating the elicit lifecycle objective with strategy selection, deriving the goal set from a stored readiness grade, and persisting `goal` as a runtime/manifest axis. - **D66-L — Structure-optional user turns feed SPEC-mode capture; `freestyle` is no longer runtime strategy state.** The durable commitment is that ordinary user-driven turns, pasted material, and structure-optional conversation are allowed without banning structured exchanges. D98-L supersedes the `freestyle` runtime-strategy framing: this behavior is part of SPEC-mode elicitation/capture conduct, not a separate op_mode, authority posture, AUTO strategy, or explicit user/system runtime pin. Current materialized state lives in [`src/agents/skills/strategies/README.md`](src/agents/skills/strategies/README.md), [`src/agents/skills/strategies/freestyle/SKILL.md`](src/agents/skills/strategies/freestyle/SKILL.md), [`src/agents/skills/methods/capture/SKILL.md`](src/agents/skills/methods/capture/SKILL.md), [`src/agents/runtime/policy.ts`](src/agents/runtime/policy.ts), [`src/.pi/extensions/commands/index.ts`](src/.pi/extensions/commands/index.ts), and [`src/session/README.md`](src/session/README.md). Depends on: D18-L, D25-L, D26-L, D40-L, D45-L, D49-L, D50-L, D59-L, D63-L, D65-L. Refines: R16. Refined by: D80-L, D81-L (2026-06-12 FE-861 grill: the capture half — submit-time capture wiring and the "directly-stated only" commitment line — is superseded by the banded capture sweep and the commitment gradient; capture runs on every elicitor turn over the un-swept tail, resolving the every-turn-vs-on-demand open question). Supersedes: treating offer-first (R16) as a universal per-turn session invariant; treating freestyle as a new operational mode or authority posture. - **D80-L — Generalized capture is the elicitor's banded capture sweep: in-turn, synchronous, over the un-swept transcript tail.** Capture is conduct of the foreground elicitor, not product wiring: there is no observer/auditor queue on the primary path (D18-L, reaffirmed — the v1 observer failed on structure-dependence and context starvation), no product-side LLM extraction pass on the submit paths, no gateway/translation/judgment layer between the agent and graph truth, and no capture subagent in the current block. The **banded capture sweep** is one band-ordered pass that walks intent-kind groups (the same typology the `elicitation_gaps` register references via `refersTo: NodeKind`, D65-L/D75-L), committing through the real role-named `mutateGraph` grammar (D53-L/A14-L) and moving gap dispositions through `update_elicitation_gaps`. Its input window is the **un-swept transcript tail** — all conversational and digest content since the last sweep, tracked by a **sweep watermark** (prior art: the I45-L assistant-visible watermark and the own-mutation stamp) — so capture is robust to RPC-submitted messages, interruptions, and multi-message bursts, and probes get a crisp invariant: after any elicitor turn, nothing conversational remains behind the watermark. Default is a single pass; bulk material (pastes, document reads, exploration digests) may engage an **escalation valve** — chunked/iterated sweeping within the same turn — without changing window or watermark semantics. Choreography is **capture-then-ask**: the sweep commits facts and moves gaps *before* the elicitor composes its next question, so the question provably benefits from what was just captured. Consequence: the deterministic labeled-prefix capture core (`graph/capture/structured-response.ts`), its `session.submitMessage`/`session.submitExchangeResponse` wiring, and the `capture-response-to-graph` proof are retired fossils — capture becomes turn-coupled (same agent for RPC transport clients, different moment; no coverage loss). Depends on: A14-L, A22-L, D18-L, D49-L, D53-L, D63-L, D65-L, D66-L; I45-L. Supersedes: submit-time product-side capture (the D66-L "exactly as the structured-response capture tracer does" wiring), the labeled-prefix extraction core, and the capture-quality spike's product-side extraction-pass shape. - **D81-L — Capture commitment gradient: confidence, not directness; low-confidence noticings spawn elicitation gaps.** What the sweep commits is governed by confidence in grounding, not by whether the user uttered the exact words: directly-stated facts commit with `basis: explicit`; confidently-materialized items — including implied edges and structure soundly inferred from stated content — commit with `basis: implicit`, which D63-L already licenses (agent-materialized-from-user-input); low-confidence **noticings** are never committed — the sweep's prompt directs the elicitor to spawn an `elicitation_gap` instead (`basis: implicit`, rationale citing the noticing), so the false-commit guard's positive output *is* the capture-reflection behavior: one prompted discipline discharges both the guard and the gap-writeback obligation, the agenda durably carries what was noticed, and the anti-shadowing line (D65-L) holds because the gap carries question/rationale, never domain content as truth. There is **no structural gate**: the guard is commitment rules in the sweep prompting plus the false-commit scenario matrix re-aimed at the low-confidence line and run at probe tier (some spike implication rows become legitimate implicit commits under the gradient; expected gap-spawns become assertable probe outcomes); CI guards structural legality only at the `CommandExecutor` boundary. Refines: D18-L (low-confidence material now spawns gaps rather than only "folding into later questions"; preface, D47-L, remains the orientation carrier). Depends on: A22-L, D18-L, D47-L, D63-L, D65-L. Supersedes: "implications never become graph truth" as a *directness* rule, and the spike matrix's `shouldCommit` expectations as written. @@ -317,12 +317,12 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c 3. **Gap-reflection conduct belongs to the capture skill, not `review-for-gaps`.** D81-L spawn-on-noticing + close-on-answered is **always-on capture-sweep conduct** (every elicitor turn), so it lives with the D80-L capture skill, not an optionally-selected method. `review-for-gaps` is demoted to the **deliberate-audit sense only** (missing support, contradictions, verification debt). Read/interpret-gap semantics stay on the `read_elicitation_gaps` tool description (tool-local), not duplicated into a skill; the D81-L commitment gradient lives once, in the capture skill, with gap-spawn as its third outlet. 4. **The prompt-content rewrite is design work entangled with live/stubbed seams — not a keyword fossil sweep.** The strategy/method bodies drift and overlap, but audit (2026-06-18) found their suspect tokens are mostly *not* dead history: `tool_meta` is live across every exchange projection; `capture_*` is a live `tool_meta.next` sequencing marker (`present_* → request_* → capture_*`), distinct from the D80-L-retired labeled-prefix capture core; and `present_candidates` + `user_rubric` / `meta_rubric` / `graph_refs` are the **anticipated payload of the live candidate topology stub** (`projections/exchanges/present-candidates.ts`, PLAN-confirmed stubbed), not removable fossils. Only `renderCall` is genuinely unreferenced (confirm against the Pi display API before removal). Rewriting prompt content must reconcile against the candidate stub, the exchange `tool_meta` model, and the D80-L sweep model rather than strip by keyword. Lexicon sweep in the same pass: `elicitation backlog` → `elicitation gaps` / `coverage obligation` (the D65-L rename; the inlined `elicit-expand` posture in `elicitor/SYSTEM.md` still carries the old term after the goal-axis drop relocated it). - Prompt-shape closure (revised 2026-06-22): (a) **`SKILL.md` directory topology is adopted for every strategy/lens/method** because the Agent Skills standard is now the target prompt-resource format; `references/` remains deferred until a concrete skill needs progressive disclosure, and D39-L's code-owned path list remains the availability surface. (b) **`src/agents/prompts//SYSTEM.md`** is adopted for live and named future bodies; no flat agent-body shape remains open. (c) **`[sub]` sub-agent notation** is deferred until the first real delegated sub-agent lands; no empty sub-agent stubs are introduced. (d) **`_generated/` typed-vocab references** are **materialized** (first instance: the kind→band table at `src/graph/schema/_generated/ontology.md`, generated by `npm run generate:ontology` from the typed `graph/schema` sources and drift-checked by `npm run check:data-model`, wired into `npm run check`); they are read-only projections locked separately from authored prompt-resource bodies, and prompt resources cite them rather than restating vocabulary (the data-model-legibility frontier owns the expansion to further tables + the authored judgment layer). Current state: [`src/agents/skills/README.md`](src/agents/skills/README.md). Resolved 2026-06-18: the capture home is `methods/capture`, absorbing the former `infer-and-capture` method name; the full D80/D81/D82 conduct body remains FE-861. Depends on: D23-L, D25-L, D26-L, D39-L, D40-L, D58-L, D59-L, D65-L, D73-L, D80-L, D81-L. Refines: D25-L, D26-L, D40-L, D58-L, D59-L. Supersedes: `goal` as an AUTO-able manifest/runtime axis (the "objective axes `strategy`, `lens`, and `goal`" triple in D40-L/D58-L/D59-L → two axes, goal inlined); `propose-graph` / `project-graph` as `strategy`-axis members (D25-L/D26-L list them as strategies); treating gap spawn/close as a `review-for-gaps` method responsibility; `infer-and-capture` as a separate method; treating the `capture_*` / candidate / `tool_meta` prompt-resource references as removable fossils; and the 2026-06-19 deferral of Agent Skills `SKILL.md` topology. + Prompt-shape closure (revised 2026-06-22): (a) **`SKILL.md` directory topology is adopted for every strategy/lens/method** because the Agent Skills standard is now the target prompt-resource format; `references/` remains deferred until a concrete skill needs progressive disclosure, and D39-L's code-owned path list remains the availability surface. (b) **`src/agents/prompts//SYSTEM.md`** is adopted for live and named future bodies; no flat agent-body shape remains open. (c) **`[sub]` sub-agent notation** is deferred until the first real delegated sub-agent lands; no empty sub-agent stubs are introduced. (d) **generated typed-vocab context references** are **materialized** (first instance: the kind→band table at `src/agents/contexts/references/graph-ontology.md`, generated by `npm run generate:ontology` from the typed `graph/schema` sources and drift-checked by `npm run check:data-model`, wired into `npm run check`); they are read-only projections locked separately from authored prompt-resource bodies, and prompt resources cite them rather than restating vocabulary (the data-model-legibility frontier owns the expansion to further tables + the authored judgment layer). Current state: [`src/agents/contexts/README.md`](src/agents/contexts/README.md) and [`src/agents/skills/README.md`](src/agents/skills/README.md). Resolved 2026-06-18: the capture home is `methods/capture`, absorbing the former `infer-and-capture` method name; the full D80/D81/D82 conduct body remains FE-861. Depends on: D23-L, D25-L, D26-L, D39-L, D40-L, D58-L, D59-L, D65-L, D73-L, D80-L, D81-L. Refines: D25-L, D26-L, D40-L, D58-L, D59-L. Supersedes: `goal` as an AUTO-able manifest/runtime axis (the "objective axes `strategy`, `lens`, and `goal`" triple in D40-L/D58-L/D59-L → two axes, goal inlined); `propose-graph` / `project-graph` as `strategy`-axis members (D25-L/D26-L list them as strategies); treating gap spawn/close as a `review-for-gaps` method responsibility; `infer-and-capture` as a separate method; treating the `capture_*` / candidate / `tool_meta` prompt-resource references as removable fossils; and the 2026-06-19 deferral of Agent Skills `SKILL.md` topology. Depends on: D19-L, D52-L, D60-L, D62-L, D65-L, D75-L. Refines: D60-L (RENDER stage). Supersedes: the ad-hoc `[bracket]`-header + bullet-list render style as the house convention; hand-rolled markdown and tree string generation in the old renderer layer; carrying sessions in the `` cwd render. - **D95-L — Elicitor capability spine: `capture` / `generate` / `project` are the three SPEC-mode capabilities.** The elicitor's work decomposes into three capabilities by what each does to the graph: **capture** commits ground material already present in the transcript tail into graph truth (the D80-L banded sweep + D81-L commitment gradient + D82-L acquisition layer, already specced); **generate** proposes new typed graph expressions on a requested plane from grounding plus a conceptual frame, fanning candidates out and committing the chosen one through review (D96-L); **project** derives nodes on one plane from a subset/plane of the existing graph with connecting cross-plane edges (e.g. requirements→design, design→oracles, A33-L). D98-L makes this a SPEC-mode capability vocabulary, not a runtime-axis topology: capture/generate/project are the elicitor's jobs, while strategy/lens/method files are optional prompt-resource organization if they improve behavior. Capture remains always-on conduct of every elicitor turn; generate and project are requested just-in-time and readiness remains advisory rather than a graph-write tool gate (D74-L/D86-L). Background acquisition subagents (D82-L near-future, A34-L) are the `acquire` arm feeding capture, not a fourth capability. Depends on: D74-L, D80-L, D81-L, D82-L, D85-L, D86-L, D98-L. Supersedes: the proposed `grounding` / `elicitation` / `projection` lifecycle directories as a replacement skill topology, and treating strategy/lens/method as the load-bearing runtime capability model. - **D96-L — `generate` is one deep plane-parameterized skill; fan-in is a three-value mode carried by `present_candidates` + the review-set path, not three skills.** Generative proposal across the intent, design, and oracle planes is **one** `generate` skill taking the target plane (and lens frame) as a parameter, not per-plane `propose-scenarios` / `propose-design-shapes` / `propose-oracle-ensembles` skills (the earlier per-plane sketch in [`docs/design/ELICITATION_LENSES.md`](docs/design/ELICITATION_LENSES.md)). The fan-out/fan-in shape is shared: the skill fans candidate expressions out, then **fan-in is a three-value mode** — `pick` (choose one), `synthesize` (merge candidates into one), `compose` (accept several) — expressed as plane-keyed method conduct over `present_candidates` plus the review-set path rather than branched per plane. Plane-specific judgment (the "design it twice" pattern for design, oracle-family selection for oracles, the kernel/lens heuristics in [`docs/design/BEHAVIORAL_KERNELS.md`](docs/design/BEHAVIORAL_KERNELS.md) / [`docs/design/ELICITATION_LENSES.md`](docs/design/ELICITATION_LENSES.md) for intent) lives in plane-keyed skill content read on demand (D58-L manifest world), not in separate skills. This **entailed un-stubbing the `present_candidates` topology** — the tool [`src/.pi/extensions/exchanges/present-candidates.ts`](src/.pi/extensions/exchanges/present-candidates.ts), the projection [`src/projections/exchanges/present-candidates.ts`](src/projections/exchanges/present-candidates.ts), and the renderer [`src/agents/contexts/exchanges/present-candidates.ts`](src/agents/contexts/exchanges/present-candidates.ts) — which D85-L move 4 confirmed as a live anticipated stub, not a fossil: candidate presentation gets its product owner here. The materialized tool remains **pick-only at the UI boundary**: intent uses the pick as recognition/provenance, while design-plane synthesize is performed by the method after the pick and then reviewed/committed through `present_review_set → request_response → acceptReviewSet`, so no `fan_in_mode` field is needed unless a later plane proves the UI itself must carry that mode. Commitment still flows through the review-set path (D27-L); `present_candidates` recognizes fan-out presentation and never commits graph truth itself (I51-L). Depends on: D26-L, D27-L, D30-L, D31-L, D58-L, D74-L, D85-L, D95-L; A31-L, A32-L. Supersedes: per-plane generative skills as the topology; treating `present_candidates` as a permanent stub without a product owner; prebuilding a fan-in schema field before a plane proves it necessary. -- **D97-L — Skill ontology-heuristic provenance: three sources — consumed context renders, generated typed-vocab, hand-authored judgment — kept distinct.** Skill bodies that teach the agent how to think about the graph model draw ontology/heuristic content from three provenance classes that must not blur: (1) **dynamic instance context** rendered into the prompt by the context-render house style (D83-L, FE-870) — graph overviews, gap agendas, neighborhoods — consumed, never restated in skill prose; (2) **generated typed-vocab references** (`_generated/`, D85-L prompt-shape closure (d)) projected from the closed `kinds.ts` enums (D73-L) and drift-checked, for any skill that must enumerate node kinds / edge categories / bands / planes; and (3) **hand-authored judgment** — the irreducible "how to reason" content (kernels, lenses, oracle-family selection) that is neither instance data nor mechanical vocabulary. The rule: a skill cites the context renderer or the generated reference rather than copying its content, so ontology drift (D73-L renames, D94-L band changes) propagates through one canonical source. Depends on: D58-L, D73-L, D83-L, D85-L, D94-L; FE-870. Supersedes: hand-restating node-kind / band / edge-category / plane vocabulary inside skill bodies. +- **D97-L — Skill ontology-heuristic provenance: three sources — consumed context renders, generated typed-vocab, hand-authored judgment — kept distinct.** Skill bodies that teach the agent how to think about the graph model draw ontology/heuristic content from three provenance classes that must not blur: (1) **dynamic instance context** rendered into the prompt by the context-render house style (D83-L, FE-870) — graph overviews, gap agendas, neighborhoods — consumed, never restated in skill prose; (2) **generated typed-vocab context references** (`src/agents/contexts/references/`, D85-L prompt-shape closure (d)) projected from the closed `kinds.ts` enums (D73-L) and drift-checked, for any skill that must enumerate node kinds / edge categories / bands / planes; and (3) **hand-authored judgment** — the irreducible "how to reason" content (kernels, lenses, oracle-family selection) that is neither instance data nor mechanical vocabulary. The rule: a skill cites the context renderer or the generated reference rather than copying its content, so ontology drift (D73-L renames, D94-L band changes) propagates through one canonical source. Depends on: D58-L, D73-L, D83-L, D85-L, D94-L; FE-870. Supersedes: hand-restating node-kind / band / edge-category / plane vocabulary inside skill bodies. - **D98-L — Operational mode only: suspend strategy/lens/method runtime axes; target product modes are SPEC and CODE.** The architectural correction is that the `strategy` / `lens` / `method` model is not yet proven as the right product/runtime abstraction. It may still organize prompt-resource files and concise agent-readable references, but it must not be a user-facing TUI picker, transcript-backed posture field, AUTO axis, tool gate, or the source of foreground-agent identity until live elicitor behavior proves that shape earns its cost. Runtime state narrows to one mutable axis: operational mode. The target product mode labels are **`SPEC`** and **`CODE`**. `SPEC` runs the `elicitor`, whose job is to get a user from zero to a complete spec through three capabilities: (1) capture arbitrary unstructured material into graph truth with correct confidence/basis/gap handling; (2) generate candidate graph concepts on the intent, design, and oracle planes and commit coherent graph expressions through the appropriate review/commit path; and (3) project selected graph subsets or planes into downstream planes with connecting nodes and edges. `CODE` runs the **`executor`**, a Brunch-aware coding assistant that merges the prior `orchestrator` and `pi-coder` directions: it can read/use Brunch graph and session context, can act as a normal coding assistant under the CODE tool policy, and owns the plan-execution orchestration tool surface (the previously stubbed orchestrator tool) instead of requiring a separate execute-mode coordinator. The TUI should expose only `mode: SPEC | CODE`; prompt-resource/reference loading remains agent-internal and load-on-demand unless a narrow runtime moment proves eager injection necessary. Depends on: D23-L, D40-L, D58-L, D85-L, D90-L, D93-L, D95-L, D97-L. Establishing frontier: `data-model-legibility` for the SPEC-mode guidance/reference substrate, followed by executor/tool-port work for CODE. Supersedes: runtime persistence or UI exposure of strategy/lens/method axes; the `elicit` / `execute` / planned `code` three-mode foreground roster; the separation between `orchestrator` as execute coordinator and `pi-coder` as direct-coding mode; and treating method routing as the product-level capability model. ### Critical Invariants @@ -544,7 +544,7 @@ src/.pi/ | **mutateGraph** | Canonical atomic authored graph-mutation command/tool. Takes `{ createBasis, ops }`, where `ops` can create, patch, or delete graph items and `create_edge` uses role-named endpoint fields instead of authored `source` / `target`. One tool call, one selected-spec LSN, stable kind-ordinal allocation, all-or-nothing (I34-L). The load-bearing direct-commit path for `propose-graph` (D53-L), where concept-level materialization is `basis: implicit` (D63-L). | | **propose-graph** | Capability-readiness id for the direct-commit graph-write mechanism. The agent may present a concept-level commitment and, after user acceptance, persist a full subgraph through `mutateGraph` without intermediate entity-level review (D26-L, D53-L, D85-L). It is no longer a strategy-axis value; `commit-graph` carries the method mechanics. The hardest thing to get structurally legal and the primary proof target for A14-L. | | **project-graph** | Capability-readiness id for the review-set graph-write mechanism. The agent derives nodes and edges from existing graph truth (e.g. projecting requirements from upstream goals/constraints), presents them for review, and persists only accepted exact items (D27-L, D85-L). It is no longer a strategy-axis value; `generate-proposal` carries the method mechanics. | -| **freestyle** | Structure-optional elicitor strategy (D66-L): the turn may be ordinary user-driven chat rather than an offer-first structured exchange, structured-exchange tools stay available, and user-invoked slash/skill-commands are ergonomic here. It grows graph truth only through generalized capture — the banded capture sweep (D80-L) over the elicitor's turn. Initiative/interaction-style, not authority; never AUTO-selected — user pin only. Refines R16. | +| **freestyle** | Retired runtime-strategy term for structure-optional SPEC-mode turns (D66-L/D98-L). The live product behavior is simpler: ordinary user-driven chat, pasted material, and structured exchanges are all allowed inputs to the elicitor, and graph truth grows only through generalized capture — the banded capture sweep (D80-L) over the elicitor's turn. | | **Banded capture sweep** | The generalized-capture procedure (D80-L): one band-ordered in-turn pass walking intent-kind groups over the un-swept transcript tail, committing per the commitment gradient through role-named `mutateGraph` and moving gap dispositions through `update_elicitation_gaps`, before the next question is composed (capture-then-ask). Single pass by default; bulk material may engage an in-turn chunk/iterate escalation valve. | | **Sweep watermark** | Transcript position marking how far the banded capture sweep has consumed; the un-swept tail behind it is the sweep's input window regardless of how content arrived (answers, pastes, tool results, digests). Probe invariant: after any elicitor turn, no conversational content remains behind it (D80-L). | | **Commitment gradient** | The capture commitment rule (D81-L): confidence, not directness. Stated → `basis: explicit`; confidently materialized (incl. implied edges/structure) → `basis: implicit`; low-confidence **noticings** → never committed, spawned as elicitation gaps instead. | @@ -627,15 +627,15 @@ src/.pi/ | **Promotion** | The explicit act of moving a `scratch///` run into tracked `runs///` evidence, the only path by which exploratory dev output becomes a curated probe run (D70-L). | | **`BRUNCH_DEV`** | The single env switch gating every dev affordance at once: dev RPC methods, introspection-extension registration, scratch artifact routing, and the scoped offline-default lift (D71-L). Generalizes the former `BRUNCH_DEV_RPC`. | | **Conversational introspection** | The targeted capability (validated A26-L) where, in a `BRUNCH_DEV` session, the agent can inspect prior session-log values through `brunch_session_query` and captured provider payload/base options through `brunch_introspect_query`, then surface exact returned bytes in chat for discussion. The tools are dev instrumentation, not product behavior; live compliance with exact echoing is outer-loop fitness. | -| **Elicitation lens** | Retired term. The interaction-shape axis is now **Strategy** (`step-wise-decision-tree`, `step-wise-disambiguate`, `freestyle`) and the topical-focus axis is **Lens** (`intent`, `design`, `oracle`) — two orthogonal session-agent axes (D25-L/D85-L). The prior free-text catalogue (`step-by-step`, `disambiguate-via-examples`, `propose-scenarios-with-tradeoffs`, `propose-design-shapes`, `propose-oracle-ensembles`, `project-requirements-from-upstream`) is superseded. | +| **Elicitation lens** | Retired term. D98-L suspends strategy/lens/method as runtime axes; prior lens/strategy catalogues survive only as possible prompt-resource or reference vocabulary when a concrete elicitor behavior needs them. Plane concepts (`intent`, `design`, `oracle`) remain graph/model vocabulary, not session posture. | | **Single-exchange elicitation flow** | A prompt/answer exchange such as step-by-step questioning or contrastive disambiguation. The elicitor captures high-confidence extractive content synchronously post-exchange; low-confidence implications stay in preface/question material. | | **Batch-proposal flow** | A proposal/review flow with structured entity-draft payloads in structured-exchange proposal details. Durable graph changes land only through review-set approval. | | **Grounding bundle** | The minimum set of anchors required to establish the frame for main elicitation: a *domain anchor*, a *protagonist anchor*, a *pain/pull anchor*, and a *constraint anchor*. Captured technical constraints land in the constraint anchor and bound subsequent technical-design fan-outs. | | **Grounding anchor** | One sentence-scale fact captured during early elicitation that contributes to the grounding bundle. | -| **Establishment offer** | A structured-exchange payload facet summarising the elicitor's perceived gaps, available strategies for the next move, recommendation, and confidence. Source of ambient affordances rendered in chrome/web orientation regions; inspectable post-hoc and fixture-able through transcript replay. Orientation artifact, not a default exhaustive strategy menu. | -| **Elicitor intent hint** | A structured-exchange payload facet emitted alongside a prompt or proposal, declaring `lens` and semantic targets for downstream capture/reviewer/future-auditor routing and extraction guidance. | -| **Review set** | A cohesive batch proposal presented to the user for review-cycle acceptance (approve / request changes / reject), modeled on the GitHub PR-review-cycle. Used for batch-proposal flows and for design/oracle commitment review sets (the `commit-converge` goal, D59-L). | -| **Commitment review set** | A focus-primary review set: design-oriented sets primarily commit requirement/invariant-like intent claims; oracle-oriented sets primarily commit criterion/check/example-like verification claims. Support/provenance edges are part of the accepted batch. Driven by the `commit-converge` goal (D59-L), not a persisted posture. | +| **Establishment offer** | A structured-exchange payload facet summarising the elicitor's perceived gaps, recommended next move, and confidence. Source of ambient affordances rendered in chrome/web orientation regions; inspectable post-hoc and fixture-able through transcript replay. Orientation artifact, not a default exhaustive strategy/menu surface. | +| **Elicitor intent hint** | A structured-exchange payload facet emitted alongside a prompt or proposal, declaring semantic targets and any concrete plane/provenance fields needed by downstream capture/reviewer/future-auditor routing. It must not depend on a generic runtime `lens` axis. | +| **Review set** | A cohesive batch proposal presented to the user for review-cycle acceptance (approve / request changes / reject), modeled on the GitHub PR-review-cycle. Used for batch-proposal flows and for design/oracle commitment review sets; `commit-converge` survives only as SPEC-mode prompt guidance, not runtime state (D59-L/D98-L). | +| **Commitment review set** | A focus-primary review set: design-oriented sets primarily commit requirement/invariant-like intent claims; oracle-oriented sets primarily commit criterion/check/example-like verification claims. Support/provenance edges are part of the accepted batch. Driven by SPEC-mode prompt guidance, not a persisted posture. | | **Batch acceptance** | The single `CommandExecutor` call (`acceptReviewSet`) that commits an entire review set atomically as one LSN and one change-log entry, attributed to the user. Partial acceptance and accept-with-edits are not product operations. | | **Reviewer** | An agent role that runs async after batch acceptance, scoped to the accepted batch plus graph neighborhood, analyzing for coherence / completeness / gaps. Authority is narrow: writes only `reconciliation_need` records via `CommandExecutor`. | | **Anchor scenario** | A particular vignette embedded inside one alternative pitch to ground its framing. Transcript-rendered; not persisted as a graph entity. | @@ -763,29 +763,29 @@ The first required probe is M0: after manual TUI interaction, a checker proves ` | I15-L | `acceptReviewSet` contract tests plus FE-809 public-RPC review approval tests/probe prove one selected-spec LSN / one change-log entry / one explicit-basis batch, with partial acceptance unrepresentable. Future property tests can broaden batch-acceptance fuzzing but are no longer the first proof. | | I16-L | M5+ middle-loop architectural boundary test on reviewer-attributed `CommandExecutor` writers (rejects any non-`reconciliation_need` target); paired with reviewer-attributed command-result audit fixture. | | I17-L | M5+ inner-loop schema validation on review-set structured-exchange payloads (must declare `epistemic_status`); paired with outer-loop fixture assertion that status varies appropriately with grounding density (POC-phase fitness, not gate). | -| I18-L | M5+ inner-loop schema validation on elicitor-emitted structured-exchange payload facets that need routing (must declare `lens`); paired with middle-loop property test that generated payloads route to the correct capture/reviewer/future-auditor consumer. | +| I18-L | Inner-loop schema validation on elicitor-emitted structured-exchange payload facets that need routing (must declare explicit plane/provenance fields only when a concrete downstream reader needs them; no generic runtime `lens` requirement); paired with middle-loop property test that generated payloads route to the correct capture/reviewer/future-auditor consumer. | | I19-L | Brunch extension/runtime guard tests for `/fork`/`/clone` blocking, explicit absence of a `/tree` blocker, plus transcript-reader non-linearity rejection tests. | | I20-L | Proposal-validation contract tests plus `present_review_set` dry-run gating prove invalid proposals emit non-reviewable `structural_illegal`; FE-809 real probe confirms invalid agent attempts did not become the pending review exchange. | | I21-L | M3 RPC/WebSocket explicit-session projection tests; future write-lease tests when browser writes land. | | I22-L | FE-744 coordinator inventory/activation tests plus pty/ANSI-stripped TUI probe assertions: no stale transcript before explicit resume, new-spec path creates an implicit first session, new-session path yields binding-only JSONL, resume path renders the chosen transcript, chrome includes activated session id, and RPC/headless boot exposes structured initial-selection state instead of invoking TUI picker code. | | I23-L | FE-744 structured-exchange tests: `present_*` results persist rich markdown display through `toolResult.content`/`renderResult`; `request_*` tools mount an input-replacing TUI response surface when available; single-choice, multi-choice, freeform, and freeform-plus-choice answers persist as self-contained request result details; RPC/fixture paths submit the same semantic response through JSON-editor fallback or Brunch product handlers; recovery helpers detect unmatched required presents; session exchange projection pairs the prompt-side present with the terminal request result. Structured-exchange schema tests cover the landed target details model: checked `schema`/`v`, `tool_meta`, candidate rubric/graph-ref shapes, review-set pointer shape, request answered/cancelled/unavailable unions, `comment` vs runtime `message`, and capture no-graph-payload minimum. | | I24-L | Sealed-profile tests: resource-loader options disable ambient discovery; inline Brunch extension resources still load intentionally through `resources_discover`; settings/keybinding/tool/prompt policy audit proves no ambient user/project `.pi/` setting changes Brunch product behavior. | -| I25-L | Runtime-state tests: append init/switch custom entries, reload the linear transcript, reconstruct the active `op_mode` / `strategy` / `lens` (foreground role derived from `op_mode`), tolerate stale `agentGoal` fields on old entries without re-emitting them, and verify before-agent-start/tool-call policy suppresses disallowed tools for `elicit` while selected-spec gap coverage activates commitment proposal tools. | +| I25-L | Runtime-state tests: append init/switch custom entries, reload the linear transcript, reconstruct the active operational mode only (foreground role derived from mode), tolerate stale legacy `agentGoal`/strategy/lens fields on old entries without re-emitting them, and verify before-agent-start/tool-call policy suppresses disallowed tools for SPEC while CODE receives executor authority. | | I26-L | Structured-exchange schema tests prove the acknowledged Zod seam parses and exports JSON Schema; future M4 architectural tests should grep/import-audit schema libraries and Drizzle row-schema derivation boundaries. | | I28-L | Inner — TypeBox schema validation of [src/.pi/extensions/compaction/index.ts](src/.pi/extensions/compaction/index.ts) shape; deterministic anchor-rendering unit tests (same branch + same config → same header bytes). Middle (M9) — compaction round-trip property tests across all configured anchors and selection rules; fallback-to-Pi-default behavior under simulated auth failure, empty LLM output, and thrown error. Outer (M9) — long-horizon adversarial fixture confirms session binding, latest runtime state, latest establishment offer, in-flight side-task results, and unresolved staleness hints remain agent-intelligible post-compaction. | | I29-L | Inner — SDK child-session tests prove sealed service construction, agent-body system prompt ownership, no inherited parent conversation, explicit tool allowlists per starter agent, no-tools projector/reviewer behavior, duplicate/malformed frontmatter failure, explicit registry discovery from `src/agents/prompts//SYSTEM.md`, config validation, bounded concurrency, invalid caller-shape rejection before runner invocation, and parent-abort behavior before/during setup and after session creation. Middle — when startup wiring lands, a product-path smoke should prove the launch gate supplies deps intentionally and ordinary elicit sessions without deps do not register/advertise `subagent`. Outer — probe-driven proposal-generation or delegated-acquisition runs invoking explorer/researcher/projector/reviewer confirm subagent outputs ground proposals/digests without bypassing primary authority. | | I30-L | FE-807 covered the now-superseded labeled-text response tracer (D80-L retires it). The FE-861 **capture commitment-gradient routing gate** is now landed for the full closed matrix (explicit/implicit commits via `mutateGraph`; low-confidence never commits and maps to one gap; contradictions route to `semantic_conflict` reconciliation needs; structural gaps derive `answered`; `manual`-gap close on the one `{specId, lsn}` clock; binary `shouldCommit` retired in favor of gradient `expectedOutcome`). The paired **sweep-watermark property** is landed (`sweep-watermark.test.ts` + live `before_agent_start` wiring), and the submit-time labeled-prefix fossil + its `capture-response-to-graph` / `submit-message-capture` proofs are now deleted (D80-L fossil retirement). Confidence-classification accuracy and gap/recon dedup quality stay fitness/blind-spot (see below). | -| I31-L | Capability-readiness tests proving live gap coverage negotiates/unlocks later actions without disabling gathering/refinement; prompt/tool-policy tests proving readiness-thin pinned axes still compose while gated methods stay withheld; graph write tests proving later-band node kinds are not rejected solely because grounding is thin. | +| I31-L | Capability-readiness tests proving live gap coverage negotiates/unlocks later actions without disabling gathering/refinement; prompt/tool-policy tests proving readiness does not withhold graph-write tools or require pinned runtime prompt-resource axes; graph write tests proving later-band node kinds are not rejected solely because grounding is thin. | | I32-L | FE-744 public-RPC structured-exchange parity proof: `rpc.discover` contract tests, pending/respond lifecycle tests, deterministic permutation run over Brunch JSON-RPC only, no repeated deterministic prompts, and parity assertions over the resulting Pi JSONL, transcript display, and session exchange projections. | | I33-L | Current schema tests cover minimum no-graph `capture_*` details and reject graph payload fields. Future capture-analysis runtime tests must still cover persisted result rendering, no graph-write side effects, Brunch-semantic transcript inclusion, and hidden/collapsed TUI rendering fallback. | | I36-L | Per-plane kind enum validation tests in CommandExecutor (`command-executor.test.ts`). The former kind-to-category derivation clause is retired with the `intentKindCategory` axis (D56-L). | | I37-L | M4 node-creation tests: decision/term rejected without detail; constraint accepted with or without detail; other kinds rejected with detail; unknown detail fields rejected. | -| I38-L | `agents-composition-layer` inner tests: given projected runtime states and selected-spec gaps/readiness states, compose emits manifests whose strategy/lens/method resources are legal, Brunch-owned, readable, and filtered by capability-readiness plus the agent allow-list; AUTO axes list only legal choices, pinned axes stay visible whenever the tuple is role/mode-legal, and no `` family is emitted. Middle/outer probes may track whether the model actually reads the selected resource before applying it as fitness, not as an inner-loop gate. | +| I38-L | `agents-composition-layer` inner tests: given projected runtime states and selected-spec gaps/readiness states, compose emits manifests whose resources/references are legal, Brunch-owned, readable, and filtered by operational mode plus the agent allow-list; no AUTO/pinned strategy/lens/method axes or `` family are emitted. Middle/outer probes may track whether the model actually reads the selected resource before applying it as fitness, not as an inner-loop gate. | | I39-L | `graph-tool-resilience` CommandExecutor/adapter/context tests: counter rows allocate monotonic per-kind ordinals in multi-node batches, rollback does not persist failed ordinals/counter rows, DB constraints reject duplicate `(spec_id, plane, kind, kind_ordinal)`, projected-code metadata is unique and parses by longest prefix, existing-code refs resolve inside the selected spec, and prompt/tool renderers use codes as primary handles. Remaining proof: deletion/supersession no-reuse. | | I40-L | `graph-tool-resilience` CommandExecutor/adapter tests: `mutateGraph` applies one batch create-basis to all created nodes/edges, single-node `createNode` rejects retired basis values before LSN/counter/node/change-log allocation, `propose-graph` adapter commits use `implicit`, review-set translation uses `explicit`, retired `accepted_review_set` is rejected, and `change_log.operation` remains independent of basis. FE-807 adds direct structured text response capture with `basis: explicit`. FE-809 adds real project-graph review-cycle acceptance proof with explicit-basis readback under `.fixtures/runs/project-graph-review-cycle/2026-06-06-project-graph-review-cycle/`. | | I41-L | `graph-tool-resilience` CommandExecutor tests reject supersession cycles across existing edges, intra-batch edges, and mixed existing+batch edges, including rollback of batch nodes/edges/change_log; existing acyclic supersession paths still commit. | | I45-L | Middle — watermark-projection property tests (own-write stamping vs foreign `worldUpdate`; strict-greater item set per I4-L; no-`worldUpdate` when `current==watermark`); **seed/full-overview snapshots advance the watermark while narrow `getNodes`/`queryNodes` reads do not**; **no redundant `worldUpdate` immediately after a seed that named the current snapshot LSN**; **same-session submit/capture write bumps `current_lsn` and is surfaced by the next `worldUpdate` (not swallowed)**; **a foreign write that lands between the snapshot read and seed insertion is not masked by the seed**; change-log-range fixtures driving a foreign writer (a second faux session or a direct `CommandExecutor` write) through the real boot. Inner — projection unit tests over synthetic transcript continuity entries. **Live 2026-06-11** — the coverage-first scaffold is fully flipped; no skipped/`todo` rows remain. | -| I46-L | Middle — Tier-2 faux-turn-through-real-boot assertions: new session seeds-then-kicks before the first provider call; resumed-session kick decision classifies **latest unresolved conversational debt** (ignoring trailing continuity-only entries) and still fires when a user tail is followed by reconciler-inserted seed/staleness notices; **crash-after-notice-before-provider reboot still kicks when the underlying debt is an unanswered user/assistant turn** (idempotent re-boot); resumed-session kick stays silent when the latest debt already rests at a `request_*`/system leaf; no fabricated user entry in any path; AUTO never originates `freestyle`. Outer — manual walkthrough of opening-offer quality. **Live 2026-06-11** via `bootTier2RuntimeFromFixture` (real-boot-over-fixture resume chassis); the `request_*` idle proof uses fixtures built from the real result projections (key-presence envelope), not hand-built shapes. | +| I46-L | Middle — Tier-2 faux-turn-through-real-boot assertions: new session seeds-then-kicks before the first provider call; resumed-session kick decision classifies **latest unresolved conversational debt** (ignoring trailing continuity-only entries) and still fires when a user tail is followed by reconciler-inserted seed/staleness notices; **crash-after-notice-before-provider reboot still kicks when the underlying debt is an unanswered user/assistant turn** (idempotent re-boot); resumed-session kick stays silent when the latest debt already rests at a `request_*`/system leaf; no fabricated user entry in any path; AUTO never originates `freestyle`. Outer — manual walkthrough of opening-offer quality. **Live 2026-06-11** via `bootTier2RuntimeFromFixture` (real-boot-over-fixture resume chassis); the `request_*` idle proof uses fixtures built from the real result projections (key-presence envelope), not hand-built shapes. D98-L follow-up should replace the legacy AUTO/freestyle origination assertion with SPEC-mode offer/ambient-turn policy. | | I47-L | Middle — restart/resume idempotence property tests (repeated boot does not duplicate seed/`worldUpdate`; dedupe derived from projection); **compaction+resume preserves the projected watermark and does not spuriously re-emit `worldUpdate`** (preserved-anchor set retains the latest watermark carrier); carrier-discipline source/architecture tests (continuity facts are custom entries, not synthetic `toolCall`s or prompt-only). **Live 2026-06-11** via `rebootTier2Runtime` (actual restart over the same session file, Pi's deferred JSONL flushed first); the suite's sets-and-`{specId, lsn}` convention is enforced mechanically by a source scan banning golden matchers. | | I48-L | Inner — seed CLI contract tests for target workspace resolution, seed set/slug filtering, explicit all-seeds mode, `CommandExecutor`/change-log routing, and destination reporting. Middle — fresh workbench tracer: seed one named fixture into `.fixtures/workbenches//.brunch/data.db`, launch `npm run dev -- --cwd .fixtures/workbenches/` (or print/RPC equivalent), and assert selected workspace state plus graph overview come only from that workbench DB. | | I49-L | Middle (covered by `subagent-reconciliation` slice 4) — negative-space invariant over the code-owned op_mode→delegatable-set allowlist: spawnable agents per op_mode equal the allowlist; a frontmatter-authored manifest cannot widen advertisement into a read-only mode; a **test-only write-capable background manifest** proves `elicit` refuses to spawn it, so the boundary is proven before the execute-mode write worker exists. Paired with the D91-L ambient-seal assertion (world is injected, not discovered: in-memory services, no `~/.pi`). See `src/.pi/extensions/subagents/subagents.test.ts` and §Verification Design subagent-reconciliation oracle battery (oracle 4). | diff --git a/package.json b/package.json index c62c9bc06..4447b8fca 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "build": "tsc -p tsconfig.build.json && npm run build:info && npm run build:pi-assets && npm run build:web", "build:info": "node scripts/write-build-info.mjs", "prepack": "RELEASE=true npm run build", - "build:pi-assets": "mkdir -p dist/.pi/components/workspace-dialog dist/.pi/extensions/subagents dist/agents/prompts dist/agents/skills && cp -R src/.pi/components/workspace-dialog/assets dist/.pi/components/workspace-dialog/ && cp -R src/agents/prompts/elicitor src/agents/prompts/explorer src/agents/prompts/orchestrator src/agents/prompts/pi-coder src/agents/prompts/projector src/agents/prompts/researcher src/agents/prompts/reviewer dist/agents/prompts/ && cp -R src/agents/skills/strategies src/agents/skills/lenses src/agents/skills/methods dist/agents/skills/ && cp src/.pi/extensions/subagents/config.json dist/.pi/extensions/subagents/", + "build:pi-assets": "mkdir -p dist/.pi/components/workspace-dialog dist/.pi/extensions/subagents dist/agents/prompts dist/agents/skills dist/agents/contexts && cp -R src/.pi/components/workspace-dialog/assets dist/.pi/components/workspace-dialog/ && cp -R src/agents/prompts/elicitor src/agents/prompts/explorer src/agents/prompts/orchestrator src/agents/prompts/pi-coder src/agents/prompts/projector src/agents/prompts/researcher src/agents/prompts/reviewer dist/agents/prompts/ && cp -R src/agents/skills/strategies src/agents/skills/lenses src/agents/skills/methods dist/agents/skills/ && cp -R src/agents/contexts/references dist/agents/contexts/ && cp src/.pi/extensions/subagents/config.json dist/.pi/extensions/subagents/", "build:web": "vite build", "seed": "tsx src/graph/seed-fixtures.ts", "generate:ontology": "tsx src/graph/schema/generate-ontology-ref.ts", diff --git a/src/agents/contexts/README.md b/src/agents/contexts/README.md index 0f8bc0d52..943149994 100644 --- a/src/agents/contexts/README.md +++ b/src/agents/contexts/README.md @@ -4,11 +4,12 @@ SPEC decisions: D52-L, D58-L, D60-L, D76-L, D78-L, D83-L, D91-L, D96-L ## Owns -`src/agents/contexts/` owns reusable Brunch-authored text that enters the model: pushed seed blocks, context-tool result text, graph/context markdown, and structured-exchange tool result text. +`src/agents/contexts/` owns reusable Brunch-authored text that enters the model: pushed seed blocks, context-tool result text, graph/context markdown, generated/authored shared context references, and structured-exchange tool result text. ```text contexts/ ├── primitives/ markdown, TOON, tree, and section formatting helpers +├── references/ runtime-eligible shared context references cited by skills/prompts ├── seeds/ per-turn pushed context blocks and origination seed payloads ├── graph/ graph overview/neighborhood, related-node, mutation, reconciliation text ├── elicitation.ts elicitation agenda/update text @@ -30,6 +31,8 @@ rules: `src/.pi/__tests__/architecture.test.ts` guards the adapter half of this boundary for `brunch-data` and structured-exchange tools: Pi adapters may own schemas, labels, descriptions, prompt snippets, and TUI rendering, but provider-visible Brunch text must be imported from this subtree rather than formatted inline. +`references/` files are runtime-eligible agent-readable context references. They are shared cite targets for prompt resources when vocabulary or judgment content should be loaded on demand without copying tables into skill bodies. Generated references, such as `references/graph-ontology.md`, are committed artifacts with their source-of-truth and drift-check command named in the file. The packaged CLI copies this subtree into `dist/agents/contexts/references/` because skills may cite these files at runtime. + ## Snapshot convention Context golden files live beside their tests under `__snapshots__/` and use stock Vitest file snapshots. There is no separate preview writer. diff --git a/src/graph/schema/_generated/ontology.md b/src/agents/contexts/references/graph-ontology.md similarity index 76% rename from src/graph/schema/_generated/ontology.md rename to src/agents/contexts/references/graph-ontology.md index 2358d57ac..b636b7b1c 100644 --- a/src/graph/schema/_generated/ontology.md +++ b/src/agents/contexts/references/graph-ontology.md @@ -1,18 +1,15 @@ -# Ontology reference (generated) +# Graph ontology reference -Projected from `src/graph/schema/kinds.ts` and `nodes.ts` — the source of truth -for graph vocabulary (D73-L). Regenerate with `npm run generate:ontology`; drift -is caught by `npm run check:data-model`. Do not hand-edit. +Projected from `src/graph/schema/kinds.ts` and `nodes.ts` — the source of truth for graph vocabulary (D73-L). Regenerate with `npm run generate:ontology`; drift is caught by `npm run check:data-model`. Do not hand-edit. -## Node kind → readiness band +### Node kind → readiness band -Kinds appear in canonical plane order (intent, oracle, design, plan). A band -cell of `—` means the kind carries no readiness band (D94-L). +Kinds appear in canonical plane order (intent, oracle, design, plan). A band cell of `—` means the kind carries no readiness band (D94-L). | Kind | Code | Readiness bands | -| --- | --- | --- | +| - | - | - | | goal | G | grounding | | thesis | TH | grounding | | term | T | — | diff --git a/src/agents/docs/README.md b/src/agents/docs/README.md new file mode 100644 index 000000000..04bc4325d --- /dev/null +++ b/src/agents/docs/README.md @@ -0,0 +1,20 @@ +# agents/docs/ — backstage agent-resource curation notes + +SPEC decisions: D52-L, D85-L, D97-L, D98-L + +## Owns + +`src/agents/docs/` owns backstage notes for curating Brunch-authored agent resources: recovery inventories, source-analysis notes, and judgment-layer drafting material that should inform prompt resources or context references but should not itself be loaded as runtime prompt payload. + +It is not a prompt-resource directory, not scanned by the Agent Skills loader, and not copied into packaged runtime assets. + +## Boundary rules + +```pseudo +rules: + agents/docs/ -> agents/skills/, agents/contexts/references/ [curation input only] + agents/docs/ x> runtime resource loading [not prompt payload] + agents/docs/ x> graph mutation [notes only] +``` + +Runtime-eligible shared references live in `src/agents/contexts/references/`. Skill-local progressive-disclosure payloads live under the owning skill's `references/` directory. This directory is only the backstage curation workspace for deciding what belongs in those homes. diff --git a/src/agents/skills/README.md b/src/agents/skills/README.md index 017b381d0..a50a42a1b 100644 --- a/src/agents/skills/README.md +++ b/src/agents/skills/README.md @@ -37,7 +37,7 @@ The legal set is sealed by the code-owned path list in `agents/runtime/state.ts` ## Prompt-resource sub-shapes - **`references/` subfiles:** available under the Agent Skills standard when a concrete skill needs progressive disclosure. No empty reference directories are introduced. The first materialized instance is `methods/generate-proposal/references/`, where the shared `SKILL.md` points to plane-specific payloads without advertising those payloads as separate skills. -- **_generated/ typed-vocab references:** materialized at `src/graph/schema/_generated/ontology.md` (kind→band table), the schema-owned home for vocabulary that prompt resources cite rather than restate (D97-L). Generated from the typed sources via `npm run generate:ontology` and drift-checked by `npm run check:data-model` (wired into `npm run check`); read-only and locked separately from the authored prompt-resource body lock below. Further vocabulary tables (edge categories, detail forms) are added to that file when a concrete citing need appears, not speculatively. +- **Shared typed-vocab context references:** materialized at `src/agents/contexts/references/graph-ontology.md` (kind→band table), the runtime-eligible shared context-reference home for vocabulary that prompt resources cite rather than restate (D97-L). Generated from the typed graph schema sources via `npm run generate:ontology` and drift-checked by `npm run check:data-model` (wired into `npm run check`); read-only and locked separately from the authored prompt-resource body lock below. Further vocabulary tables (edge categories, detail forms) are added to that file when a concrete citing need appears, not speculatively. ## Prompt-resource body lock ledger diff --git a/src/agents/skills/__tests__/prompt-resources.test.ts b/src/agents/skills/__tests__/prompt-resources.test.ts index f909e22b8..0c2e81f52 100644 --- a/src/agents/skills/__tests__/prompt-resources.test.ts +++ b/src/agents/skills/__tests__/prompt-resources.test.ts @@ -80,8 +80,19 @@ describe('prompt-resource skills', () => { expect(readme).toContain('/SKILL.md'); expect(readme).toContain('references/` subfiles'); expect(readme).toContain('progressive disclosure'); - expect(readme).toContain('_generated/ typed-vocab references'); + expect(readme).toContain('Shared typed-vocab context references'); + expect(readme).toContain('src/agents/contexts/references/graph-ontology.md'); expect(readme).toContain('concrete citing need appears'); expect(readme).toContain('drift-checked'); }); + + it('records the shared context-reference and backstage curation homes', async () => { + const contextsReadme = await readFile(join(projectRoot, 'src/agents/contexts/README.md'), 'utf8'); + expect(contextsReadme).toContain('references/ runtime-eligible shared context references'); + expect(contextsReadme).toContain('references/graph-ontology.md'); + + const docsReadme = await readFile(join(projectRoot, 'src/agents/docs/README.md'), 'utf8'); + expect(docsReadme).toContain('backstage notes for curating Brunch-authored agent resources'); + expect(docsReadme).toContain('not copied into packaged runtime assets'); + }); }); diff --git a/src/agents/skills/methods/capture/SKILL.md b/src/agents/skills/methods/capture/SKILL.md index cad40b37e..ddb12157a 100644 --- a/src/agents/skills/methods/capture/SKILL.md +++ b/src/agents/skills/methods/capture/SKILL.md @@ -21,7 +21,7 @@ chain capture-then-ask: ## Sweep frame -Walk the un-swept material once by readiness band and likely node kind. The canonical band order and per-kind band membership are the generated kind→band table in `src/graph/schema/_generated/ontology.md` (projected from the typed schema — cite it, do not restate it; D97-L). Conversational answers, ordinary user text, and acquisition digests are all sweep inputs. Large raw reads or tool results should be digested first; capture from the digest plus the conversation, not from unbounded raw bulk. +Walk the un-swept material once by readiness band and likely node kind. The canonical band order and per-kind band membership are the generated kind→band table in `src/agents/contexts/references/graph-ontology.md` (projected from the typed schema — cite it, do not restate it; D97-L). Conversational answers, ordinary user text, and acquisition digests are all sweep inputs. Large raw reads or tool results should be digested first; capture from the digest plus the conversation, not from unbounded raw bulk. Use the graph, gap, and reconciliation tools as the mutation boundary: diff --git a/src/graph/README.md b/src/graph/README.md index 474d3c454..9708efec6 100644 --- a/src/graph/README.md +++ b/src/graph/README.md @@ -52,11 +52,11 @@ SPEC decisions: D4-L, D20-L, D27-L, D45-L, D51-L, D52-L, D53-L, D54-L, D60-L, D6 derived readiness-band membership (`bandsForKind`), and derived intent-kind grouping. Raw domain enum taxonomy lives in the zero-import `schema/kinds.ts` leaf so web-facing graph imports do not pull in Drizzle. The kind→readiness-band - ontology is projected from this typed source to a committed reference at - `schema/_generated/ontology.md` by `schema/generate-ontology-ref.ts`; the - `check:data-model` script fails if the committed file drifts from the schema - (D87-L(d), D97-L). Skills cite that generated reference instead of restating - bands. + ontology is projected from this typed source to a committed runtime context + reference at `src/agents/contexts/references/graph-ontology.md` by + `schema/generate-ontology-ref.ts`; the `check:data-model` script fails if the + committed file drifts from the schema (D87-L(d), D97-L). Skills cite that + generated reference instead of restating bands. - **Policy** (`policy/category-policy.ts`) — the single per-category metadata table (`EDGE_CATEGORY_METADATA`): endpoint roles, impact @@ -203,11 +203,9 @@ graph/ edges.ts reconciliation-need.ts generate-ontology-ref.ts - projects the typed kind→readiness-band ontology to _generated/ontology.md + projects the typed kind→readiness-band ontology to + src/agents/contexts/references/graph-ontology.md --check mode (check:data-model) guards the committed file against drift - _generated/ - ontology.md - @generated kind→band reference; cited by skills, never hand-edited policy/ category-policy.ts diff --git a/src/graph/schema/__tests__/generate-ontology-ref.test.ts b/src/graph/schema/__tests__/generate-ontology-ref.test.ts index aafe0a11b..062af0045 100644 --- a/src/graph/schema/__tests__/generate-ontology-ref.test.ts +++ b/src/graph/schema/__tests__/generate-ontology-ref.test.ts @@ -3,6 +3,9 @@ import { readFileSync } from 'node:fs'; import { describe, expect, it } from 'vitest'; import { GENERATED_ONTOLOGY_PATH, renderOntologyReference } from '../generate-ontology-ref.js'; + +const projectRoot = new URL('../../../../', import.meta.url).pathname; +const expectedReferencePath = `${projectRoot}src/agents/contexts/references/graph-ontology.md`; import { NODE_KINDS } from '../kinds.js'; import { bandsForKind } from '../nodes.js'; @@ -20,6 +23,17 @@ describe('ontology reference generator', () => { } }); + it('writes the runtime-eligible context reference path', () => { + expect(GENERATED_ONTOLOGY_PATH).toBe(expectedReferencePath); + }); + + it('renders the reference in the shared context-reference shape', () => { + expect(markdown).toContain('@generated by src/graph/schema/generate-ontology-ref.ts'); + expect(markdown.split('\n')).toContain('### Node kind → readiness band'); + expect(markdown.split('\n')).not.toContain('## Node kind → readiness band'); + expect(markdown).toContain('Kinds appear in canonical plane order'); + }); + it('keeps the committed generated file in sync with the typed source (drift guard)', () => { const committed = readFileSync(GENERATED_ONTOLOGY_PATH, 'utf8'); expect(committed).toBe(markdown); diff --git a/src/graph/schema/generate-ontology-ref.ts b/src/graph/schema/generate-ontology-ref.ts index 2bfa723fd..64c2f9922 100644 --- a/src/graph/schema/generate-ontology-ref.ts +++ b/src/graph/schema/generate-ontology-ref.ts @@ -1,29 +1,34 @@ /** * Generated ontology reference — projects the typed graph/schema vocabulary - * into a read-only Markdown table so prompt resources can cite it instead of - * restating it (D97-L). The typed sources in this directory stay the source of - * truth (D73-L); the emitted file is never hand-edited. + * into a runtime-eligible context reference so prompt resources can cite it + * instead of restating it (D97-L). The typed sources in this directory stay the + * source of truth (D73-L); the emitted file is never hand-edited. * - * First materialization of the `_generated/` mechanism deferred in - * `src/agents/skills/README.md`. This slice generates only the kind→band table; - * further vocabulary tables (edge categories, detail forms) are added when a - * concrete citing need appears, not speculatively. + * This slice generates only the kind→band table; further vocabulary tables + * (edge categories, detail forms) are added when a concrete citing need appears, + * not speculatively. * * CLI (dev only, run via tsx): - * npm run generate:ontology # write src/graph/schema/_generated/ontology.md + * npm run generate:ontology # write src/agents/contexts/references/graph-ontology.md * npm run check:data-model # exit non-zero if the committed file is stale */ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'; -import { dirname, join } from 'node:path'; +import { dirname, join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; +import { + joinMarkdownBlocks, + markdownHeading, + markdownTable, +} from '../../agents/contexts/primitives/markdown.js'; import { NODE_KINDS } from './kinds.js'; import { bandsForKind, NODE_KIND_METADATA } from './nodes.js'; const SCHEMA_DIR = dirname(fileURLToPath(import.meta.url)); +const PROJECT_ROOT = resolve(SCHEMA_DIR, '../../..'); -export const GENERATED_ONTOLOGY_PATH = join(SCHEMA_DIR, '_generated', 'ontology.md'); +export const GENERATED_ONTOLOGY_PATH = join(PROJECT_ROOT, 'src/agents/contexts/references/graph-ontology.md'); const GENERATED_NOTICE = ''; @@ -32,28 +37,17 @@ export function renderOntologyReference(): string { const rows = NODE_KINDS.map((kind) => { const bands = bandsForKind(kind); const cell = bands.length > 0 ? bands.join(', ') : '—'; - return `| ${kind} | ${NODE_KIND_METADATA[kind].label} | ${cell} |`; + return [kind, NODE_KIND_METADATA[kind].label, cell]; }); - return [ + return `${joinMarkdownBlocks( GENERATED_NOTICE, - '', - '# Ontology reference (generated)', - '', - 'Projected from `src/graph/schema/kinds.ts` and `nodes.ts` — the source of truth', - 'for graph vocabulary (D73-L). Regenerate with `npm run generate:ontology`; drift', - 'is caught by `npm run check:data-model`. Do not hand-edit.', - '', - '## Node kind → readiness band', - '', - 'Kinds appear in canonical plane order (intent, oracle, design, plan). A band', - 'cell of `—` means the kind carries no readiness band (D94-L).', - '', - '| Kind | Code | Readiness bands |', - '| --- | --- | --- |', - ...rows, - '', - ].join('\n'); + markdownHeading(1, 'Graph ontology reference'), + 'Projected from `src/graph/schema/kinds.ts` and `nodes.ts` — the source of truth for graph vocabulary (D73-L). Regenerate with `npm run generate:ontology`; drift is caught by `npm run check:data-model`. Do not hand-edit.', + markdownHeading(3, 'Node kind → readiness band'), + 'Kinds appear in canonical plane order (intent, oracle, design, plan). A band cell of `—` means the kind carries no readiness band (D94-L).', + markdownTable([['Kind', 'Code', 'Readiness bands'], ...rows]), + )}\n`; } function runCli(argv: readonly string[]): number { diff --git a/src/treedocs.yaml b/src/treedocs.yaml index d9b8f036c..63d3a8455 100644 --- a/src/treedocs.yaml +++ b/src/treedocs.yaml @@ -188,6 +188,8 @@ tree: section.ts: 'Implements section.' toon.ts: 'Implements toon.' tree.ts: 'Implements tree.' + references: + graph-ontology.md: 'Markdown resource.' seeds: README.md: 'Documents this source subtree.' origination.ts: 'Implements origination.' @@ -209,6 +211,8 @@ tree: workspace-cwd-context.md: 'Markdown resource.' workspace-overview-context.md: 'Markdown resource.' workspace-context.ts: 'Implements workspace context.' + docs: + README.md: 'Documents this source subtree.' prompts: README.md: 'Documents this source subtree.' elicitor: @@ -334,8 +338,6 @@ tree: queries.ts: 'Implements queries.' review-set.ts: 'Implements review set.' schema: - _generated: - ontology.md: 'Markdown resource.' edges.ts: 'Implements edges.' elicitation-gap-fixtures.ts: 'Implements elicitation gap fixtures.' elicitation-gaps.ts: 'Implements elicitation gaps.' From cc246ac9a45d2dd9b5fa79f6afefb12d97da0f3d Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Fri, 26 Jun 2026 13:46:55 +0200 Subject: [PATCH 44/54] move the shared resources in contexts --- src/agents/contexts/graph/graph-slice.ts | 2 +- .../contexts/specification/specification-context.ts | 6 +++--- src/agents/contexts/workspace/workspace-context.ts | 8 ++++---- src/agents/{contexts/primitives => shared}/README.md | 0 .../primitives => shared}/__tests__/markdown.test.ts | 0 .../primitives => shared}/__tests__/section.test.ts | 0 .../primitives => shared}/__tests__/toon.test.ts | 0 .../primitives => shared}/__tests__/tree.test.ts | 0 src/agents/{contexts/primitives => shared}/markdown.ts | 0 src/agents/{contexts/primitives => shared}/section.ts | 0 src/agents/{contexts/primitives => shared}/toon.ts | 0 src/agents/{contexts/primitives => shared}/tree.ts | 0 src/graph/schema/generate-ontology-ref.ts | 6 +----- 13 files changed, 9 insertions(+), 13 deletions(-) rename src/agents/{contexts/primitives => shared}/README.md (100%) rename src/agents/{contexts/primitives => shared}/__tests__/markdown.test.ts (100%) rename src/agents/{contexts/primitives => shared}/__tests__/section.test.ts (100%) rename src/agents/{contexts/primitives => shared}/__tests__/toon.test.ts (100%) rename src/agents/{contexts/primitives => shared}/__tests__/tree.test.ts (100%) rename src/agents/{contexts/primitives => shared}/markdown.ts (100%) rename src/agents/{contexts/primitives => shared}/section.ts (100%) rename src/agents/{contexts/primitives => shared}/toon.ts (100%) rename src/agents/{contexts/primitives => shared}/tree.ts (100%) diff --git a/src/agents/contexts/graph/graph-slice.ts b/src/agents/contexts/graph/graph-slice.ts index d32dbeb53..e53ff9c1e 100644 --- a/src/agents/contexts/graph/graph-slice.ts +++ b/src/agents/contexts/graph/graph-slice.ts @@ -13,7 +13,7 @@ import { formatGraphNodeCode, type NodeKind, } from '../../../graph/schema/nodes.js'; -import { markdownTable, joinMarkdownBlocks } from '../primitives/markdown.js'; +import { markdownTable, joinMarkdownBlocks } from '../../shared/markdown.js'; /** * The full, uncapped graph overview — node codes/planes/kinds/titles plus the diff --git a/src/agents/contexts/specification/specification-context.ts b/src/agents/contexts/specification/specification-context.ts index 56362fcf9..379f57ffb 100644 --- a/src/agents/contexts/specification/specification-context.ts +++ b/src/agents/contexts/specification/specification-context.ts @@ -1,9 +1,9 @@ import type { ElicitationGap, GraphSlice } from '../../../graph/index.js'; import type { WorkspaceSessionOverview } from '../../../session/workspace-overview-context.js'; +import { joinMarkdownBlocks, markdownTable, markdownUl } from '../../shared/markdown.js'; +import { section } from '../../shared/section.js'; +import { renderToonBlock, type ToonRecord } from '../../shared/toon.js'; import { formatGraphOverview } from '../graph/graph-slice.js'; -import { joinMarkdownBlocks, markdownTable, markdownUl } from '../primitives/markdown.js'; -import { section } from '../primitives/section.js'; -import { renderToonBlock, type ToonRecord } from '../primitives/toon.js'; import { renderSoftReadinessEstimate } from '../session/readiness-estimate.js'; export interface SpecificationContextRenderInput { diff --git a/src/agents/contexts/workspace/workspace-context.ts b/src/agents/contexts/workspace/workspace-context.ts index cace719eb..b87b99fcd 100644 --- a/src/agents/contexts/workspace/workspace-context.ts +++ b/src/agents/contexts/workspace/workspace-context.ts @@ -1,9 +1,9 @@ import type { WorkspaceOverview } from '../../../session/workspace-overview-context.js'; import type { WorkspaceCwdInventory, WorkspaceTopologyEntry } from '../../../workspace/cwd-inventory.js'; -import { inlineCode, joinMarkdownBlocks, markdownTable, markdownUl } from '../primitives/markdown.js'; -import { section } from '../primitives/section.js'; -import type { RenderTreeNode } from '../primitives/tree.js'; -import { renderTreeBlock } from '../primitives/tree.js'; +import { inlineCode, joinMarkdownBlocks, markdownTable, markdownUl } from '../../shared/markdown.js'; +import { section } from '../../shared/section.js'; +import type { RenderTreeNode } from '../../shared/tree.js'; +import { renderTreeBlock } from '../../shared/tree.js'; export function renderWorkspaceContext(context: WorkspaceCwdInventory | WorkspaceOverview): string { return section( diff --git a/src/agents/contexts/primitives/README.md b/src/agents/shared/README.md similarity index 100% rename from src/agents/contexts/primitives/README.md rename to src/agents/shared/README.md diff --git a/src/agents/contexts/primitives/__tests__/markdown.test.ts b/src/agents/shared/__tests__/markdown.test.ts similarity index 100% rename from src/agents/contexts/primitives/__tests__/markdown.test.ts rename to src/agents/shared/__tests__/markdown.test.ts diff --git a/src/agents/contexts/primitives/__tests__/section.test.ts b/src/agents/shared/__tests__/section.test.ts similarity index 100% rename from src/agents/contexts/primitives/__tests__/section.test.ts rename to src/agents/shared/__tests__/section.test.ts diff --git a/src/agents/contexts/primitives/__tests__/toon.test.ts b/src/agents/shared/__tests__/toon.test.ts similarity index 100% rename from src/agents/contexts/primitives/__tests__/toon.test.ts rename to src/agents/shared/__tests__/toon.test.ts diff --git a/src/agents/contexts/primitives/__tests__/tree.test.ts b/src/agents/shared/__tests__/tree.test.ts similarity index 100% rename from src/agents/contexts/primitives/__tests__/tree.test.ts rename to src/agents/shared/__tests__/tree.test.ts diff --git a/src/agents/contexts/primitives/markdown.ts b/src/agents/shared/markdown.ts similarity index 100% rename from src/agents/contexts/primitives/markdown.ts rename to src/agents/shared/markdown.ts diff --git a/src/agents/contexts/primitives/section.ts b/src/agents/shared/section.ts similarity index 100% rename from src/agents/contexts/primitives/section.ts rename to src/agents/shared/section.ts diff --git a/src/agents/contexts/primitives/toon.ts b/src/agents/shared/toon.ts similarity index 100% rename from src/agents/contexts/primitives/toon.ts rename to src/agents/shared/toon.ts diff --git a/src/agents/contexts/primitives/tree.ts b/src/agents/shared/tree.ts similarity index 100% rename from src/agents/contexts/primitives/tree.ts rename to src/agents/shared/tree.ts diff --git a/src/graph/schema/generate-ontology-ref.ts b/src/graph/schema/generate-ontology-ref.ts index 64c2f9922..1f48469f1 100644 --- a/src/graph/schema/generate-ontology-ref.ts +++ b/src/graph/schema/generate-ontology-ref.ts @@ -17,11 +17,7 @@ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'; import { dirname, join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; -import { - joinMarkdownBlocks, - markdownHeading, - markdownTable, -} from '../../agents/contexts/primitives/markdown.js'; +import { joinMarkdownBlocks, markdownHeading, markdownTable } from '../../agents/shared/markdown.js'; import { NODE_KINDS } from './kinds.js'; import { bandsForKind, NODE_KIND_METADATA } from './nodes.js'; From 4c46efc6c459eab222d83ee0f11e7323b0d66f9c Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Fri, 26 Jun 2026 14:07:25 +0200 Subject: [PATCH 45/54] Adopt house style for graph seed renderers --- .../agent-runtime-system-prompts.test.ts | 10 +-- .../extensions/brunch-data/context/index.ts | 8 +-- src/agents/contexts/exchanges/README.md | 2 + .../__tests__/present-review-set.test.ts | 65 +++++++++++++++++++ .../contexts/exchanges/present-review-set.ts | 36 ++++++++-- .../seeds/__tests__/turn-context.test.ts | 28 ++++---- src/agents/contexts/seeds/turn-context.ts | 55 ++++------------ .../__tests__/specification-context.test.ts | 1 + 8 files changed, 138 insertions(+), 67 deletions(-) create mode 100644 src/agents/contexts/exchanges/__tests__/present-review-set.test.ts diff --git a/src/.pi/extensions/__tests__/agent-runtime-system-prompts.test.ts b/src/.pi/extensions/__tests__/agent-runtime-system-prompts.test.ts index 8ef117f62..6430d759c 100644 --- a/src/.pi/extensions/__tests__/agent-runtime-system-prompts.test.ts +++ b/src/.pi/extensions/__tests__/agent-runtime-system-prompts.test.ts @@ -216,7 +216,7 @@ describe('Brunch prompt-pack topology', () => { systemPrompt: expect.not.stringContaining(''), }); expect(result).toMatchObject({ - systemPrompt: expect.stringContaining('[Selected-spec graph context · design lens]'), + systemPrompt: expect.stringContaining('Selected-spec graph overview · design lens'), }); expect(result).toMatchObject({ systemPrompt: expect.stringContaining('design modules/interfaces'), @@ -405,10 +405,10 @@ describe('Brunch prompt-pack topology', () => { ), }); expect(defaultPrompt).toMatchObject({ - systemPrompt: expect.stringContaining('[Selected-spec graph context · auto lens]'), + systemPrompt: expect.stringContaining('Selected-spec graph overview · auto lens'), }); expect(switchedPrompt).toMatchObject({ - systemPrompt: expect.stringContaining('[Selected-spec graph context · oracle lens]'), + systemPrompt: expect.stringContaining('Selected-spec graph overview · oracle lens'), }); }); @@ -647,9 +647,9 @@ describe('Brunch prompt-pack topology', () => { expect(disambiguateIntentPrompt).not.toContain('propose-graph'); expect(disambiguateDesignPrompt).toContain('step-wise-disambiguate'); expect(disambiguateDesignPrompt).not.toContain('step-wise-decision-tree'); - expect(disambiguateIntentPrompt).toContain('[Selected-spec graph context · intent lens]'); + expect(disambiguateIntentPrompt).toContain('Selected-spec graph overview · intent lens'); expect(disambiguateIntentPrompt).toContain('intent claims, terms, assumptions'); - expect(disambiguateDesignPrompt).toContain('[Selected-spec graph context · design lens]'); + expect(disambiguateDesignPrompt).toContain('Selected-spec graph overview · design lens'); expect(disambiguateDesignPrompt).toContain('design modules/interfaces'); expect(disambiguateIntentPrompt).toContain('Clarify Brunch prompt posture'); expect(disambiguateDesignPrompt).toContain('Clarify Brunch prompt posture'); diff --git a/src/.pi/extensions/brunch-data/context/index.ts b/src/.pi/extensions/brunch-data/context/index.ts index e665539ab..ff703ed81 100644 --- a/src/.pi/extensions/brunch-data/context/index.ts +++ b/src/.pi/extensions/brunch-data/context/index.ts @@ -64,12 +64,12 @@ export function registerBrunchContext(pi: ExtensionAPI): void { name: 'read_specification_context', label: 'Read Specification Context', description: - 'Read the selected specification context: overview, spec-scoped sessions, and ranked elicitation gaps.', - promptSnippet: 'Read the selected specification overview, sessions, and elicitation gaps', + 'Read the selected specification context: overview, full graph overview, ranked elicitation gaps, and spec-scoped sessions.', + promptSnippet: 'Read the selected specification overview, graph overview, sessions, and elicitation gaps', promptGuidelines: [ 'Use read_specification_context when you need selected-spec context rather than cwd or session runtime context.', - 'This render is scope-clustered: overview, spec-scoped sessions, and ranked elicitation gaps only.', - 'Use read_graph for the full graph topology; this context carries graph size only.', + 'This render is scope-clustered: overview, full graph overview, ranked elicitation gaps, and spec-scoped sessions.', + 'Use read_graph when you need a filtered graph slice or node neighborhood after reading this overview.', ], parameters: { type: 'object', diff --git a/src/agents/contexts/exchanges/README.md b/src/agents/contexts/exchanges/README.md index 8fb717e48..f06bbe6a4 100644 --- a/src/agents/contexts/exchanges/README.md +++ b/src/agents/contexts/exchanges/README.md @@ -3,3 +3,5 @@ SPEC decisions: D13-L, D84-L, D96-L Owns model-facing text for structured-exchange tool results (`present_*` and `request_*`). Pi exchange adapters own schemas, registration, and TUI collection; this directory formats the returned text that becomes tool-result context. + +`present_review_set` renders role-named edge drafts as plain-language relations (for example, `req-1 bounds goal-1`) rather than raw structural arrows; the role-named payload remains the proposal grammar, while model-facing text uses the graph label projection. diff --git a/src/agents/contexts/exchanges/__tests__/present-review-set.test.ts b/src/agents/contexts/exchanges/__tests__/present-review-set.test.ts new file mode 100644 index 000000000..cb1172e4a --- /dev/null +++ b/src/agents/contexts/exchanges/__tests__/present-review-set.test.ts @@ -0,0 +1,65 @@ +import { describe, expect, it } from 'vitest'; + +import type { ReviewSetProposalPayload } from '../../../../graph/review-set.js'; +import { projectPresentReviewSet } from '../../../../projections/exchanges/present-review-set.js'; +import { formatPresentReviewSet } from '../present-review-set.js'; + +describe('formatPresentReviewSet', () => { + it('renders role-named edge drafts as readable relations, not raw draft arrows', () => { + const rendered = formatPresentReviewSet( + projectPresentReviewSet({ exchangeId: 'review-launch', payload: reviewSetPayload }), + ); + + expect(rendered).toContain('- req-rollback bounds goal-launch'); + expect(rendered).toContain('- check-observable witnesses goal-launch'); + expect(rendered).not.toContain('—exclusion→'); + expect(rendered).not.toContain('—witness [for]→'); + }); +}); + +const reviewSetPayload = { + schemaVersion: 1, + lens: 'intent', + epistemicStatus: 'asserted', + grounding: { + summary: 'Launch readiness needs rollback and observability.', + support: ['User asked for launch readiness.'], + }, + pitch: { + title: 'Launch readiness review set', + narrative: 'Review the launch-readiness commitments together.', + }, + entityDrafts: [ + { + draftId: 'goal-launch', + plane: 'intent', + kind: 'goal', + title: 'Launch safely', + }, + { + draftId: 'req-rollback', + plane: 'intent', + kind: 'requirement', + title: 'Rollback is required', + }, + { + draftId: 'check-observable', + plane: 'oracle', + kind: 'check', + title: 'Observe rollback path', + }, + ], + edgeDrafts: [ + { + category: 'exclusion', + boundary: { draftId: 'req-rollback' }, + subject: { draftId: 'goal-launch' }, + }, + { + category: 'witness', + oracle: { draftId: 'check-observable' }, + claim: { draftId: 'goal-launch' }, + stance: 'for', + }, + ], +} satisfies ReviewSetProposalPayload; diff --git a/src/agents/contexts/exchanges/present-review-set.ts b/src/agents/contexts/exchanges/present-review-set.ts index 74c105f88..95d11cc2c 100644 --- a/src/agents/contexts/exchanges/present-review-set.ts +++ b/src/agents/contexts/exchanges/present-review-set.ts @@ -1,4 +1,8 @@ import { roleNamedEdgeDraftEndpoints } from '../../../graph/command-executor/role-named-edge-draft.js'; +import { edgeLabel } from '../../../graph/projection/labels.js'; +import type { ReviewSetProposalPayload } from '../../../graph/review-set.js'; +import { NODE_KINDS } from '../../../graph/schema/kinds.js'; +import type { NodeKind } from '../../../graph/schema/nodes.js'; import type { PresentReviewSetProjection } from '../../../projections/exchanges/present-review-set.js'; export function formatExchangeStructuralIllegal(result: { @@ -37,14 +41,38 @@ export function formatPresentReviewSet(projection: PresentReviewSetProjection): }); lines.push('', '## Edge drafts'); + const draftKinds = draftKindMap(payload.entityDrafts); payload.edgeDrafts.forEach((draft) => { const { source: sourceRef, target: targetRef } = roleNamedEdgeDraftEndpoints(draft); - const source = 'draftId' in sourceRef ? sourceRef.draftId : sourceRef.existingCode; - const target = 'draftId' in targetRef ? targetRef.draftId : targetRef.existingCode; - const stance = draft.category === 'witness' || draft.category === 'rationale' ? ` [${draft.stance}]` : ''; - lines.push('', `- ${source} —${draft.category}${stance}→ ${target}`); + const source = endpointLabel(sourceRef); + const target = endpointLabel(targetRef); + const sourceKind = 'draftId' in sourceRef ? draftKinds.get(sourceRef.draftId) : undefined; + const targetKind = 'draftId' in targetRef ? draftKinds.get(targetRef.draftId) : undefined; + const relation = edgeLabel({ + category: draft.category, + anchorRole: 'source', + stance: 'stance' in draft ? draft.stance : undefined, + sourceKind, + targetKind, + }); + lines.push('', `- ${source} ${relation} ${target}`); if (draft.rationale) lines.push(` ${draft.rationale}`); }); return lines.join('\n'); } + +function draftKindMap(drafts: ReviewSetProposalPayload['entityDrafts']): ReadonlyMap { + const entries = drafts.flatMap((draft) => + isNodeKind(draft.kind) ? [[draft.draftId, draft.kind] as const] : [], + ); + return new Map(entries); +} + +function endpointLabel(ref: { readonly draftId: string } | { readonly existingCode: string }): string { + return 'draftId' in ref ? ref.draftId : ref.existingCode; +} + +function isNodeKind(value: string): value is NodeKind { + return NODE_KINDS.includes(value as NodeKind); +} diff --git a/src/agents/contexts/seeds/__tests__/turn-context.test.ts b/src/agents/contexts/seeds/__tests__/turn-context.test.ts index 255a51f7c..8ddf1ef42 100644 --- a/src/agents/contexts/seeds/__tests__/turn-context.test.ts +++ b/src/agents/contexts/seeds/__tests__/turn-context.test.ts @@ -75,24 +75,26 @@ describe('renderGraphSeed', () => { const design = renderGraphSeed(overview, { lens: 'design' }); const oracle = renderGraphSeed(overview, { lens: 'oracle' }); - expect(intent).toContain('[Selected-spec graph context · intent lens]'); - expect(design).toContain('[Selected-spec graph context · design lens]'); - expect(oracle).toContain('[Selected-spec graph context · oracle lens]'); - expect(intent).toContain('- selected-spec lsn: 7; nodes: 4; edges: 2'); - expect(intent).toContain('intent claims, terms, assumptions'); - expect(design).toContain('design modules/interfaces'); - expect(oracle).toContain('verification checks, evidence'); - expect(intent.indexOf('intent/goal')).toBeLessThan(intent.indexOf('design/module')); - expect(design.indexOf('design/module')).toBeLessThan(design.indexOf('intent/goal')); - expect(oracle.indexOf('oracle/check')).toBeLessThan(oracle.indexOf('intent/goal')); + expect(intent).toContain('Selected-spec graph overview · intent lens'); + expect(design).toContain('Selected-spec graph overview · design lens'); + expect(oracle).toContain('Selected-spec graph overview · oracle lens'); + expect(intent).toContain('Selected-spec graph overview · intent lens (LSN 7): 4 nodes, 2 edges'); + expect(intent).toContain('Emphasis: intent claims, terms, assumptions'); + expect(design).toContain('Emphasis: design modules/interfaces'); + expect(oracle).toContain('Emphasis: verification checks, evidence'); + expect(intent).toContain('| G1 | 1 | Fast local specification |'); + expect(design).toContain('| MOD2 | 2 | Prompt composer |'); + expect(oracle).toContain('| CH3 | 3 | Prompt posture fixture |'); + expect(intent).toContain('| id | upstream | relation | downstream |'); + expect(intent).not.toContain('-[realization]->'); + expect(intent).not.toContain('[G1] intent/goal'); expect(overview.nodes[0]?.title).toBe('Fast local specification'); }); it('bounds rendered node and edge output', () => { const rendered = renderGraphSeed(overview, { lens: 'intent', maxNodes: 2, maxEdges: 1 }); - expect(rendered).toContain('…2 more node(s) omitted'); - expect(rendered).toContain('…1 more edge(s) omitted'); + expect(rendered).toContain('Omitted: 2 node(s), 1 edge(s).'); }); }); @@ -109,7 +111,7 @@ describe('composeAgentContextSeed', () => { expect(blocks).toHaveLength(2); expect(blocks[0]).toContain('[Selected workspace context]'); - expect(blocks[1]).toContain('[Selected-spec graph context · design lens]'); + expect(blocks[1]).toContain('Selected-spec graph overview · design lens'); }); }); diff --git a/src/agents/contexts/seeds/turn-context.ts b/src/agents/contexts/seeds/turn-context.ts index 6bca9dd0a..6f7902b57 100644 --- a/src/agents/contexts/seeds/turn-context.ts +++ b/src/agents/contexts/seeds/turn-context.ts @@ -20,9 +20,10 @@ import { renderSoftReadinessEstimate } from '../../../agents/contexts/session/readiness-estimate.js'; import type { GraphSlice } from '../../../graph/queries.js'; import type { ElicitationGap } from '../../../graph/schema/elicitation-gaps.js'; -import { formatGraphNodeCode, type GraphNode } from '../../../graph/schema/nodes.js'; +import type { GraphNode } from '../../../graph/schema/nodes.js'; import type { AgentLensSelection } from '../../../session/schema/kinds.js'; import type { WorkspacePostureState } from '../../../session/workspace-session-coordinator.js'; +import { formatGraphOverview } from '../graph/graph-slice.js'; export interface AgentPromptSpecContext { id: number; @@ -120,43 +121,24 @@ export function renderGraphSeed(overview: GraphSlice, options: RenderGraphContex const byLens = lensScore(b, options.lens) - lensScore(a, options.lens); return byLens || a.id - b.id; }); - const nodesById = new Map(overview.nodes.map((node) => [node.id, node])); + const selectedNodes = emphasizedNodes.slice(0, maxNodes); + const selectedEdges = overview.edges.slice(0, maxEdges); + const omittedNodes = Math.max(overview.nodes.length - selectedNodes.length, 0); + const omittedEdges = Math.max(overview.edges.length - selectedEdges.length, 0); const lines = [ - `[Selected-spec graph context · ${options.lens} lens]`, - `- selected-spec lsn: ${overview.lsn}; nodes: ${overview.nodes.length}; edges: ${overview.edges.length}`, - `- emphasis: ${lensEmphasis(options.lens)}`, + `Emphasis: ${lensEmphasis(options.lens)}`, + formatGraphOverview( + { lsn: overview.lsn, nodes: selectedNodes, edges: selectedEdges }, + `Selected-spec graph overview · ${options.lens} lens`, + ), ]; - if (overview.nodes.length === 0) { - lines.push('- graph: empty'); - return lines.join('\n'); + if (omittedNodes > 0 || omittedEdges > 0) { + lines.push(`Omitted: ${omittedNodes} node(s), ${omittedEdges} edge(s).`); } - lines.push('- emphasized nodes:'); - for (const node of emphasizedNodes.slice(0, maxNodes)) { - lines.push(` - ${formatNode(node)}`); - } - if (overview.nodes.length > maxNodes) { - lines.push(` - …${overview.nodes.length - maxNodes} more node(s) omitted`); - } - - if (overview.edges.length > 0) { - lines.push('- edges:'); - for (const edge of overview.edges.slice(0, maxEdges)) { - const stance = edge.stance ? `/${edge.stance}` : ''; - const source = nodesById.get(edge.sourceId); - const target = nodesById.get(edge.targetId); - const sourceCode = source ? formatGraphNodeCode(source.kind, source.kindOrdinal) : `#${edge.sourceId}`; - const targetCode = target ? formatGraphNodeCode(target.kind, target.kindOrdinal) : `#${edge.targetId}`; - lines.push(` - ${sourceCode} -[${edge.category}${stance}]-> ${targetCode}`); - } - if (overview.edges.length > maxEdges) { - lines.push(` - …${overview.edges.length - maxEdges} more edge(s) omitted`); - } - } - - return lines.join('\n'); + return lines.join('\n\n'); } function lensScore(node: GraphNode, lens: AgentLensSelection): number { @@ -179,12 +161,3 @@ function lensEmphasis(lens: AgentLensSelection): string { return 'AUTO lens selection pending; keep intent, design, and oracle cues visible'; } } - -function formatNode(node: GraphNode): string { - const body = node.body ? ` — ${truncate(node.body, 120)}` : ''; - return `[${formatGraphNodeCode(node.kind, node.kindOrdinal)}] ${node.plane}/${node.kind}: ${node.title}${body}`; -} - -function truncate(value: string, maxLength: number): string { - return value.length <= maxLength ? value : `${value.slice(0, maxLength - 1)}…`; -} diff --git a/src/agents/contexts/specification/__tests__/specification-context.test.ts b/src/agents/contexts/specification/__tests__/specification-context.test.ts index ecdd4aa37..d13e03f80 100644 --- a/src/agents/contexts/specification/__tests__/specification-context.test.ts +++ b/src/agents/contexts/specification/__tests__/specification-context.test.ts @@ -30,6 +30,7 @@ describe('renderSpecificationContext', () => { expect(rendered).toContain(''); expect(rendered).toContain('Overview:'); expect(rendered).toContain('Graph (LSN 2): 5 nodes, 3 edges'); + expect(rendered).toContain('| id | upstream | relation | downstream |'); expect(rendered).toContain('Gaps:'); expect(rendered).toContain('Sessions:'); expect(rendered.indexOf('Overview:')).toBeLessThan(rendered.indexOf('Graph (LSN 2):')); From 836be0d022f94e1a03f60153c01563048667b469 Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Fri, 26 Jun 2026 14:13:58 +0200 Subject: [PATCH 46/54] Add graph authoring heuristics reference --- memory/PLAN.md | 6 +- memory/SPEC.md | 2 +- src/agents/contexts/README.md | 2 +- .../references/graph-authoring-heuristics.md | 39 ++++++ src/agents/docs/context-reference-harvest.md | 118 ++++++++++++++++++ src/agents/skills/README.md | 1 + .../skills/__tests__/prompt-resources.test.ts | 18 ++- src/agents/skills/methods/capture/SKILL.md | 17 +-- .../skills/methods/commit-graph/SKILL.md | 9 +- 9 files changed, 185 insertions(+), 27 deletions(-) create mode 100644 src/agents/contexts/references/graph-authoring-heuristics.md create mode 100644 src/agents/docs/context-reference-harvest.md diff --git a/memory/PLAN.md b/memory/PLAN.md index ab3f6bee7..a8c39cb7f 100644 --- a/memory/PLAN.md +++ b/memory/PLAN.md @@ -82,7 +82,7 @@ context-pipeline/ - `orchestrator-tool-port` (FE-1087) — **scoped but D98-sensitive.** Port the external `brunch cook` orchestrator into the future CODE/executor tool surface rather than preserving a separate execute/orchestrator product mode. First active scope: `memory/cards/orchestrator-tool-port--plan-check-tool.md`; reconcile that scope against D98-L before build. - `elicitor-project` (FE-1085) — **design-gated.** Cross-plane derivation (requirements -> design, design -> oracles) remains undesigned under A33-L; run `ln-design` before any scope/build. -- `data-model-legibility` — **active.** Single active frontier for the SPEC-mode data-model/reference substrate and pattern-establishment sweep (D97-L/D98-L): generated graph-ontology references, authored graph-authoring/checkability/projection guidance, skill citation/pruning, and stale-doc disposition under `src/agents/`. Design verdict landed (Shape C); first tracer landed and its topology correction moved the generated kind→band table + `check:data-model` drift guard into `src/agents/contexts/references/graph-ontology.md`, cited by `methods/capture`. Remaining rows are evaluated one at a time: generated edge-category + detail-form tables, authored judgment layer, and subtypes/checkability guidance. +- `data-model-legibility` — **active.** Single active frontier for the SPEC-mode data-model/reference substrate and pattern-establishment sweep (D97-L/D98-L): generated graph-ontology references, authored graph-authoring/checkability/projection guidance, skill citation/pruning, and stale-doc disposition under `src/agents/`. Design verdict landed (Shape C); first tracer landed and its topology correction moved the generated kind→band table + `check:data-model` drift guard into `src/agents/contexts/references/graph-ontology.md`, cited by `methods/capture`. The shared authored `graph-authoring-heuristics.md` reference is now materialized and cited by `capture` + `commit-graph`. Remaining rows are evaluated one at a time: generated edge-category + detail-form tables and subtypes/checkability guidance. - `renderer-golden-coverage` — **active parallel coverage track.** Remaining RENDER work lives by audience: model-facing context surfaces under `agents/contexts/`, human/product text beside its app/session owner. Remaining rows need fresh scoping against `src/agents/contexts/README.md`, `src/app/README.md`, and `src/session/README.md`. - `exchange-symmetry-audit` — **earned cleanup.** Delete-oriented audit of the exchange projection/renderer split; not a capability blocker. @@ -143,7 +143,7 @@ context-pipeline/ - **Linear:** tbd - **Branch:** tbd - **Kind:** structural / design + build -- **Status:** active; design verdict landed (`ln-design`: Shape C — two layers behind one index). First tracer-bullet **landed and topology-corrected**: generated kind→band table at `src/agents/contexts/references/graph-ontology.md` + `check:data-model` drift guard (wired into `npm run check`), cited by `methods/capture`, with packaged runtime asset copy for `contexts/references/`. Load-bearing claim 1 (typed `graph/schema` sources are the closed, importable vocabulary set — D73-L) validated by the generator while `src/agents/contexts/references/` is now the runtime-eligible reference home. Remaining: edge-category + detail-form tables, the authored judgment layer (heuristics / promotion / checkability ladder / subtypes verdict), and the subtypes→`detail` remodel review. +- **Status:** active; design verdict landed (`ln-design`: Shape C — two layers behind one index). First tracer-bullet **landed and topology-corrected**: generated kind→band table at `src/agents/contexts/references/graph-ontology.md` + `check:data-model` drift guard (wired into `npm run check`), cited by `methods/capture`, with packaged runtime asset copy for `contexts/references/`. Load-bearing claim 1 (typed `graph/schema` sources are the closed, importable vocabulary set — D73-L) validated by the generator while `src/agents/contexts/references/` is now the runtime-eligible reference home. The authored graph-authoring heuristics row is materialized at `src/agents/contexts/references/graph-authoring-heuristics.md` and cited by `capture` + `commit-graph`; checkability/subtype material remains deferred. Remaining: edge-category + detail-form tables, checkability guidance verdict, and the subtypes→`detail` remodel review. - **Certainty:** proving. - **Current execution pointer:** none active — re-scope the next slice (authored judgment layer, or further generated tables). - **Objective:** Recover + reconcile the retired `INTENT_GRAPH_SEMANTICS` content and adjacent heuristic docs into one SPEC-mode data-model reasoning substrate under `src/agents/`: runtime-eligible references in `src/agents/contexts/references/`, backstage curation notes in `src/agents/docs/`, and pruned/cited skill bodies. Generate the closed-vocabulary tables (planes / kinds / bands / edge-category policy / `detail` schemas) from typed graph sources so heuristics are **cited** (D97-L), not inlined and duplicated across skill bodies; align the result with D98-L's mode-only runtime posture. @@ -152,7 +152,7 @@ context-pipeline/ - The canonical-truth boundary is decided: what is generated from `kinds.ts` / `nodes.ts` / `category-policy.ts` vs authored judgment. (Direction set by Shape C; kind→band table materialized in `src/agents/contexts/references/graph-ontology.md`, remaining tables pending.) - Subtypes/`detail` modelling review: each retired subtype family sorted into `kind` (behavior-bearing), `detail` facet (inert classification), or already-covered; decide whether an inert `detail` facet dimension earns its carrying cost given the kind/band/form machinery already discriminates. - The two capture gaps are explicitly ruled in or out: constraint/invariant subtype enums; the 8-rung checkability ladder + `strength`. - - Skill bodies cite the new home (D97-L); inlined heuristic copies collapse to one cite-target. + - Skill bodies cite the new home (D97-L); inlined heuristic copies collapse to one cite-target. `capture` + `commit-graph` now cite `graph-authoring-heuristics.md` for shared graph-authoring judgment. - ✓ A drift guard (`check:data-model`, mirroring `check:skills`, wired into `npm run check`) fails if the generated reference diverges from the typed sources. - If `ln-design` splits this into recover-doc / build-generator / subtypes-remodel frontiers, create a `data-model-legibility` arc per §Initiatives. - **Traceability:** D73-L (domain owns vocabulary), D88-L (`detail` form union), D97-L (heuristic provenance), D98-L (SPEC/CODE mode-only runtime posture); un-defers and relocates the generated-reference pattern into `src/agents/contexts/references/`; relates to `elicitor-project` (A33-L, shared D97-L rule). diff --git a/memory/SPEC.md b/memory/SPEC.md index 2b28741c0..138ffaf68 100644 --- a/memory/SPEC.md +++ b/memory/SPEC.md @@ -322,7 +322,7 @@ The POC's purpose is to prove three things: (a) that pi's coding-agent harness c Depends on: D19-L, D52-L, D60-L, D62-L, D65-L, D75-L. Refines: D60-L (RENDER stage). Supersedes: the ad-hoc `[bracket]`-header + bullet-list render style as the house convention; hand-rolled markdown and tree string generation in the old renderer layer; carrying sessions in the `` cwd render. - **D95-L — Elicitor capability spine: `capture` / `generate` / `project` are the three SPEC-mode capabilities.** The elicitor's work decomposes into three capabilities by what each does to the graph: **capture** commits ground material already present in the transcript tail into graph truth (the D80-L banded sweep + D81-L commitment gradient + D82-L acquisition layer, already specced); **generate** proposes new typed graph expressions on a requested plane from grounding plus a conceptual frame, fanning candidates out and committing the chosen one through review (D96-L); **project** derives nodes on one plane from a subset/plane of the existing graph with connecting cross-plane edges (e.g. requirements→design, design→oracles, A33-L). D98-L makes this a SPEC-mode capability vocabulary, not a runtime-axis topology: capture/generate/project are the elicitor's jobs, while strategy/lens/method files are optional prompt-resource organization if they improve behavior. Capture remains always-on conduct of every elicitor turn; generate and project are requested just-in-time and readiness remains advisory rather than a graph-write tool gate (D74-L/D86-L). Background acquisition subagents (D82-L near-future, A34-L) are the `acquire` arm feeding capture, not a fourth capability. Depends on: D74-L, D80-L, D81-L, D82-L, D85-L, D86-L, D98-L. Supersedes: the proposed `grounding` / `elicitation` / `projection` lifecycle directories as a replacement skill topology, and treating strategy/lens/method as the load-bearing runtime capability model. - **D96-L — `generate` is one deep plane-parameterized skill; fan-in is a three-value mode carried by `present_candidates` + the review-set path, not three skills.** Generative proposal across the intent, design, and oracle planes is **one** `generate` skill taking the target plane (and lens frame) as a parameter, not per-plane `propose-scenarios` / `propose-design-shapes` / `propose-oracle-ensembles` skills (the earlier per-plane sketch in [`docs/design/ELICITATION_LENSES.md`](docs/design/ELICITATION_LENSES.md)). The fan-out/fan-in shape is shared: the skill fans candidate expressions out, then **fan-in is a three-value mode** — `pick` (choose one), `synthesize` (merge candidates into one), `compose` (accept several) — expressed as plane-keyed method conduct over `present_candidates` plus the review-set path rather than branched per plane. Plane-specific judgment (the "design it twice" pattern for design, oracle-family selection for oracles, the kernel/lens heuristics in [`docs/design/BEHAVIORAL_KERNELS.md`](docs/design/BEHAVIORAL_KERNELS.md) / [`docs/design/ELICITATION_LENSES.md`](docs/design/ELICITATION_LENSES.md) for intent) lives in plane-keyed skill content read on demand (D58-L manifest world), not in separate skills. This **entailed un-stubbing the `present_candidates` topology** — the tool [`src/.pi/extensions/exchanges/present-candidates.ts`](src/.pi/extensions/exchanges/present-candidates.ts), the projection [`src/projections/exchanges/present-candidates.ts`](src/projections/exchanges/present-candidates.ts), and the renderer [`src/agents/contexts/exchanges/present-candidates.ts`](src/agents/contexts/exchanges/present-candidates.ts) — which D85-L move 4 confirmed as a live anticipated stub, not a fossil: candidate presentation gets its product owner here. The materialized tool remains **pick-only at the UI boundary**: intent uses the pick as recognition/provenance, while design-plane synthesize is performed by the method after the pick and then reviewed/committed through `present_review_set → request_response → acceptReviewSet`, so no `fan_in_mode` field is needed unless a later plane proves the UI itself must carry that mode. Commitment still flows through the review-set path (D27-L); `present_candidates` recognizes fan-out presentation and never commits graph truth itself (I51-L). Depends on: D26-L, D27-L, D30-L, D31-L, D58-L, D74-L, D85-L, D95-L; A31-L, A32-L. Supersedes: per-plane generative skills as the topology; treating `present_candidates` as a permanent stub without a product owner; prebuilding a fan-in schema field before a plane proves it necessary. -- **D97-L — Skill ontology-heuristic provenance: three sources — consumed context renders, generated typed-vocab, hand-authored judgment — kept distinct.** Skill bodies that teach the agent how to think about the graph model draw ontology/heuristic content from three provenance classes that must not blur: (1) **dynamic instance context** rendered into the prompt by the context-render house style (D83-L, FE-870) — graph overviews, gap agendas, neighborhoods — consumed, never restated in skill prose; (2) **generated typed-vocab context references** (`src/agents/contexts/references/`, D85-L prompt-shape closure (d)) projected from the closed `kinds.ts` enums (D73-L) and drift-checked, for any skill that must enumerate node kinds / edge categories / bands / planes; and (3) **hand-authored judgment** — the irreducible "how to reason" content (kernels, lenses, oracle-family selection) that is neither instance data nor mechanical vocabulary. The rule: a skill cites the context renderer or the generated reference rather than copying its content, so ontology drift (D73-L renames, D94-L band changes) propagates through one canonical source. Depends on: D58-L, D73-L, D83-L, D85-L, D94-L; FE-870. Supersedes: hand-restating node-kind / band / edge-category / plane vocabulary inside skill bodies. +- **D97-L — Skill ontology-heuristic provenance: three sources — consumed context renders, generated typed-vocab, hand-authored judgment — kept distinct.** Skill bodies that teach the agent how to think about the graph model draw ontology/heuristic content from three provenance classes that must not blur: (1) **dynamic instance context** rendered into the prompt by the context-render house style (D83-L, FE-870) — graph overviews, gap agendas, neighborhoods — consumed, never restated in skill prose; (2) **generated typed-vocab context references** (`src/agents/contexts/references/`, D85-L prompt-shape closure (d)) projected from the closed `kinds.ts` enums (D73-L) and drift-checked, for any skill that must enumerate node kinds / edge categories / bands / planes; and (3) **hand-authored judgment** — the irreducible "how to reason" content (kernels, lenses, oracle-family selection) that is neither instance data nor mechanical vocabulary. The materialized shared authored judgment reference is [`src/agents/contexts/references/graph-authoring-heuristics.md`](src/agents/contexts/references/graph-authoring-heuristics.md), cited by `capture` and `commit-graph` for declarative graph claims, settled commitment, low-confidence/contradiction routing, confident relation endpoints, and role-named mutation grammar. The rule: a skill cites the context renderer or the generated/authored reference rather than copying its content, so ontology drift (D73-L renames, D94-L band changes) propagates through one canonical source. Depends on: D58-L, D73-L, D83-L, D85-L, D94-L; FE-870. Supersedes: hand-restating node-kind / band / edge-category / plane vocabulary inside skill bodies. - **D98-L — Operational mode only: suspend strategy/lens/method runtime axes; target product modes are SPEC and CODE.** The architectural correction is that the `strategy` / `lens` / `method` model is not yet proven as the right product/runtime abstraction. It may still organize prompt-resource files and concise agent-readable references, but it must not be a user-facing TUI picker, transcript-backed posture field, AUTO axis, tool gate, or the source of foreground-agent identity until live elicitor behavior proves that shape earns its cost. Runtime state narrows to one mutable axis: operational mode. The target product mode labels are **`SPEC`** and **`CODE`**. `SPEC` runs the `elicitor`, whose job is to get a user from zero to a complete spec through three capabilities: (1) capture arbitrary unstructured material into graph truth with correct confidence/basis/gap handling; (2) generate candidate graph concepts on the intent, design, and oracle planes and commit coherent graph expressions through the appropriate review/commit path; and (3) project selected graph subsets or planes into downstream planes with connecting nodes and edges. `CODE` runs the **`executor`**, a Brunch-aware coding assistant that merges the prior `orchestrator` and `pi-coder` directions: it can read/use Brunch graph and session context, can act as a normal coding assistant under the CODE tool policy, and owns the plan-execution orchestration tool surface (the previously stubbed orchestrator tool) instead of requiring a separate execute-mode coordinator. The TUI should expose only `mode: SPEC | CODE`; prompt-resource/reference loading remains agent-internal and load-on-demand unless a narrow runtime moment proves eager injection necessary. Depends on: D23-L, D40-L, D58-L, D85-L, D90-L, D93-L, D95-L, D97-L. Establishing frontier: `data-model-legibility` for the SPEC-mode guidance/reference substrate, followed by executor/tool-port work for CODE. Supersedes: runtime persistence or UI exposure of strategy/lens/method axes; the `elicit` / `execute` / planned `code` three-mode foreground roster; the separation between `orchestrator` as execute coordinator and `pi-coder` as direct-coding mode; and treating method routing as the product-level capability model. ### Critical Invariants diff --git a/src/agents/contexts/README.md b/src/agents/contexts/README.md index 943149994..c0d301b74 100644 --- a/src/agents/contexts/README.md +++ b/src/agents/contexts/README.md @@ -31,7 +31,7 @@ rules: `src/.pi/__tests__/architecture.test.ts` guards the adapter half of this boundary for `brunch-data` and structured-exchange tools: Pi adapters may own schemas, labels, descriptions, prompt snippets, and TUI rendering, but provider-visible Brunch text must be imported from this subtree rather than formatted inline. -`references/` files are runtime-eligible agent-readable context references. They are shared cite targets for prompt resources when vocabulary or judgment content should be loaded on demand without copying tables into skill bodies. Generated references, such as `references/graph-ontology.md`, are committed artifacts with their source-of-truth and drift-check command named in the file. The packaged CLI copies this subtree into `dist/agents/contexts/references/` because skills may cite these files at runtime. +`references/` files are runtime-eligible agent-readable context references. They are shared cite targets for prompt resources when vocabulary or judgment content should be loaded on demand without copying tables into skill bodies. Generated references, such as `references/graph-ontology.md`, are committed artifacts with their source-of-truth and drift-check command named in the file. Authored references, such as `references/graph-authoring-heuristics.md`, carry shared judgment with concrete skill readers and must point to generated references for vocabulary rather than restating tables. The packaged CLI copies this subtree into `dist/agents/contexts/references/` because skills may cite these files at runtime. ## Snapshot convention diff --git a/src/agents/contexts/references/graph-authoring-heuristics.md b/src/agents/contexts/references/graph-authoring-heuristics.md new file mode 100644 index 000000000..c6b834c73 --- /dev/null +++ b/src/agents/contexts/references/graph-authoring-heuristics.md @@ -0,0 +1,39 @@ +# Graph authoring heuristics + +Runtime-eligible shared reference for graph-writing judgment (D97-L/D98-L). Use this for authoring discipline that is shared by `capture` and `commit-graph`; use `graph-ontology.md` for generated kind/band vocabulary instead of restating kind tables here. + +## Author declarative graph claims + +Every graph node should read as a stable claim, not an interview prompt or scratch note. + +- Normalize questions into the underlying declarative claim before writing graph truth. +- Keep follow-ups with no stable claim out of graph truth; route them to elicitation gaps instead. +- Promote before filing as `context`: if the material is success-critical, limiting, possibly false but consequential, a choice among alternatives, or a value bet, use the sharper node kind. +- Use `context` only for descriptive material that aids interpretation but does not yet carry a stronger graph role. + +## Commit only settled material + +Graph writes are for material whose commitment path is settled. + +- Direct user statements and approved review-set items are `explicit` graph truth. +- Confident structure materialized from accepted content may be `implicit` graph truth. +- Low-confidence noticings, suspicions, possible implications, or missing pieces do not become graph truth; route them to an `elicitation_gap` question plus rationale. +- Contradictions with existing graph truth are retrospective repair work; route them to a `reconciliation_need`, not a gap and not an overwrite. + +## Build relation-bearing batches from confident endpoints + +Create relation-bearing graph batches only after endpoint confidence is settled. + +```pseudo +chain relation-bearing-authoring: + candidate relation + -> confirm or create confident endpoint nodes + -> skip edge if either endpoint remains low-confidence + -> use role-named mutate_graph endpoints +``` + +Do not use capture-local or prose-local edge dialects. `mutate_graph` edges use role fields such as `dependency/dependent`, `support/claim`, `abstract/concrete`, and `boundary/subject`; diagnostics from `structural_illegal` are the repair path. + +## Keep mutation grammar role-named + +Prepare one coherent `mutate_graph` batch when the user-facing commitment is already settled. Prefer create-only direct commits in the current product posture: `create_node` ops plus role-named `create_edge` ops. Do not invent graph payload fields, LSNs, edge categories, result shapes, or partial-write recovery paths. diff --git a/src/agents/docs/context-reference-harvest.md b/src/agents/docs/context-reference-harvest.md new file mode 100644 index 000000000..753855480 --- /dev/null +++ b/src/agents/docs/context-reference-harvest.md @@ -0,0 +1,118 @@ +# Context reference harvest ledger + +Status: backstage-only curation ledger. This file is not runtime prompt payload, is not copied into packaged agent assets, and is not a shared context reference. Runtime-eligible references live under `src/agents/contexts/references/`; skill-local progressive-disclosure references live under the owning skill's `references/` directory. + +Purpose: record the row-by-row disposition of recovered or design-era data-model guidance before any authored runtime reference is created. Rows point to source material and next action; they do not restate the ontology. + +A source may carry more than one disposition class when it has separable uses. Treat the classes as labels, not an exclusive enum. Generated-reference inputs are the exception that preserves D97-L: only typed code sources may generate reference tables; recovered/design prose may motivate which table to generate, but it is authored-reference input or backstage rationale, not the source of truth. + +## Disposition classes + +| Class | Meaning | +| - | - | +| generated-reference input | Typed code source for generated content, not hand-authored prose. | +| authored-runtime-reference input | Candidate source for a shared reference under `src/agents/contexts/references/`. | +| skill-local-reference input | Candidate source for a specific skill's `references/` payload. | +| backstage-only rationale | Useful design history or validation record, but not model-facing prompt payload. | +| historical/archive candidate | Superseded or stale enough that future work should retire/archive rather than harvest directly. | +| leave-as-is | Current prompt/resource file already sits in the right home; no harvest action now. | + +## Source ledger + +| Source | Disposition labels | Candidate future reference | Reader / blocker | D98-sensitive notes | Next action | +| - | - | - | - | - | - | +| `/private/tmp/igs_recovered.md` (`INTENT_GRAPH_SEMANTICS`) | authored-runtime-reference input; backstage-only rationale; historical/archive candidate | `graph-authoring-heuristics.md`; `checkability-ladder.md`; may motivate generated edge-category/detail-form table scope but is not their generated-reference input | Reader: capture/commit/generate methods needing graph vocabulary and graph-authoring judgment. Blocker: reconcile every subtype/checkability claim against live `src/graph/schema/{kinds,nodes}.ts`, `src/graph/policy/category-policy.ts`, D87-L, D88-L, D94-L before accepting it. | Contains retired subtype proposals and old edge/ontology language; do not revive stale modality/subtype claims or create runtime `strategy` / `lens` / `method` session state. | Derive generated tables only from current typed sources; separately review promotion rules, checkability ladder, and subtype material as authored judgment rows. | +| `docs/design/ELICITATION_QUESTIONS.md` | authored-runtime-reference input | `elicitation-question-hints.md` | Reader: future elicitor question/gap guidance. Blocker: refresh against post-FE-1052 kind names, `story` / `unknown` / `entity` / `sketch`, and four-band D94-L model. | Uses older band framing and mentions strategy/lens as prompt-space terms; keep as prompt-resource vocabulary only, not runtime state. | Treat the durable thesis as: node kind is closed ontology; questions are open/projectable hints inside a kind. Rewrite examples before model-facing use. | +| `docs/design/ONTOLOGY_REVIEW_PROTOCOL.md` | backstage-only rationale; authored-runtime-reference input | possible `graph-authoring-heuristics.md` citations; may motivate generated edge-category/detail-form table scope but typed code remains the generated-reference input | Reader: data-model maintainers and future generated-reference authors. Blocker: live code/SPEC are authoritative; §0/§2–3/§9 are historical and `thesis → claim` did not land. | Mentions methods as validation lenses; preserve only as prompt/resource vocabulary where useful, never as user-changeable runtime axes. | Use as design-validation record for D87-L/D88-L, not as prompt payload. Pull only claims that still match current SPEC/code. | +| `docs/design/ELICITATION_LENSES.md` | authored-runtime-reference input; skill-local-reference input; historical/archive candidate | `proposal-meta-rubric.md`; `projection-guidance.md` | Reader: `generate-proposal` and future `project` capability. Blocker: D98-L retired `strategy` / `lens` / `method` as runtime state; A33-L still design-gates `project`. | Highly D98-sensitive: old lens catalogue must not reintroduce runtime lens/strategy/method axes. Fan-out/fan-in and D31 meta-rubric may survive as prompt conduct. | Harvest fan-out/fan-in, grounding-density, and meta-rubric ideas only into the relevant method/reference home after translating away runtime-axis assumptions. | +| `docs/design/BEHAVIORAL_KERNELS.md` | authored-runtime-reference input; historical/archive candidate | `checkability-ladder.md`; possible `elicitation-question-hints.md` | Reader: future elicitation/gap guidance if kernel prompts prove useful. Blocker: no current runtime kernel ontology; must not create a parallel data model or prompt taxonomy without a concrete reader. | Kernel terminology is interviewer machinery at most, not graph state and not runtime session state. | Defer. Mine only specific checkability/question patterns if a later scope names a reader. | +| `src/agents/skills/methods/capture/SKILL.md` | leave-as-is; authored-runtime-reference input; partially materialized | `graph-authoring-heuristics.md` materialized; `checkability-ladder.md` deferred | Reader: capture now cites the shared authoring reference for declarative graph claims, low-confidence routing, contradiction routing, relation-bearing confidence, and role-named mutation grammar. FE-861 sweep sequencing, gap conduct, and commitment-gradient table remain local. | Method is a prompt-resource id, not runtime state. No D98 issue while it stays code-owned prompt-resource conduct. | Materialized shared graph-authoring guidance; defer checkability-ladder extraction until a second concrete reader needs it. | +| `src/agents/skills/methods/commit-graph/SKILL.md` | leave-as-is; authored-runtime-reference input; materialized | `graph-authoring-heuristics.md` | Reader: graph-write methods needing declarative-node, promotion, settled-commitment, confident-endpoint, and role-named mutation discipline. | Method remains prompt-resource conduct; do not make it a user-changeable runtime mode. | Materialized shared authoring reference and cite from this method; remaining direct-commit sequencing stays local. | +| `src/agents/skills/methods/generate-proposal/SKILL.md` | leave-as-is; skill-local-reference input; authored-runtime-reference input | `proposal-meta-rubric.md`; `projection-guidance.md` | Reader: current generate method and future project design. Blocker: proposal meta-rubric might belong skill-local unless `project` becomes a second reader. | Names intent/design/oracle lenses/planes as prompt conduct; keep out of runtime state and schema fields. | Leave body unchanged now. Revisit after `elicitor-project` design chooses whether projection folds into generate or needs a distinct surface. | +| `src/agents/skills/methods/generate-proposal/references/intent.md` | leave-as-is; skill-local-reference input | `proposal-meta-rubric.md` only if shared beyond generate | Reader: generate intent-plane fan-out. Blocker: no second reader yet. | Plane-specific prompt payload is okay; do not turn `pick` into a schema/runtime field. | Leave in skill-local home. | +| `src/agents/skills/methods/generate-proposal/references/design.md` | leave-as-is; skill-local-reference input | `proposal-meta-rubric.md`; possible `projection-guidance.md` | Reader: generate design-plane fan-out and possible future project design. Blocker: A33-L design verdict. | `synthesize` is method conduct, not schema or runtime axis. | Leave in skill-local home; use as input to `project` design only if that frontier needs it. | +| `src/agents/skills/methods/generate-proposal/references/oracle.md` | leave-as-is; skill-local-reference input | `proposal-meta-rubric.md`; `checkability-ladder.md` | Reader: generate oracle-plane fan-out; possible future oracle/checkability guidance. Blocker: avoid mixing verification-strategy guidance with graph ontology unless a concrete citing need appears. | `compose` is method conduct, not schema or runtime axis. | Leave in skill-local home; selectively mine oracle-family/checkability phrasing if a later shared reference has multiple readers. | + +## Candidate reference queue + +```pseudo +tree context-reference-candidates: + graph-authoring-heuristics.md: + home: src/agents/contexts/references/ + status: materialized for capture + commit-graph shared authoring rules + readers: + - capture/SKILL.md + - commit-graph/SKILL.md + - future project/generate graph-draft guidance + included: + - declarative graph claims + - promotion before context + - settled commitment paths + - low-confidence and contradiction routing + - relation-bearing endpoint confidence + - role-named mutate_graph grammar + deferred: + - checkability ladder + - constraint/invariant subtype enums + - generated edge-category/detail-form tables + d98_guard: method vocabulary allowed only as prompt conduct + + checkability-ladder.md: + home: src/agents/contexts/references/ or skill-local oracle reference, depending on readers + readers: + - oracle generate/project guidance + - future elicitation question/gap guidance, if checkability becomes cross-method + likely inputs: + - recovered progressive checkability ladder + - BEHAVIORAL_KERNELS artifact shapes + - oracle reference's oracle-family / blind-spot guidance + blockers: + - decide whether the 8-rung ladder and strength field are ruled in or out by data-model-legibility acceptance + - keep criterion/check/vv_obligation vocabulary aligned with live schema + d98_guard: no new runtime lens/kernel state + + elicitation-question-hints.md: + home: src/agents/contexts/references/ only if multiple elicitor methods cite it + readers: + - capture / elicit-by-question / review-for-gaps candidates, pending scope + likely inputs: + - refreshed ELICITATION_QUESTIONS thesis and examples + - selected BEHAVIORAL_KERNELS question patterns, if still useful + blockers: + - refresh stale kind names and four-band model + - prevent catalog examples from becoming stored enums or hidden domain facts + d98_guard: examples are prompt hints, not strategy/lens/method runtime state + + proposal-meta-rubric.md: + home: skill-local generate-proposal reference unless project creates a second reader + readers: + - generate-proposal now + - possible project capability later + likely inputs: + - ELICITATION_LENSES fan-out/fan-in and D31 meta-rubric material + - generate-proposal SKILL shared candidate constraints + blockers: + - wait for elicitor-project design verdict before making shared runtime reference + d98_guard: pick/synthesize/compose remain conduct, not schema/runtime fields + + projection-guidance.md: + home: unresolved; likely after elicitor-project design + readers: + - future project capability + - maybe generate-proposal design/oracle references + likely inputs: + - ELICITATION_LENSES project-requirements-from-upstream material + - ONTOLOGY_REVIEW_PROTOCOL method-as-detail/routing rationale + blockers: + - A33-L project design verdict + - decide whether projection is generate-with-upstream-input or distinct surface + d98_guard: no revival of project strategy/lens/method as user-changeable state +``` + +## Runtime/backstage guardrails + +- This ledger is a pointer and disposition table, not a canonical ontology or prompt body. +- Generated tables must come from typed graph sources, not recovered prose or design docs. +- Authored references need concrete readers; otherwise leave material in the current skill-local or backstage home. +- D98-sensitive vocabulary is allowed only when it describes prompt-resource organization or internal conduct. It must not become session-agent state beyond SPEC/CODE operational mode. +- Rows are harvested one at a time. Do not bulk-import old design docs into runtime references. diff --git a/src/agents/skills/README.md b/src/agents/skills/README.md index a50a42a1b..e119b7465 100644 --- a/src/agents/skills/README.md +++ b/src/agents/skills/README.md @@ -38,6 +38,7 @@ The legal set is sealed by the code-owned path list in `agents/runtime/state.ts` - **`references/` subfiles:** available under the Agent Skills standard when a concrete skill needs progressive disclosure. No empty reference directories are introduced. The first materialized instance is `methods/generate-proposal/references/`, where the shared `SKILL.md` points to plane-specific payloads without advertising those payloads as separate skills. - **Shared typed-vocab context references:** materialized at `src/agents/contexts/references/graph-ontology.md` (kind→band table), the runtime-eligible shared context-reference home for vocabulary that prompt resources cite rather than restate (D97-L). Generated from the typed graph schema sources via `npm run generate:ontology` and drift-checked by `npm run check:data-model` (wired into `npm run check`); read-only and locked separately from the authored prompt-resource body lock below. Further vocabulary tables (edge categories, detail forms) are added to that file when a concrete citing need appears, not speculatively. +- **Shared authored context references:** materialized at `src/agents/contexts/references/graph-authoring-heuristics.md` when two or more prompt resources need the same judgment rules. These files cite generated vocabulary references for kind/band tables and carry only shared conduct; skill-specific sequencing stays in the owning `SKILL.md`. ## Prompt-resource body lock ledger diff --git a/src/agents/skills/__tests__/prompt-resources.test.ts b/src/agents/skills/__tests__/prompt-resources.test.ts index 0c2e81f52..ee52d0a4d 100644 --- a/src/agents/skills/__tests__/prompt-resources.test.ts +++ b/src/agents/skills/__tests__/prompt-resources.test.ts @@ -13,7 +13,20 @@ const resourceExpectations = [ }, { file: 'src/agents/skills/methods/capture/SKILL.md', - needles: ['single home', 'FE-861', 'Gap close/spawn responsibility belongs here'], + needles: [ + 'single home', + 'FE-861', + 'Gap close/spawn responsibility belongs here', + 'graph-authoring-heuristics.md', + ], + }, + { + file: 'src/agents/skills/methods/commit-graph/SKILL.md', + needles: ['graph-authoring-heuristics.md', 'role-named mutation grammar'], + }, + { + file: 'src/agents/contexts/references/graph-authoring-heuristics.md', + needles: ['Graph authoring heuristics', 'graph-ontology.md', 'low-confidence', 'mutate_graph'], }, { file: 'src/agents/skills/methods/generate-proposal/SKILL.md', @@ -84,12 +97,15 @@ describe('prompt-resource skills', () => { expect(readme).toContain('src/agents/contexts/references/graph-ontology.md'); expect(readme).toContain('concrete citing need appears'); expect(readme).toContain('drift-checked'); + expect(readme).toContain('Shared authored context references'); + expect(readme).toContain('src/agents/contexts/references/graph-authoring-heuristics.md'); }); it('records the shared context-reference and backstage curation homes', async () => { const contextsReadme = await readFile(join(projectRoot, 'src/agents/contexts/README.md'), 'utf8'); expect(contextsReadme).toContain('references/ runtime-eligible shared context references'); expect(contextsReadme).toContain('references/graph-ontology.md'); + expect(contextsReadme).toContain('references/graph-authoring-heuristics.md'); const docsReadme = await readFile(join(projectRoot, 'src/agents/docs/README.md'), 'utf8'); expect(docsReadme).toContain('backstage notes for curating Brunch-authored agent resources'); diff --git a/src/agents/skills/methods/capture/SKILL.md b/src/agents/skills/methods/capture/SKILL.md index ddb12157a..5ea1765be 100644 --- a/src/agents/skills/methods/capture/SKILL.md +++ b/src/agents/skills/methods/capture/SKILL.md @@ -21,7 +21,7 @@ chain capture-then-ask: ## Sweep frame -Walk the un-swept material once by readiness band and likely node kind. The canonical band order and per-kind band membership are the generated kind→band table in `src/agents/contexts/references/graph-ontology.md` (projected from the typed schema — cite it, do not restate it; D97-L). Conversational answers, ordinary user text, and acquisition digests are all sweep inputs. Large raw reads or tool results should be digested first; capture from the digest plus the conversation, not from unbounded raw bulk. +Walk the un-swept material once by readiness band and likely node kind. The canonical band order and per-kind band membership are the generated kind→band table in `src/agents/contexts/references/graph-ontology.md` (projected from the typed schema — cite it, do not restate it; D97-L). Shared graph-authoring judgment — declarative claims, low-confidence routing, contradiction routing, confident endpoints, and role-named mutation grammar — lives in `src/agents/contexts/references/graph-authoring-heuristics.md`. Conversational answers, ordinary user text, and acquisition digests are all sweep inputs. Large raw reads or tool results should be digested first; capture from the digest plus the conversation, not from unbounded raw bulk. Use the graph, gap, and reconciliation tools as the mutation boundary: @@ -32,7 +32,7 @@ Use the graph, gap, and reconciliation tools as the mutation boundary: | Manual gap disposition | `update_elicitation_gaps` `set_disposition` | one disposition write on the graph clock | | Contradiction with existing graph truth | `update_reconciliation_needs` `create` | one reconciliation need; records the impasse, never overwrites the conflicting node | -Do not invent graph payload fields, LSNs, result shapes, or capture-local edge syntax. Relation-bearing capture uses `mutate_graph` role fields such as `dependency/dependent`, `support/claim`, `abstract/concrete`, `boundary/subject`, and sibling category roles. +Do not invent graph payload fields, LSNs, result shapes, or capture-local edge syntax. Follow the role-named mutation grammar in `graph-authoring-heuristics.md`. ## Commitment gradient @@ -72,18 +72,7 @@ Structural gaps become answered from graph truth. Do not hand-set `answered` for ## Relation-bearing capture -Review captured nodes before adding edges: - -```pseudo -chain relation-capture: - candidate relation - -> check previous-band nodes - -> check likely upstream kinds - -> commit missing high-confidence nodes first - -> commit edge with role-named endpoints -``` - -If either endpoint is low-confidence, do not create the edge. Spawn or reuse a gap for the missing endpoint/relationship instead. +Review captured nodes before adding edges. Use `graph-authoring-heuristics.md` for the shared relation-bearing rule: commit missing high-confidence endpoints first, use role-named endpoints, and skip the edge when either endpoint is low-confidence. Spawn or reuse a gap for the missing endpoint/relationship instead. ## Anti-goals diff --git a/src/agents/skills/methods/commit-graph/SKILL.md b/src/agents/skills/methods/commit-graph/SKILL.md index e844cabce..957143921 100644 --- a/src/agents/skills/methods/commit-graph/SKILL.md +++ b/src/agents/skills/methods/commit-graph/SKILL.md @@ -7,13 +7,8 @@ description: "Commit graph truth only through Brunch graph tools and CommandExec Use this method only after the active posture and user exchange have established a legal direct-commit path. It is sequencing guidance for graph writes, not permission to treat every answer as durable truth. -Before committing, read enough selected-spec context to resolve existing projected codes and avoid duplicate or contradictory nodes. Decide the basis from the commitment path: explicit for direct user statements or approved review-set items, implicit for accepted concept-level materialization. Prepare one coherent batch of nodes and edges; edges must use the closed graph category set and justify stance where `witness`/`rationale` is used. +Before committing, read enough selected-spec context to resolve existing projected codes and avoid duplicate or contradictory nodes. Use `src/agents/contexts/references/graph-authoring-heuristics.md` for shared graph-authoring judgment: declarative claims, promotion before `context`, settled commitment paths, confident relation endpoints, and role-named mutation grammar. Prepare one coherent batch of nodes and edges; edges must use the closed graph category set and justify stance where `witness`/`rationale` is used. -## Authoring discipline - -- **No question kind — normalize interrogatives.** Every intent node is a declarative claim. When material arrives as a question ("Open question: …", "Should we …?", "Is X true?"), rewrite it into the underlying declarative claim before authoring: a possibly-false premise downstream depends on → `assumption`; how success will be judged → `criterion`; an open choice among alternatives → `context` stating the choice is unresolved (preserve wording in `body`); a follow-up with no stable claim yet → keep out of graph truth. When such a `context` later resolves, author a fresh `decision` and link `decision -[supersession]-> context`. -- **Promote before filing as `context`.** `context` is the last-resort descriptive bucket. Before filing one, check for promotion: must be true for success → `requirement`/`invariant`; limits acceptable solutions → `constraint`; may be false and matters → `assumption`; chooses among alternatives → `decision`; a bet about users/market/value → `thesis`; only aids interpretation → keep as `context`. - -Invoke `mutate_graph` when the batch can be validated atomically and the user-facing commitment is already settled. For direct agent commits in the current product posture, keep the batch create-only: `create_node` ops plus role-named `create_edge` ops. On `structural_illegal`, use diagnostics to repair and retry within the current method budget; do not expose half-written state or manually patch around CommandExecutor. On ambiguity, stop and ask or route through `generate-proposal`. +Invoke `mutate_graph` when the batch can be validated atomically and the user-facing commitment is already settled. For direct agent commits in the current product posture, keep the batch create-only per `graph-authoring-heuristics.md`: `create_node` ops plus role-named `create_edge` ops. On `structural_illegal`, use diagnostics to repair and retry within the current method budget; do not expose half-written state or manually patch around CommandExecutor. On ambiguity, stop and ask or route through `generate-proposal`. Compose this with `read-context` before the write and `capture` when the write follows a completed exchange. Out of scope: direct database writes, raw file edits, invented edge categories, partial acceptance, or using graph commits for workspace posture. From 1db43045aa2aada5a8be00143ac9afa411b40bfa Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Fri, 26 Jun 2026 14:28:12 +0200 Subject: [PATCH 47/54] Generate graph edge and detail reference tables --- memory/PLAN.md | 6 +- .../references/graph-authoring-heuristics.md | 8 +- .../contexts/references/graph-ontology.md | 45 +++++++++- src/agents/docs/context-reference-harvest.md | 6 +- .../__tests__/generate-ontology-ref.test.ts | 50 ++++++++++- src/graph/schema/generate-ontology-ref.ts | 86 ++++++++++++++++--- 6 files changed, 180 insertions(+), 21 deletions(-) diff --git a/memory/PLAN.md b/memory/PLAN.md index a8c39cb7f..4cf391ab4 100644 --- a/memory/PLAN.md +++ b/memory/PLAN.md @@ -82,7 +82,7 @@ context-pipeline/ - `orchestrator-tool-port` (FE-1087) — **scoped but D98-sensitive.** Port the external `brunch cook` orchestrator into the future CODE/executor tool surface rather than preserving a separate execute/orchestrator product mode. First active scope: `memory/cards/orchestrator-tool-port--plan-check-tool.md`; reconcile that scope against D98-L before build. - `elicitor-project` (FE-1085) — **design-gated.** Cross-plane derivation (requirements -> design, design -> oracles) remains undesigned under A33-L; run `ln-design` before any scope/build. -- `data-model-legibility` — **active.** Single active frontier for the SPEC-mode data-model/reference substrate and pattern-establishment sweep (D97-L/D98-L): generated graph-ontology references, authored graph-authoring/checkability/projection guidance, skill citation/pruning, and stale-doc disposition under `src/agents/`. Design verdict landed (Shape C); first tracer landed and its topology correction moved the generated kind→band table + `check:data-model` drift guard into `src/agents/contexts/references/graph-ontology.md`, cited by `methods/capture`. The shared authored `graph-authoring-heuristics.md` reference is now materialized and cited by `capture` + `commit-graph`. Remaining rows are evaluated one at a time: generated edge-category + detail-form tables and subtypes/checkability guidance. +- `data-model-legibility` — **active.** Single active frontier for the SPEC-mode data-model/reference substrate and pattern-establishment sweep (D97-L/D98-L): generated graph-ontology references, authored graph-authoring/checkability/projection guidance, skill citation/pruning, and stale-doc disposition under `src/agents/`. Design verdict landed (Shape C); first tracer landed and its topology correction moved the generated kind→band table + `check:data-model` drift guard into `src/agents/contexts/references/graph-ontology.md`, cited by `methods/capture`. The shared authored `graph-authoring-heuristics.md` reference is now materialized and cited by `capture` + `commit-graph`; generated edge-category + detail/form tables are now materialized from typed graph sources in `graph-ontology.md`. Remaining rows are evaluated one at a time: checkability guidance and subtypes/`detail` remodel review. - `renderer-golden-coverage` — **active parallel coverage track.** Remaining RENDER work lives by audience: model-facing context surfaces under `agents/contexts/`, human/product text beside its app/session owner. Remaining rows need fresh scoping against `src/agents/contexts/README.md`, `src/app/README.md`, and `src/session/README.md`. - `exchange-symmetry-audit` — **earned cleanup.** Delete-oriented audit of the exchange projection/renderer split; not a capability blocker. @@ -143,13 +143,13 @@ context-pipeline/ - **Linear:** tbd - **Branch:** tbd - **Kind:** structural / design + build -- **Status:** active; design verdict landed (`ln-design`: Shape C — two layers behind one index). First tracer-bullet **landed and topology-corrected**: generated kind→band table at `src/agents/contexts/references/graph-ontology.md` + `check:data-model` drift guard (wired into `npm run check`), cited by `methods/capture`, with packaged runtime asset copy for `contexts/references/`. Load-bearing claim 1 (typed `graph/schema` sources are the closed, importable vocabulary set — D73-L) validated by the generator while `src/agents/contexts/references/` is now the runtime-eligible reference home. The authored graph-authoring heuristics row is materialized at `src/agents/contexts/references/graph-authoring-heuristics.md` and cited by `capture` + `commit-graph`; checkability/subtype material remains deferred. Remaining: edge-category + detail-form tables, checkability guidance verdict, and the subtypes→`detail` remodel review. +- **Status:** active; design verdict landed (`ln-design`: Shape C — two layers behind one index). First tracer-bullet **landed and topology-corrected**: generated kind→band table at `src/agents/contexts/references/graph-ontology.md` + `check:data-model` drift guard (wired into `npm run check`), cited by `methods/capture`, with packaged runtime asset copy for `contexts/references/`. Load-bearing claim 1 (typed `graph/schema` sources are the closed, importable vocabulary set — D73-L) validated by the generator while `src/agents/contexts/references/` is now the runtime-eligible reference home. The authored graph-authoring heuristics row is materialized at `src/agents/contexts/references/graph-authoring-heuristics.md` and cited by `capture` + `commit-graph`; generated edge-category + detail/form tables are materialized in `graph-ontology.md` from `kinds.ts`, `nodes.ts`, and `category-policy.ts`. Remaining: checkability guidance verdict and the subtypes→`detail` remodel review. - **Certainty:** proving. - **Current execution pointer:** none active — re-scope the next slice (authored judgment layer, or further generated tables). - **Objective:** Recover + reconcile the retired `INTENT_GRAPH_SEMANTICS` content and adjacent heuristic docs into one SPEC-mode data-model reasoning substrate under `src/agents/`: runtime-eligible references in `src/agents/contexts/references/`, backstage curation notes in `src/agents/docs/`, and pruned/cited skill bodies. Generate the closed-vocabulary tables (planes / kinds / bands / edge-category policy / `detail` schemas) from typed graph sources so heuristics are **cited** (D97-L), not inlined and duplicated across skill bodies; align the result with D98-L's mode-only runtime posture. - **Acceptance:** - ✓ `ln-design` produced ≥3 module shapes for the home + generation seam with a recommendation (Shape C), before any doc/script. - - The canonical-truth boundary is decided: what is generated from `kinds.ts` / `nodes.ts` / `category-policy.ts` vs authored judgment. (Direction set by Shape C; kind→band table materialized in `src/agents/contexts/references/graph-ontology.md`, remaining tables pending.) + - ✓ The canonical-truth boundary is decided: what is generated from `kinds.ts` / `nodes.ts` / `category-policy.ts` vs authored judgment. Kind→band, edge-category policy, required detail, and `detail.form` tables are materialized in `src/agents/contexts/references/graph-ontology.md`. - Subtypes/`detail` modelling review: each retired subtype family sorted into `kind` (behavior-bearing), `detail` facet (inert classification), or already-covered; decide whether an inert `detail` facet dimension earns its carrying cost given the kind/band/form machinery already discriminates. - The two capture gaps are explicitly ruled in or out: constraint/invariant subtype enums; the 8-rung checkability ladder + `strength`. - Skill bodies cite the new home (D97-L); inlined heuristic copies collapse to one cite-target. `capture` + `commit-graph` now cite `graph-authoring-heuristics.md` for shared graph-authoring judgment. diff --git a/src/agents/contexts/references/graph-authoring-heuristics.md b/src/agents/contexts/references/graph-authoring-heuristics.md index c6b834c73..008b8a1ab 100644 --- a/src/agents/contexts/references/graph-authoring-heuristics.md +++ b/src/agents/contexts/references/graph-authoring-heuristics.md @@ -1,6 +1,6 @@ # Graph authoring heuristics -Runtime-eligible shared reference for graph-writing judgment (D97-L/D98-L). Use this for authoring discipline that is shared by `capture` and `commit-graph`; use `graph-ontology.md` for generated kind/band vocabulary instead of restating kind tables here. +Runtime-eligible shared reference for graph-writing judgment (D97-L/D98-L). Use this for authoring discipline that is shared by `capture` and `commit-graph`; use `graph-ontology.md` for generated kind/band, edge-category, and detail/form vocabulary instead of restating tables here. ## Author declarative graph claims @@ -32,8 +32,12 @@ chain relation-bearing-authoring: -> use role-named mutate_graph endpoints ``` -Do not use capture-local or prose-local edge dialects. `mutate_graph` edges use role fields such as `dependency/dependent`, `support/claim`, `abstract/concrete`, and `boundary/subject`; diagnostics from `structural_illegal` are the repair path. +Do not use capture-local or prose-local edge dialects. `graph-ontology.md` lists the generated edge-category policy table; `mutate_graph` edges use role fields such as `dependency/dependent`, `support/claim`, `abstract/concrete`, and `boundary/subject`; diagnostics from `structural_illegal` are the repair path. ## Keep mutation grammar role-named Prepare one coherent `mutate_graph` batch when the user-facing commitment is already settled. Prefer create-only direct commits in the current product posture: `create_node` ops plus role-named `create_edge` ops. Do not invent graph payload fields, LSNs, edge categories, result shapes, or partial-write recovery paths. + +## Treat detail.form as inert payload + +Use `graph-ontology.md` for the generated required-detail and allowed-form tables. Node `kind` drives graph behavior; `detail.form` is only method payload plus a renderer hook. Do not infer edge legality, readiness, commitment strength, or runtime method state from `form`. diff --git a/src/agents/contexts/references/graph-ontology.md b/src/agents/contexts/references/graph-ontology.md index b636b7b1c..afbc78831 100644 --- a/src/agents/contexts/references/graph-ontology.md +++ b/src/agents/contexts/references/graph-ontology.md @@ -2,7 +2,7 @@ # Graph ontology reference -Projected from `src/graph/schema/kinds.ts` and `nodes.ts` — the source of truth for graph vocabulary (D73-L). Regenerate with `npm run generate:ontology`; drift is caught by `npm run check:data-model`. Do not hand-edit. +Projected from `src/graph/schema/kinds.ts`, `nodes.ts`, and `graph/policy/category-policy.ts` — the source of truth for graph vocabulary and edge policy (D73-L). Regenerate with `npm run generate:ontology`; drift is caught by `npm run check:data-model`. Do not hand-edit. ### Node kind → readiness band @@ -34,3 +34,46 @@ Kinds appear in canonical plane order (intent, oracle, design, plan). A band cel | milestone | M | commitment | | frontier | F | commitment | | slice | S | commitment | + +### Edge category policy + +Role-named endpoints are the fields authors use in `mutate_graph`; impact and projection flags come from edge-category metadata, not storage direction (D51-L/D87-L). + +| Category | Endpoint roles | Affected endpoint | Impact strength | Stance | Criteria help? | Projection effect | +| - | - | - | - | - | - | - | +| dependency | dependency → dependent | target | cascade | — | no | none | +| witness | oracle → claim | source | advisory | required | yes | none | +| rationale | support → claim | source | advisory | required | no | none | +| realization | abstract → concrete | target | advisory | — | no | none | +| refinement | abstract → concrete | target | advisory | — | no | none | +| exclusion | boundary → subject | target | advisory | — | no | none | +| composition | whole → part | source | advisory | — | no | none | +| cross_reference | peer → peer | — | none | — | no | none | +| supersession | successor → predecessor | source | advisory | — | no | hide_predecessor_from_active_context | + +### Node detail payloads + +Only these node kinds require non-form detail payloads. Other per-kind behavior comes from `kind`, not detail shape (D54-L/D88-L). + +| Kind | Payload | Fields | +| - | - | - | +| decision | Detail required for decision nodes. | chosen_option, rejected, rationale | +| term | Detail required for term nodes. | definition, aliases | + +### Claim detail.form payloads + +`kind` drives graph behavior; `detail.form` is inert method payload plus a renderer hook (D88-L). + +| Kind | Allowed forms | +| - | - | +| requirement | plain, gherkin, formal | +| criterion | plain, gherkin, formal | +| invariant | plain, gherkin, formal | +| context | given | + +| Form | Payload | Fields | +| - | - | - | +| plain | Plain claim form. | form | +| gherkin | Gherkin Given/When/Then payload. | form, given, when, then | +| formal | Formal verification payload. | form, language, statement | +| given | Axiom/given payload on a context node. | form, statement | diff --git a/src/agents/docs/context-reference-harvest.md b/src/agents/docs/context-reference-harvest.md index 753855480..a925325a5 100644 --- a/src/agents/docs/context-reference-harvest.md +++ b/src/agents/docs/context-reference-harvest.md @@ -21,7 +21,7 @@ A source may carry more than one disposition class when it has separable uses. T | Source | Disposition labels | Candidate future reference | Reader / blocker | D98-sensitive notes | Next action | | - | - | - | - | - | - | -| `/private/tmp/igs_recovered.md` (`INTENT_GRAPH_SEMANTICS`) | authored-runtime-reference input; backstage-only rationale; historical/archive candidate | `graph-authoring-heuristics.md`; `checkability-ladder.md`; may motivate generated edge-category/detail-form table scope but is not their generated-reference input | Reader: capture/commit/generate methods needing graph vocabulary and graph-authoring judgment. Blocker: reconcile every subtype/checkability claim against live `src/graph/schema/{kinds,nodes}.ts`, `src/graph/policy/category-policy.ts`, D87-L, D88-L, D94-L before accepting it. | Contains retired subtype proposals and old edge/ontology language; do not revive stale modality/subtype claims or create runtime `strategy` / `lens` / `method` session state. | Derive generated tables only from current typed sources; separately review promotion rules, checkability ladder, and subtype material as authored judgment rows. | +| `/private/tmp/igs_recovered.md` (`INTENT_GRAPH_SEMANTICS`) | authored-runtime-reference input; backstage-only rationale; historical/archive candidate | `graph-authoring-heuristics.md`; `checkability-ladder.md`; generated edge-category/detail-form table scope is materialized from typed sources only | Reader: capture/commit/generate methods needing graph vocabulary and graph-authoring judgment. Blocker: reconcile every subtype/checkability claim against live `src/graph/schema/{kinds,nodes}.ts`, `src/graph/policy/category-policy.ts`, D87-L, D88-L, D94-L before accepting it. | Contains retired subtype proposals and old edge/ontology language; do not revive stale modality/subtype claims or create runtime `strategy` / `lens` / `method` session state. | Generated edge/detail/form tables now derive from current typed sources; separately review promotion rules, checkability ladder, and subtype material as authored judgment rows. | | `docs/design/ELICITATION_QUESTIONS.md` | authored-runtime-reference input | `elicitation-question-hints.md` | Reader: future elicitor question/gap guidance. Blocker: refresh against post-FE-1052 kind names, `story` / `unknown` / `entity` / `sketch`, and four-band D94-L model. | Uses older band framing and mentions strategy/lens as prompt-space terms; keep as prompt-resource vocabulary only, not runtime state. | Treat the durable thesis as: node kind is closed ontology; questions are open/projectable hints inside a kind. Rewrite examples before model-facing use. | | `docs/design/ONTOLOGY_REVIEW_PROTOCOL.md` | backstage-only rationale; authored-runtime-reference input | possible `graph-authoring-heuristics.md` citations; may motivate generated edge-category/detail-form table scope but typed code remains the generated-reference input | Reader: data-model maintainers and future generated-reference authors. Blocker: live code/SPEC are authoritative; §0/§2–3/§9 are historical and `thesis → claim` did not land. | Mentions methods as validation lenses; preserve only as prompt/resource vocabulary where useful, never as user-changeable runtime axes. | Use as design-validation record for D87-L/D88-L, not as prompt payload. Pull only claims that still match current SPEC/code. | | `docs/design/ELICITATION_LENSES.md` | authored-runtime-reference input; skill-local-reference input; historical/archive candidate | `proposal-meta-rubric.md`; `projection-guidance.md` | Reader: `generate-proposal` and future `project` capability. Blocker: D98-L retired `strategy` / `lens` / `method` as runtime state; A33-L still design-gates `project`. | Highly D98-sensitive: old lens catalogue must not reintroduce runtime lens/strategy/method axes. Fan-out/fan-in and D31 meta-rubric may survive as prompt conduct. | Harvest fan-out/fan-in, grounding-density, and meta-rubric ideas only into the relevant method/reference home after translating away runtime-axis assumptions. | @@ -54,7 +54,7 @@ tree context-reference-candidates: deferred: - checkability ladder - constraint/invariant subtype enums - - generated edge-category/detail-form tables + - generated edge-category/detail-form tables (materialized in graph-ontology.md) d98_guard: method vocabulary allowed only as prompt conduct checkability-ladder.md: @@ -112,7 +112,7 @@ tree context-reference-candidates: ## Runtime/backstage guardrails - This ledger is a pointer and disposition table, not a canonical ontology or prompt body. -- Generated tables must come from typed graph sources, not recovered prose or design docs. +- Generated kind/band, edge-category, and detail/form tables must come from typed graph sources, not recovered prose or design docs. - Authored references need concrete readers; otherwise leave material in the current skill-local or backstage home. - D98-sensitive vocabulary is allowed only when it describes prompt-resource organization or internal conduct. It must not become session-agent state beyond SPEC/CODE operational mode. - Rows are harvested one at a time. Do not bulk-import old design docs into runtime references. diff --git a/src/graph/schema/__tests__/generate-ontology-ref.test.ts b/src/graph/schema/__tests__/generate-ontology-ref.test.ts index 062af0045..4200619fd 100644 --- a/src/graph/schema/__tests__/generate-ontology-ref.test.ts +++ b/src/graph/schema/__tests__/generate-ontology-ref.test.ts @@ -2,12 +2,18 @@ import { readFileSync } from 'node:fs'; import { describe, expect, it } from 'vitest'; +import { EDGE_CATEGORY_METADATA } from '../../policy/category-policy.js'; import { GENERATED_ONTOLOGY_PATH, renderOntologyReference } from '../generate-ontology-ref.js'; const projectRoot = new URL('../../../../', import.meta.url).pathname; const expectedReferencePath = `${projectRoot}src/agents/contexts/references/graph-ontology.md`; -import { NODE_KINDS } from '../kinds.js'; -import { bandsForKind } from '../nodes.js'; +import { EDGE_CATEGORIES, NODE_KINDS } from '../kinds.js'; +import { + bandsForKind, + CLAIM_FORM_JSON_SCHEMAS, + NODE_DETAIL_FORMS, + NODE_DETAIL_JSON_SCHEMAS, +} from '../nodes.js'; describe('ontology reference generator', () => { const markdown = renderOntologyReference(); @@ -30,8 +36,48 @@ describe('ontology reference generator', () => { it('renders the reference in the shared context-reference shape', () => { expect(markdown).toContain('@generated by src/graph/schema/generate-ontology-ref.ts'); expect(markdown.split('\n')).toContain('### Node kind → readiness band'); + expect(markdown.split('\n')).toContain('### Edge category policy'); + expect(markdown.split('\n')).toContain('### Node detail payloads'); + expect(markdown.split('\n')).toContain('### Claim detail.form payloads'); expect(markdown.split('\n')).not.toContain('## Node kind → readiness band'); expect(markdown).toContain('Kinds appear in canonical plane order'); + expect(markdown).toContain( + '`kind` drives graph behavior; `detail.form` is inert method payload plus a renderer hook (D88-L).', + ); + }); + + it('lists every edge category with role-named endpoints and policy flags from the typed source', () => { + for (const category of EDGE_CATEGORIES) { + const metadata = EDGE_CATEGORY_METADATA[category]; + const row = rows.find((line) => line.startsWith(`| ${category} |`)); + expect(row, `row for edge category ${category}`).toBeDefined(); + expect(row).toContain(`${metadata.sourceRole} → ${metadata.targetRole}`); + expect(row).toContain(metadata.affected ?? '—'); + expect(row).toContain(metadata.impactKind); + expect(row).toContain(metadata.stanceRequired ? 'required' : '—'); + expect(row).toContain(metadata.criteriaHelpSignal ? 'yes' : 'no'); + expect(row).toContain(metadata.projectionEffect); + } + }); + + it('lists required detail kinds and allowed detail.form payload fields from the typed source', () => { + for (const [kind, schema] of Object.entries(NODE_DETAIL_JSON_SCHEMAS)) { + const expectedFields = Object.keys(schema.properties).join(', '); + const row = rows.find((line) => line.startsWith(`| ${kind} |`) && line.includes(expectedFields)); + expect(row, `required detail row for ${kind}`).toBeDefined(); + } + + for (const [kind, forms] of Object.entries(NODE_DETAIL_FORMS)) { + const expectedForms = forms.join(', '); + const row = rows.find((line) => line.startsWith(`| ${kind} |`) && line.includes(expectedForms)); + expect(row, `detail.form row for ${kind}`).toBeDefined(); + } + + for (const [form, schema] of Object.entries(CLAIM_FORM_JSON_SCHEMAS)) { + const expectedFields = Object.keys(schema.properties).join(', '); + const row = rows.find((line) => line.startsWith(`| ${form} |`) && line.includes(expectedFields)); + expect(row, `form row for ${form}`).toBeDefined(); + } }); it('keeps the committed generated file in sync with the typed source (drift guard)', () => { diff --git a/src/graph/schema/generate-ontology-ref.ts b/src/graph/schema/generate-ontology-ref.ts index 1f48469f1..5f7c95f77 100644 --- a/src/graph/schema/generate-ontology-ref.ts +++ b/src/graph/schema/generate-ontology-ref.ts @@ -4,9 +4,9 @@ * instead of restating it (D97-L). The typed sources in this directory stay the * source of truth (D73-L); the emitted file is never hand-edited. * - * This slice generates only the kind→band table; further vocabulary tables - * (edge categories, detail forms) are added when a concrete citing need appears, - * not speculatively. + * Generates the compact vocabulary tables that prompt resources cite: node + * kind→band, edge-category policy, required node detail, and inert detail.form + * payloads. * * CLI (dev only, run via tsx): * npm run generate:ontology # write src/agents/contexts/references/graph-ontology.md @@ -18,8 +18,19 @@ import { dirname, join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import { joinMarkdownBlocks, markdownHeading, markdownTable } from '../../agents/shared/markdown.js'; -import { NODE_KINDS } from './kinds.js'; -import { bandsForKind, NODE_KIND_METADATA } from './nodes.js'; +import { EDGE_CATEGORY_METADATA } from '../policy/category-policy.js'; +import { EDGE_CATEGORIES, NODE_KINDS } from './kinds.js'; +import { + bandsForKind, + claimFormKnownFields, + CLAIM_FORM_JSON_SCHEMAS, + NODE_DETAIL_JSON_SCHEMAS, + NODE_KIND_METADATA, + nodeDetailForms, + nodeDetailKnownFields, + NODE_KINDS_REQUIRING_DETAIL, + NODE_KINDS_WITH_FORM_DETAIL, +} from './nodes.js'; const SCHEMA_DIR = dirname(fileURLToPath(import.meta.url)); const PROJECT_ROOT = resolve(SCHEMA_DIR, '../../..'); @@ -29,20 +40,75 @@ export const GENERATED_ONTOLOGY_PATH = join(PROJECT_ROOT, 'src/agents/contexts/r const GENERATED_NOTICE = ''; +function yesNo(value: boolean): string { + return value ? 'yes' : 'no'; +} + +function dashWhenEmpty(value: readonly string[]): string { + return value.length > 0 ? value.join(', ') : '—'; +} + export function renderOntologyReference(): string { - const rows = NODE_KINDS.map((kind) => { + const nodeKindRows = NODE_KINDS.map((kind) => { const bands = bandsForKind(kind); - const cell = bands.length > 0 ? bands.join(', ') : '—'; - return [kind, NODE_KIND_METADATA[kind].label, cell]; + return [kind, NODE_KIND_METADATA[kind].label, dashWhenEmpty(bands)]; }); + const edgeCategoryRows = EDGE_CATEGORIES.map((category) => { + const metadata = EDGE_CATEGORY_METADATA[category]; + return [ + category, + `${metadata.sourceRole} → ${metadata.targetRole}`, + metadata.affected ?? '—', + metadata.impactKind, + metadata.stanceRequired ? 'required' : '—', + yesNo(metadata.criteriaHelpSignal), + metadata.projectionEffect, + ]; + }); + + const requiredDetailRows = NODE_KINDS_REQUIRING_DETAIL.map((kind) => [ + kind, + NODE_DETAIL_JSON_SCHEMAS[kind].description as string, + nodeDetailKnownFields(kind).join(', '), + ]); + + const allowedFormRows = NODE_KINDS_WITH_FORM_DETAIL.map((kind) => [kind, nodeDetailForms(kind).join(', ')]); + + const formPayloadRows = Object.keys(CLAIM_FORM_JSON_SCHEMAS).map((form) => [ + form, + CLAIM_FORM_JSON_SCHEMAS[form as keyof typeof CLAIM_FORM_JSON_SCHEMAS].description as string, + claimFormKnownFields(form as keyof typeof CLAIM_FORM_JSON_SCHEMAS).join(', '), + ]); + return `${joinMarkdownBlocks( GENERATED_NOTICE, markdownHeading(1, 'Graph ontology reference'), - 'Projected from `src/graph/schema/kinds.ts` and `nodes.ts` — the source of truth for graph vocabulary (D73-L). Regenerate with `npm run generate:ontology`; drift is caught by `npm run check:data-model`. Do not hand-edit.', + 'Projected from `src/graph/schema/kinds.ts`, `nodes.ts`, and `graph/policy/category-policy.ts` — the source of truth for graph vocabulary and edge policy (D73-L). Regenerate with `npm run generate:ontology`; drift is caught by `npm run check:data-model`. Do not hand-edit.', markdownHeading(3, 'Node kind → readiness band'), 'Kinds appear in canonical plane order (intent, oracle, design, plan). A band cell of `—` means the kind carries no readiness band (D94-L).', - markdownTable([['Kind', 'Code', 'Readiness bands'], ...rows]), + markdownTable([['Kind', 'Code', 'Readiness bands'], ...nodeKindRows]), + markdownHeading(3, 'Edge category policy'), + 'Role-named endpoints are the fields authors use in `mutate_graph`; impact and projection flags come from edge-category metadata, not storage direction (D51-L/D87-L).', + markdownTable([ + [ + 'Category', + 'Endpoint roles', + 'Affected endpoint', + 'Impact strength', + 'Stance', + 'Criteria help?', + 'Projection effect', + ], + ...edgeCategoryRows, + ]), + markdownHeading(3, 'Node detail payloads'), + 'Only these node kinds require non-form detail payloads. Other per-kind behavior comes from `kind`, not detail shape (D54-L/D88-L).', + markdownTable([['Kind', 'Payload', 'Fields'], ...requiredDetailRows]), + markdownHeading(3, 'Claim detail.form payloads'), + '`kind` drives graph behavior; `detail.form` is inert method payload plus a renderer hook (D88-L).', + markdownTable([['Kind', 'Allowed forms'], ...allowedFormRows]), + markdownTable([['Form', 'Payload', 'Fields'], ...formPayloadRows]), )}\n`; } From 99f47ab2c3cca6fe2dbef339b572c39e6cf0c786 Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Fri, 26 Jun 2026 14:51:09 +0200 Subject: [PATCH 48/54] Enforce context import boundary --- .oxlintrc.json | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/.oxlintrc.json b/.oxlintrc.json index aabbe15d7..11b142cb3 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -50,6 +50,28 @@ ] } }, + { + "files": ["src/agents/contexts/**/*.ts", "src/agents/contexts/**/*.tsx"], + "rules": { + "no-restricted-imports": [ + "error", + { + "patterns": [ + { + "group": [ + "**/.pi/**", + "**/app/**", + "**/db/**", + "**/rpc/**", + "**/web/**" + ], + "message": "D52-L/D83-L: agents/contexts/ is model-facing render code; it must not import adapters, app entrypoints, transports, web, or db." + } + ] + } + ] + } + }, { "files": ["src/graph/**", "src/db/**"], "rules": { "no-restricted-imports": "off" } From 604ef803504f5b9262dd3450624cc515f92da980 Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Fri, 26 Jun 2026 14:51:42 +0200 Subject: [PATCH 49/54] Reconcile ontology reference docs --- src/agents/skills/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/agents/skills/README.md b/src/agents/skills/README.md index e119b7465..9b07f64a7 100644 --- a/src/agents/skills/README.md +++ b/src/agents/skills/README.md @@ -37,7 +37,7 @@ The legal set is sealed by the code-owned path list in `agents/runtime/state.ts` ## Prompt-resource sub-shapes - **`references/` subfiles:** available under the Agent Skills standard when a concrete skill needs progressive disclosure. No empty reference directories are introduced. The first materialized instance is `methods/generate-proposal/references/`, where the shared `SKILL.md` points to plane-specific payloads without advertising those payloads as separate skills. -- **Shared typed-vocab context references:** materialized at `src/agents/contexts/references/graph-ontology.md` (kind→band table), the runtime-eligible shared context-reference home for vocabulary that prompt resources cite rather than restate (D97-L). Generated from the typed graph schema sources via `npm run generate:ontology` and drift-checked by `npm run check:data-model` (wired into `npm run check`); read-only and locked separately from the authored prompt-resource body lock below. Further vocabulary tables (edge categories, detail forms) are added to that file when a concrete citing need appears, not speculatively. +- **Shared typed-vocab context references:** materialized at `src/agents/contexts/references/graph-ontology.md`, the runtime-eligible shared context-reference home for generated node-kind/band, edge-policy, detail-payload, and `detail.form` vocabulary that prompt resources cite rather than restate (D97-L). Generated from the typed graph schema sources via `npm run generate:ontology` and drift-checked by `npm run check:data-model` (wired into `npm run check`); read-only and locked separately from the authored prompt-resource body lock below. - **Shared authored context references:** materialized at `src/agents/contexts/references/graph-authoring-heuristics.md` when two or more prompt resources need the same judgment rules. These files cite generated vocabulary references for kind/band tables and carry only shared conduct; skill-specific sequencing stays in the owning `SKILL.md`. ## Prompt-resource body lock ledger From da51899871ea3a5d53a507ab128593eed3c0eab1 Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Fri, 26 Jun 2026 14:55:47 +0200 Subject: [PATCH 50/54] Delete legacy edge repair bridge --- src/graph/__tests__/command-executor.test.ts | 75 -------------------- src/graph/command-executor.ts | 72 ------------------- src/graph/command-executor/command-types.ts | 15 ---- src/graph/workspace-store.ts | 1 - 4 files changed, 163 deletions(-) diff --git a/src/graph/__tests__/command-executor.test.ts b/src/graph/__tests__/command-executor.test.ts index 47fe6410f..93eb97a14 100644 --- a/src/graph/__tests__/command-executor.test.ts +++ b/src/graph/__tests__/command-executor.test.ts @@ -8,11 +8,9 @@ import { eq } from 'drizzle-orm'; import { describe, expect, it, beforeEach } from 'vitest'; -import { formatGraphOverview } from '../../agents/contexts/graph/graph-slice.js'; import { createDb, type BrunchDb } from '../../db/connection.js'; import { changeLog, - edges, elicitationGaps, graphClock, nodeKindCounters, @@ -21,7 +19,6 @@ import { specs, } from '../../db/schema.js'; import { CommandExecutor } from '../command-executor.js'; -import { queryGraph } from '../queries.js'; import { runCreateOnlyMutation } from './support/create-only-mutation.js'; function createTestDb(): BrunchDb { @@ -742,78 +739,6 @@ describe('CommandExecutor', () => { }); }); - it('repairs retired edge categories before graph renderers project impact direction', () => { - const source = executor.createNode({ - specId, - plane: 'intent', - kind: 'requirement', - title: 'Requirement', - }); - const target = executor.createNode({ - specId, - plane: 'intent', - kind: 'constraint', - title: 'Constraint', - }); - if (source.status !== 'success' || target.status !== 'success') throw new Error('unreachable'); - - db.insert(edges) - .values([ - { - spec_id: specId, - category: 'support' as never, - source_id: source.nodeId, - target_id: target.nodeId, - stance: 'for', - basis: 'explicit', - created_at_lsn: 0, - updated_at_lsn: 0, - }, - { - spec_id: specId, - category: 'boundary' as never, - source_id: target.nodeId, - target_id: source.nodeId, - basis: 'explicit', - created_at_lsn: 0, - updated_at_lsn: 0, - }, - ]) - .run(); - - expect(() => formatGraphOverview(queryGraph(db, specId, undefined, { visibility: 'all' }))).toThrow( - /affected/u, - ); - - const repair = executor.repairLegacyEdgeCategories(); - - expect(repair).toMatchObject({ - status: 'success', - repairedSpecs: [ - { - specId, - renamedCounts: { - boundary: 1, - support: 1, - }, - }, - ], - }); - expect( - db - .select({ category: edges.category, stance: edges.stance }) - .from(edges) - .all() - .sort((left, right) => left.category.localeCompare(right.category)), - ).toEqual([ - { category: 'exclusion', stance: null }, - { category: 'rationale', stance: 'for' }, - ]); - expect(formatGraphOverview(queryGraph(db, specId, undefined, { visibility: 'all' }))).toContain( - 'edges (sorted by upstream)', - ); - }); - it('repairs a floor-predating spec with the missing manual situating gap', () => { const legacy = executor.createSpec({ name: 'Legacy Spec', slug: 'legacy-spec' }); expect(legacy.status).toBe('success'); diff --git a/src/graph/command-executor.ts b/src/graph/command-executor.ts index ca9d7362c..0b0063e7b 100644 --- a/src/graph/command-executor.ts +++ b/src/graph/command-executor.ts @@ -33,8 +33,6 @@ import type { CreateReconNeedResult, CreateSpecInput, CreateSpecResult, - RepairLegacyEdgeCategoriesResult, - RepairLegacyEdgeCategoriesSpecResult, RepairSeededElicitationGapsResult, RepairSeededElicitationGapsSpecResult, ResolveReconNeedInput, @@ -59,7 +57,6 @@ import type { } from './command-executor/graph-mutation-types.js'; import { writeGraphMutation } from './command-executor/graph-mutation-writer.js'; import { translateReviewSetPayloadToMutateGraph } from './review-set.js'; -import type { EdgeCategory } from './schema/edges.js'; import type { ElicitationGapLensAffinity, GapPredicate } from './schema/elicitation-gaps.js'; import type { ReadinessBand } from './schema/kinds.js'; import { type NodeBasis, type NodeKind, type NodePlane } from './schema/nodes.js'; @@ -91,8 +88,6 @@ export type { CreateReconNeedResult, CreateSpecInput, CreateSpecResult, - RepairLegacyEdgeCategoriesResult, - RepairLegacyEdgeCategoriesSpecResult, RepairSeededElicitationGapsResult, RepairSeededElicitationGapsSpecResult, ResolveReconNeedInput, @@ -200,16 +195,6 @@ const SEEDED_ELICITATION_GAPS: readonly { }, ] as const; -const LEGACY_EDGE_CATEGORY_RENAMES: readonly { - readonly from: string; - readonly to: EdgeCategory; -}[] = [ - { from: 'proof', to: 'witness' }, - { from: 'support', to: 'rationale' }, - { from: 'boundary', to: 'exclusion' }, - { from: 'association', to: 'cross_reference' }, -]; - function seededElicitationGapKey(seed: { readonly refersTo: string; readonly question: string; @@ -405,63 +390,6 @@ export class CommandExecutor { }); } - /** Repair local rows written before the D87-L edge-category rename batch. */ - repairLegacyEdgeCategories(): RepairLegacyEdgeCategoriesResult { - return this.db.transaction((tx) => { - const renamedCountsBySpec = new Map>(); - const legacyCategories = new Set(LEGACY_EDGE_CATEGORY_RENAMES.map((rename) => rename.from)); - const edgeRows = tx - .select({ - specId: schema.edges.spec_id, - category: schema.edges.category, - }) - .from(schema.edges) - .all(); - - for (const row of edgeRows) { - const category = String(row.category); - if (!legacyCategories.has(category)) continue; - const counts = renamedCountsBySpec.get(row.specId) ?? new Map(); - counts.set(category, (counts.get(category) ?? 0) + 1); - renamedCountsBySpec.set(row.specId, counts); - } - - if (renamedCountsBySpec.size === 0) { - return { status: 'success' as const, repairedSpecs: [] }; - } - - for (const rename of LEGACY_EDGE_CATEGORY_RENAMES) { - tx.update(schema.edges) - .set({ category: rename.to }) - // The left side is typed as the current enum, but local SQLite rows may - // still contain retired literals from before D87-L. - .where(eq(schema.edges.category, rename.from as EdgeCategory)) - .run(); - } - - const repairedSpecs: RepairLegacyEdgeCategoriesSpecResult[] = []; - for (const [specId, counts] of renamedCountsBySpec) { - const lsn = this.bumpExistingSpecLsn(tx, specId); - const renamedCounts = Object.fromEntries(counts.entries()); - tx.insert(schema.changeLog) - .values({ - spec_id: specId, - lsn, - operation: 'repair_legacy_edge_categories', - payload: JSON.stringify({ - specId, - renamedCounts, - renames: LEGACY_EDGE_CATEGORY_RENAMES, - }), - }) - .run(); - repairedSpecs.push({ specId, renamedCounts, lsn }); - } - - return { status: 'success' as const, repairedSpecs }; - }); - } - /** Create an elicitation gap through the command boundary. */ createElicitationGap(input: CreateElicitationGapInput): CreateElicitationGapResult { const diagnostics = validateCreateElicitationGap(input); diff --git a/src/graph/command-executor/command-types.ts b/src/graph/command-executor/command-types.ts index ea9134d7f..7fdc7b130 100644 --- a/src/graph/command-executor/command-types.ts +++ b/src/graph/command-executor/command-types.ts @@ -82,17 +82,6 @@ interface RepairSeededElicitationGapsSuccess { readonly repairedSpecs: readonly RepairSeededElicitationGapsSpecResult[]; } -export interface RepairLegacyEdgeCategoriesSpecResult { - readonly specId: number; - readonly renamedCounts: Readonly>; - readonly lsn: number; -} - -interface RepairLegacyEdgeCategoriesSuccess { - readonly status: 'success'; - readonly repairedSpecs: readonly RepairLegacyEdgeCategoriesSpecResult[]; -} - /** Spec row returned by CommandExecutor reads. */ export interface SpecRecord { readonly id: number; @@ -112,7 +101,6 @@ export type CommandResult = | ElicitationGapSuccess | ElicitationGapDispositionSuccess | RepairSeededElicitationGapsSuccess - | RepairLegacyEdgeCategoriesSuccess | StructuralIllegal | NeedsHuman | PolicyBlocked @@ -139,9 +127,6 @@ export type SetElicitationGapDispositionResult = ElicitationGapDispositionSucces /** Result of repairing legacy specs missing the current seeded gap floor. */ export type RepairSeededElicitationGapsResult = RepairSeededElicitationGapsSuccess; -/** Result of repairing local graph rows that predate D87-L edge-category renames. */ -export type RepairLegacyEdgeCategoriesResult = RepairLegacyEdgeCategoriesSuccess; - /** Successful accepted review-set graph batch execution. */ interface AcceptReviewSetSuccess extends MutateGraphSuccess {} diff --git a/src/graph/workspace-store.ts b/src/graph/workspace-store.ts index 18c7b0eab..d0296ca3e 100644 --- a/src/graph/workspace-store.ts +++ b/src/graph/workspace-store.ts @@ -50,7 +50,6 @@ export interface WorkspaceGraphRuntime { export async function openWorkspaceGraphRuntime(cwd: string): Promise { const db = await openWorkspaceDb(cwd); const commandExecutor = new CommandExecutor(db); - commandExecutor.repairLegacyEdgeCategories(); commandExecutor.repairSeededElicitationGaps(); return { commandExecutor, From a17674eba2f61a5bba27ad909d844753ce63797b Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Fri, 26 Jun 2026 14:57:04 +0200 Subject: [PATCH 51/54] Use direct Gherkin then keys --- src/graph/__tests__/command-executor.test.ts | 3 +-- src/graph/__tests__/mutate-graph-edge-schema.test.ts | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/graph/__tests__/command-executor.test.ts b/src/graph/__tests__/command-executor.test.ts index 93eb97a14..452067bae 100644 --- a/src/graph/__tests__/command-executor.test.ts +++ b/src/graph/__tests__/command-executor.test.ts @@ -233,7 +233,6 @@ describe('CommandExecutor', () => { }); it('creates a criterion node with a gherkin form detail', () => { - const thenField = `${'the'}n`; const result = executor.createNode({ specId, plane: 'intent', @@ -243,7 +242,7 @@ describe('CommandExecutor', () => { form: 'gherkin', given: ['the app is offline'], when: ['the user saves'], - [thenField]: ['the change is persisted locally'], + then: ['the change is persisted locally'], }, }); diff --git a/src/graph/__tests__/mutate-graph-edge-schema.test.ts b/src/graph/__tests__/mutate-graph-edge-schema.test.ts index 2678367de..752e3f420 100644 --- a/src/graph/__tests__/mutate-graph-edge-schema.test.ts +++ b/src/graph/__tests__/mutate-graph-edge-schema.test.ts @@ -103,12 +103,11 @@ describe('authored graph-mutation schemas', () => { }); it('teaches and enforces claim-kind detail.form companions', () => { - const thenField = `${'the'}n`; const requirementGherkin = createNodeOp('requirement', { form: 'gherkin', given: ['offline'], when: ['save'], - [thenField]: ['persisted'], + then: ['persisted'], }); const criterionFormal = createNodeOp('criterion', { form: 'formal', From 517bb4c2c7039e6f7c7ba935f3350cc6a898ed88 Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Fri, 26 Jun 2026 14:57:32 +0200 Subject: [PATCH 52/54] Correct graph command adapter ownership comment --- src/.pi/extensions/brunch-data/graph/command-adapter.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/.pi/extensions/brunch-data/graph/command-adapter.ts b/src/.pi/extensions/brunch-data/graph/command-adapter.ts index 623520e3c..bf351a8b1 100644 --- a/src/.pi/extensions/brunch-data/graph/command-adapter.ts +++ b/src/.pi/extensions/brunch-data/graph/command-adapter.ts @@ -4,9 +4,8 @@ * SPEC: D4-L, D20-L, D52-L, D53-L * * This module translates Pi tool parameters (flat JSON from LLM tool calls) - * into CommandExecutor input types and formats CommandExecutor results into - * Pi tool result content. It does NOT import from db/ — all graph access - * routes through CommandExecutor and graph query readers. + * into CommandExecutor input types. It does NOT import from db/ — all graph + * access routes through CommandExecutor and graph query readers. */ import type { From d4e17a48313f10bcead872f456375f158095121f Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Fri, 26 Jun 2026 15:02:07 +0200 Subject: [PATCH 53/54] Complete data model legibility verdict --- .oxlintrc.json | 8 +-- memory/PLAN.md | 25 +++----- src/agents/docs/context-reference-harvest.md | 64 ++++++++++++++----- .../generate-proposal/references/oracle.md | 5 +- 4 files changed, 61 insertions(+), 41 deletions(-) diff --git a/.oxlintrc.json b/.oxlintrc.json index 11b142cb3..6fa331a48 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -58,13 +58,7 @@ { "patterns": [ { - "group": [ - "**/.pi/**", - "**/app/**", - "**/db/**", - "**/rpc/**", - "**/web/**" - ], + "group": ["**/.pi/**", "**/app/**", "**/db/**", "**/rpc/**", "**/web/**"], "message": "D52-L/D83-L: agents/contexts/ is model-facing render code; it must not import adapters, app entrypoints, transports, web, or db." } ] diff --git a/memory/PLAN.md b/memory/PLAN.md index 4cf391ab4..152c14377 100644 --- a/memory/PLAN.md +++ b/memory/PLAN.md @@ -69,10 +69,11 @@ context-pipeline/ ### Active -- None. `elicitor-generate` is tied off pending branch submission; the next frontier is `elicitor-project`. +- No active frontier is selected. Scope the next item from §Next. ### Recently Completed +- 2026-06-26 `data-model-legibility` (FE-1090) — **reference substrate complete.** Generated ontology tables are materialized from typed graph sources with `check:data-model`; authored graph-authoring heuristics are cited by `capture` + `commit-graph`; the final checkability/subtype audit closed with no schema/runtime expansion: progressive checkability is accepted only as skill-local oracle conduct, `checkability`/`strength` fields are rejected carrying cost, subtype enums are rejected as parallel ontology, and `detail.form` remains inert payload plus renderer hook. - 2026-06-25 `elicitor-generate` (FE-1059) — **generate capability done through promoted A31-L fan-out evidence.** Built slices: `present_candidates` tool/projection/renderer + pick path; intent/design/oracle facets under one plane-parameterized `generate-proposal` method; progressive-disclosure references; real-boot activation check; and real-model fan-out witness harness. Promoted run `.fixtures/runs/generate-fan-out/2026-06-24T16-51-13-704Z/` passed with `openai-codex/gpt-5.5`: oracle lens pinned, `SKILL.md` and `references/oracle.md` read, `present_candidates` emitted, no pre-prompt kick, no graph delta, no `mutate_graph`, and no approved review result. A32-L fan-in completion and the A1 anti-prompt remain follow-ups, not branch debt. - 2026-06-24 `subagent-reconciliation` (FE-1054) — foreground/background reconciliation complete through the execute-mode readiness target (D90-L-D93-L/I49-L): shared `AgentManifest`, code-owned background discovery, semi-permeable injected-world child sessions, sovereign grants gated by code-owned `canDelegate`, return rendering, and live `execute` -> `orchestrator` mode with a product-registered stub tool. `code` -> `pi-coder` remains future work. - 2026-06-24 `readiness-bands-interrogation` (FE-1058) — D94-L/I50-L materialized: derived four-band ladder, two carriers (`gap.band` for asking agenda, plane-derived node bands for projection thresholds), per-kind table deleted, `projection` band added, and goldens/readers reconciled. @@ -82,7 +83,6 @@ context-pipeline/ - `orchestrator-tool-port` (FE-1087) — **scoped but D98-sensitive.** Port the external `brunch cook` orchestrator into the future CODE/executor tool surface rather than preserving a separate execute/orchestrator product mode. First active scope: `memory/cards/orchestrator-tool-port--plan-check-tool.md`; reconcile that scope against D98-L before build. - `elicitor-project` (FE-1085) — **design-gated.** Cross-plane derivation (requirements -> design, design -> oracles) remains undesigned under A33-L; run `ln-design` before any scope/build. -- `data-model-legibility` — **active.** Single active frontier for the SPEC-mode data-model/reference substrate and pattern-establishment sweep (D97-L/D98-L): generated graph-ontology references, authored graph-authoring/checkability/projection guidance, skill citation/pruning, and stale-doc disposition under `src/agents/`. Design verdict landed (Shape C); first tracer landed and its topology correction moved the generated kind→band table + `check:data-model` drift guard into `src/agents/contexts/references/graph-ontology.md`, cited by `methods/capture`. The shared authored `graph-authoring-heuristics.md` reference is now materialized and cited by `capture` + `commit-graph`; generated edge-category + detail/form tables are now materialized from typed graph sources in `graph-ontology.md`. Remaining rows are evaluated one at a time: checkability guidance and subtypes/`detail` remodel review. - `renderer-golden-coverage` — **active parallel coverage track.** Remaining RENDER work lives by audience: model-facing context surfaces under `agents/contexts/`, human/product text beside its app/session owner. Remaining rows need fresh scoping against `src/agents/contexts/README.md`, `src/app/README.md`, and `src/session/README.md`. - `exchange-symmetry-audit` — **earned cleanup.** Delete-oriented audit of the exchange projection/renderer split; not a capability blocker. @@ -140,19 +140,18 @@ context-pipeline/ ### data-model-legibility - **Name:** Single canonical home for data-model meta-guidance + generation seam -- **Linear:** tbd -- **Branch:** tbd +- **Linear:** [FE-1090](https://linear.app/hash/issue/FE-1090/data-model-legibility-reference-substrate) +- **Branch:** `ln/fe-1090-data-model-legibility-reference-substrate` - **Kind:** structural / design + build -- **Status:** active; design verdict landed (`ln-design`: Shape C — two layers behind one index). First tracer-bullet **landed and topology-corrected**: generated kind→band table at `src/agents/contexts/references/graph-ontology.md` + `check:data-model` drift guard (wired into `npm run check`), cited by `methods/capture`, with packaged runtime asset copy for `contexts/references/`. Load-bearing claim 1 (typed `graph/schema` sources are the closed, importable vocabulary set — D73-L) validated by the generator while `src/agents/contexts/references/` is now the runtime-eligible reference home. The authored graph-authoring heuristics row is materialized at `src/agents/contexts/references/graph-authoring-heuristics.md` and cited by `capture` + `commit-graph`; generated edge-category + detail/form tables are materialized in `graph-ontology.md` from `kinds.ts`, `nodes.ts`, and `category-policy.ts`. Remaining: checkability guidance verdict and the subtypes→`detail` remodel review. +- **Status:** done. Design verdict landed (`ln-design`: Shape C — two layers behind one index). Generated kind→band, edge-category, and detail/form tables live at `src/agents/contexts/references/graph-ontology.md` with the `check:data-model` drift guard (wired into `npm run check`) and packaged runtime asset copy. The authored graph-authoring heuristics row lives at `src/agents/contexts/references/graph-authoring-heuristics.md` and is cited by `capture` + `commit-graph`. Final verdict: progressive checkability is accepted only as oracle skill conduct in `generate-proposal/references/oracle.md`; claim-level `checkability`/`strength` fields and subtype enums are rejected carrying cost; `detail.form` remains inert payload plus renderer hook. - **Certainty:** proving. -- **Current execution pointer:** none active — re-scope the next slice (authored judgment layer, or further generated tables). - **Objective:** Recover + reconcile the retired `INTENT_GRAPH_SEMANTICS` content and adjacent heuristic docs into one SPEC-mode data-model reasoning substrate under `src/agents/`: runtime-eligible references in `src/agents/contexts/references/`, backstage curation notes in `src/agents/docs/`, and pruned/cited skill bodies. Generate the closed-vocabulary tables (planes / kinds / bands / edge-category policy / `detail` schemas) from typed graph sources so heuristics are **cited** (D97-L), not inlined and duplicated across skill bodies; align the result with D98-L's mode-only runtime posture. - **Acceptance:** - ✓ `ln-design` produced ≥3 module shapes for the home + generation seam with a recommendation (Shape C), before any doc/script. - ✓ The canonical-truth boundary is decided: what is generated from `kinds.ts` / `nodes.ts` / `category-policy.ts` vs authored judgment. Kind→band, edge-category policy, required detail, and `detail.form` tables are materialized in `src/agents/contexts/references/graph-ontology.md`. - - Subtypes/`detail` modelling review: each retired subtype family sorted into `kind` (behavior-bearing), `detail` facet (inert classification), or already-covered; decide whether an inert `detail` facet dimension earns its carrying cost given the kind/band/form machinery already discriminates. - - The two capture gaps are explicitly ruled in or out: constraint/invariant subtype enums; the 8-rung checkability ladder + `strength`. - - Skill bodies cite the new home (D97-L); inlined heuristic copies collapse to one cite-target. `capture` + `commit-graph` now cite `graph-authoring-heuristics.md` for shared graph-authoring judgment. + - ✓ Subtypes/`detail` modelling review: retired subtype families are rejected as parallel ontology; method-specific structure is already covered by inert `detail.form` from typed sources. + - ✓ The two capture gaps are explicitly ruled in or out: constraint/invariant subtype enums are rejected as parallel ontology; the 8-rung checkability ladder is narrowed to oracle skill conduct; `strength` / claim-level checkability fields are rejected carrying cost. + - ✓ Skill bodies cite the new home (D97-L); inlined heuristic copies collapse to one cite-target. `capture` + `commit-graph` now cite `graph-authoring-heuristics.md` for shared graph-authoring judgment; oracle checkability conduct stays skill-local in `generate-proposal/references/oracle.md`. - ✓ A drift guard (`check:data-model`, mirroring `check:skills`, wired into `npm run check`) fails if the generated reference diverges from the typed sources. - If `ln-design` splits this into recover-doc / build-generator / subtypes-remodel frontiers, create a `data-model-legibility` arc per §Initiatives. - **Traceability:** D73-L (domain owns vocabulary), D88-L (`detail` form union), D97-L (heuristic provenance), D98-L (SPEC/CODE mode-only runtime posture); un-defers and relocates the generated-reference pattern into `src/agents/contexts/references/`; relates to `elicitor-project` (A33-L, shared D97-L rule). @@ -183,8 +182,7 @@ context-pipeline/ ```text frontiers: - Active: - none + Active: {} Next: orchestrator-tool-port @@ -196,11 +194,6 @@ frontiers: status: design-gated depends_on: elicitor-generate, D95-L, D96-L, I51-L - data-model-legibility - status: active (design landed Shape C; first tracer landed and topology-corrected) - depends_on: graph/schema typed sources (kinds.ts, nodes.ts, category-policy.ts), D73-L, D88-L, D97-L, D98-L - materialized: src/agents/contexts/references/graph-ontology.md + check:data-model - renderer-golden-coverage status: active parallel coverage depends_on: context-pipeline PULL+PROJECT, D83-L, D52-L diff --git a/src/agents/docs/context-reference-harvest.md b/src/agents/docs/context-reference-harvest.md index a925325a5..97b28a4f8 100644 --- a/src/agents/docs/context-reference-harvest.md +++ b/src/agents/docs/context-reference-harvest.md @@ -21,17 +21,17 @@ A source may carry more than one disposition class when it has separable uses. T | Source | Disposition labels | Candidate future reference | Reader / blocker | D98-sensitive notes | Next action | | - | - | - | - | - | - | -| `/private/tmp/igs_recovered.md` (`INTENT_GRAPH_SEMANTICS`) | authored-runtime-reference input; backstage-only rationale; historical/archive candidate | `graph-authoring-heuristics.md`; `checkability-ladder.md`; generated edge-category/detail-form table scope is materialized from typed sources only | Reader: capture/commit/generate methods needing graph vocabulary and graph-authoring judgment. Blocker: reconcile every subtype/checkability claim against live `src/graph/schema/{kinds,nodes}.ts`, `src/graph/policy/category-policy.ts`, D87-L, D88-L, D94-L before accepting it. | Contains retired subtype proposals and old edge/ontology language; do not revive stale modality/subtype claims or create runtime `strategy` / `lens` / `method` session state. | Generated edge/detail/form tables now derive from current typed sources; separately review promotion rules, checkability ladder, and subtype material as authored judgment rows. | +| `/private/tmp/igs_recovered.md` (`INTENT_GRAPH_SEMANTICS`) | skill-local-reference input; backstage-only rationale; historical/archive candidate | `graph-authoring-heuristics.md` materialized; generated edge-category/detail-form table materialized from typed sources; oracle checkability guidance accepted skill-local only | Reader: capture/commit methods already cite shared graph-authoring judgment. Oracle `generate-proposal` may use progressive-checkability language when designing verification ensembles. | Contains retired subtype proposals and old edge/ontology language; do not revive stale modality/subtype claims, claim metadata, `strength`, or runtime `strategy` / `lens` / `method` session state. | Verdict complete: promotion rules already accepted in `graph-authoring-heuristics.md`; the 8-rung ladder is narrowed to skill-local oracle prompting; `strength` and claim-level `checkability` fields are rejected carrying cost; subtype/detail candidates are rejected as parallel enums except for already-shipped `detail.form` from typed sources. | | `docs/design/ELICITATION_QUESTIONS.md` | authored-runtime-reference input | `elicitation-question-hints.md` | Reader: future elicitor question/gap guidance. Blocker: refresh against post-FE-1052 kind names, `story` / `unknown` / `entity` / `sketch`, and four-band D94-L model. | Uses older band framing and mentions strategy/lens as prompt-space terms; keep as prompt-resource vocabulary only, not runtime state. | Treat the durable thesis as: node kind is closed ontology; questions are open/projectable hints inside a kind. Rewrite examples before model-facing use. | | `docs/design/ONTOLOGY_REVIEW_PROTOCOL.md` | backstage-only rationale; authored-runtime-reference input | possible `graph-authoring-heuristics.md` citations; may motivate generated edge-category/detail-form table scope but typed code remains the generated-reference input | Reader: data-model maintainers and future generated-reference authors. Blocker: live code/SPEC are authoritative; §0/§2–3/§9 are historical and `thesis → claim` did not land. | Mentions methods as validation lenses; preserve only as prompt/resource vocabulary where useful, never as user-changeable runtime axes. | Use as design-validation record for D87-L/D88-L, not as prompt payload. Pull only claims that still match current SPEC/code. | | `docs/design/ELICITATION_LENSES.md` | authored-runtime-reference input; skill-local-reference input; historical/archive candidate | `proposal-meta-rubric.md`; `projection-guidance.md` | Reader: `generate-proposal` and future `project` capability. Blocker: D98-L retired `strategy` / `lens` / `method` as runtime state; A33-L still design-gates `project`. | Highly D98-sensitive: old lens catalogue must not reintroduce runtime lens/strategy/method axes. Fan-out/fan-in and D31 meta-rubric may survive as prompt conduct. | Harvest fan-out/fan-in, grounding-density, and meta-rubric ideas only into the relevant method/reference home after translating away runtime-axis assumptions. | -| `docs/design/BEHAVIORAL_KERNELS.md` | authored-runtime-reference input; historical/archive candidate | `checkability-ladder.md`; possible `elicitation-question-hints.md` | Reader: future elicitation/gap guidance if kernel prompts prove useful. Blocker: no current runtime kernel ontology; must not create a parallel data model or prompt taxonomy without a concrete reader. | Kernel terminology is interviewer machinery at most, not graph state and not runtime session state. | Defer. Mine only specific checkability/question patterns if a later scope names a reader. | +| `docs/design/BEHAVIORAL_KERNELS.md` | skill-local-reference input; backstage-only rationale; historical/archive candidate | oracle checkability phrasing in `generate-proposal/references/oracle.md`; possible future `elicitation-question-hints.md` | Reader: oracle generate guidance for weakest-sufficient verification artifacts; future elicitation/gap guidance only if a scoped reader appears. Blocker: no current runtime kernel ontology; must not create a parallel data model or prompt taxonomy without a concrete reader. | Kernel terminology is interviewer machinery at most, not graph state and not runtime session state. | Accepted only as skill-local oracle prompting for progressive verification artifacts; kernel labels/taxonomy remain rejected as runtime model and deferred for elicitation questions. | | `src/agents/skills/methods/capture/SKILL.md` | leave-as-is; authored-runtime-reference input; partially materialized | `graph-authoring-heuristics.md` materialized; `checkability-ladder.md` deferred | Reader: capture now cites the shared authoring reference for declarative graph claims, low-confidence routing, contradiction routing, relation-bearing confidence, and role-named mutation grammar. FE-861 sweep sequencing, gap conduct, and commitment-gradient table remain local. | Method is a prompt-resource id, not runtime state. No D98 issue while it stays code-owned prompt-resource conduct. | Materialized shared graph-authoring guidance; defer checkability-ladder extraction until a second concrete reader needs it. | | `src/agents/skills/methods/commit-graph/SKILL.md` | leave-as-is; authored-runtime-reference input; materialized | `graph-authoring-heuristics.md` | Reader: graph-write methods needing declarative-node, promotion, settled-commitment, confident-endpoint, and role-named mutation discipline. | Method remains prompt-resource conduct; do not make it a user-changeable runtime mode. | Materialized shared authoring reference and cite from this method; remaining direct-commit sequencing stays local. | | `src/agents/skills/methods/generate-proposal/SKILL.md` | leave-as-is; skill-local-reference input; authored-runtime-reference input | `proposal-meta-rubric.md`; `projection-guidance.md` | Reader: current generate method and future project design. Blocker: proposal meta-rubric might belong skill-local unless `project` becomes a second reader. | Names intent/design/oracle lenses/planes as prompt conduct; keep out of runtime state and schema fields. | Leave body unchanged now. Revisit after `elicitor-project` design chooses whether projection folds into generate or needs a distinct surface. | | `src/agents/skills/methods/generate-proposal/references/intent.md` | leave-as-is; skill-local-reference input | `proposal-meta-rubric.md` only if shared beyond generate | Reader: generate intent-plane fan-out. Blocker: no second reader yet. | Plane-specific prompt payload is okay; do not turn `pick` into a schema/runtime field. | Leave in skill-local home. | | `src/agents/skills/methods/generate-proposal/references/design.md` | leave-as-is; skill-local-reference input | `proposal-meta-rubric.md`; possible `projection-guidance.md` | Reader: generate design-plane fan-out and possible future project design. Blocker: A33-L design verdict. | `synthesize` is method conduct, not schema or runtime axis. | Leave in skill-local home; use as input to `project` design only if that frontier needs it. | -| `src/agents/skills/methods/generate-proposal/references/oracle.md` | leave-as-is; skill-local-reference input | `proposal-meta-rubric.md`; `checkability-ladder.md` | Reader: generate oracle-plane fan-out; possible future oracle/checkability guidance. Blocker: avoid mixing verification-strategy guidance with graph ontology unless a concrete citing need appears. | `compose` is method conduct, not schema or runtime axis. | Leave in skill-local home; selectively mine oracle-family/checkability phrasing if a later shared reference has multiple readers. | +| `src/agents/skills/methods/generate-proposal/references/oracle.md` | leave-as-is; skill-local-reference input; materialized | `proposal-meta-rubric.md`; skill-local progressive-checkability guidance | Reader: generate oracle-plane fan-out and fan-in. | `compose` and progressive-checkability language are method conduct, not schema fields, stored claim metadata, or runtime axes. | Materialized the accepted narrow verdict here: choose the weakest sufficient oracle artifact and name evidence breadth/blind spots without adding `checkability`, `strength`, kernel, or subtype schema. | ## Candidate reference queue @@ -51,24 +51,22 @@ tree context-reference-candidates: - low-confidence and contradiction routing - relation-bearing endpoint confidence - role-named mutate_graph grammar - deferred: - - checkability ladder - - constraint/invariant subtype enums + rejected_or_deferred: + - claim-level checkability / strength fields (rejected carrying cost) + - constraint/invariant subtype enums (rejected parallel ontology) - generated edge-category/detail-form tables (materialized in graph-ontology.md) d98_guard: method vocabulary allowed only as prompt conduct checkability-ladder.md: - home: src/agents/contexts/references/ or skill-local oracle reference, depending on readers - readers: - - oracle generate/project guidance - - future elicitation question/gap guidance, if checkability becomes cross-method - likely inputs: - - recovered progressive checkability ladder - - BEHAVIORAL_KERNELS artifact shapes - - oracle reference's oracle-family / blind-spot guidance - blockers: - - decide whether the 8-rung ladder and strength field are ruled in or out by data-model-legibility acceptance - - keep criterion/check/vv_obligation vocabulary aligned with live schema + status: rejected as shared runtime reference for now + verdict: + - no new context reference: only `generate-proposal/references/oracle.md` has a concrete present reader + - no schema fields: do not add `checkability`, `strength`, `validTraces`, or `invalidTraces` to graph nodes + - accepted nucleus: oracle prompting should choose the weakest sufficient verification artifact and name evidence breadth plus blind spots + materialized_in: + - src/agents/skills/methods/generate-proposal/references/oracle.md + deferred: + - future elicitation question/gap guidance may reopen shared reference status only with a second concrete reader d98_guard: no new runtime lens/kernel state elicitation-question-hints.md: @@ -109,6 +107,38 @@ tree context-reference-candidates: d98_guard: no revival of project strategy/lens/method as user-changeable state ``` +## Closed verdict: checkability, subtypes, and inert detail facets + +```pseudo +tree data-model-legibility-verdict: + accepted: + graph-authoring promotion rules: + home: src/agents/contexts/references/graph-authoring-heuristics.md + readers: [capture, commit-graph] + generated vocabulary tables: + home: src/agents/contexts/references/graph-ontology.md + source: typed graph schema and policy only + oracle progressive-checkability conduct: + home: src/agents/skills/methods/generate-proposal/references/oracle.md + reader: generate-proposal oracle plane + rule: choose the weakest sufficient oracle artifact and disclose evidence breadth/blind spots + rejected_carrying_cost: + graph node fields: [checkability, strength, validTraces, invalidTraces] + subtype enums: [constraint subtype, invariant subtype, criterion subtype, example subtype] + reason: no current reader needs a stored axis beyond kind, readiness band, edge policy, and detail.form + already_covered: + method-specific claim structure: + mechanism: detail.form + guard: kind drives behavior; form is inert payload plus renderer hook + negative/positive examples: + mechanism: example kind + edge stance/category policy where structurally legal + deferred: + elicitation question hints: + trigger: a future scoped reader needs reusable question patterns + project/projection guidance: + trigger: A33-L design verdict +``` + ## Runtime/backstage guardrails - This ledger is a pointer and disposition table, not a canonical ontology or prompt body. diff --git a/src/agents/skills/methods/generate-proposal/references/oracle.md b/src/agents/skills/methods/generate-proposal/references/oracle.md index 55b4ae723..0f1f26cb2 100644 --- a/src/agents/skills/methods/generate-proposal/references/oracle.md +++ b/src/agents/skills/methods/generate-proposal/references/oracle.md @@ -4,7 +4,9 @@ Use this reference when the active lens is `oracle` and the user needs alternati The oracle plane fan-in move is **compose**. Verification-design alternatives are additive: one family may catch semantic drift, another may catch schema breakage, and another may make human judgment repeatable. Redundancy across independent oracle families is a feature when it reduces bad degrees of freedom at acceptable cost. -Fan out oracle ensembles, not isolated checks. Each candidate should name: +Fan out oracle ensembles, not isolated checks. Choose the weakest sufficient oracle artifact for the claim at hand: human review, concrete example/counterexample, regression or golden, runtime contract, property/model-based rule, probe/transcript, or proof obligation. Treat that as verification conduct only — do not add graph node fields such as `checkability`, `strength`, or trace lists. + +Each candidate should name: - **Observability**: what the system exposes that lets the oracle see the behavior. - **Reproducibility**: whether the observation can be replayed or fixture-backed. @@ -12,6 +14,7 @@ Fan out oracle ensembles, not isolated checks. Each candidate should name: - **Oracle family**: schema/static check, fixture/golden, property/model-based, probe/transcript, visual/manual review, or another locally justified family. - **Fixture/probe commitments**: what artifacts must be kept, refreshed, or run to make the oracle repeatable. - **Loop tier**: inner, middle, or outer, with verification economics named. +- **Evidence breadth**: whether the claim is reviewed, example-backed, regression-covered, enforced, or proved, without storing that breadth as graph metadata. - **Blind spots**: what the oracle misses, its false-positive shape, and the trigger for revisiting it. Use the D31-L meta-rubric through the verification-design column: From 73a70237ad5064a27344f4a3047ac603a0eebd54 Mon Sep 17 00:00:00 2001 From: Lu Nelson Date: Fri, 26 Jun 2026 15:03:05 +0200 Subject: [PATCH 54/54] Update prompt resource topology test --- src/agents/skills/__tests__/prompt-resources.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/agents/skills/__tests__/prompt-resources.test.ts b/src/agents/skills/__tests__/prompt-resources.test.ts index ee52d0a4d..7f50e1142 100644 --- a/src/agents/skills/__tests__/prompt-resources.test.ts +++ b/src/agents/skills/__tests__/prompt-resources.test.ts @@ -95,7 +95,7 @@ describe('prompt-resource skills', () => { expect(readme).toContain('progressive disclosure'); expect(readme).toContain('Shared typed-vocab context references'); expect(readme).toContain('src/agents/contexts/references/graph-ontology.md'); - expect(readme).toContain('concrete citing need appears'); + expect(readme).toContain('edge-policy, detail-payload, and `detail.form` vocabulary'); expect(readme).toContain('drift-checked'); expect(readme).toContain('Shared authored context references'); expect(readme).toContain('src/agents/contexts/references/graph-authoring-heuristics.md');