FE-1497: Build the controller read path and the SDCPN plugin package - #9325
Open
lunelson wants to merge 3 commits into
Open
FE-1497: Build the controller read path and the SDCPN plugin package#9325lunelson wants to merge 3 commits into
lunelson wants to merge 3 commits into
Conversation
3 tasks
|
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
|
3 tasks
This was referenced Aug 25, 2026
Contributor
Author
lunelson
changed the base branch from
ln/fe-1404-run-condition-3
to
graphite-base/9325
August 25, 2026 12:53
3 tasks
lunelson
force-pushed
the
ln/fe-1497-controller-read-path
branch
from
August 26, 2026 08:30
3d0a574 to
9ae74f7
Compare
lunelson
force-pushed
the
graphite-base/9325
branch
from
August 26, 2026 08:30
a306716 to
931a655
Compare
lunelson
changed the base branch from
graphite-base/9325
to
ln/fe-1404-run-condition-3
August 26, 2026 08:30
2 tasks
lunelson
marked this pull request as ready for review
August 26, 2026 08:37
lunelson
force-pushed
the
ln/fe-1404-run-condition-3
branch
from
August 26, 2026 17:18
bdd4e1b to
9879c16
Compare
lunelson
force-pushed
the
ln/fe-1497-controller-read-path
branch
from
August 26, 2026 17:18
0cc223e to
60fd060
Compare
This was referenced Aug 26, 2026
lunelson
dismissed stale reviews from vilkinsons and CiaranMn
August 26, 2026 17:51
The base branch was changed.
lunelson
force-pushed
the
ln/fe-1497-controller-read-path
branch
from
August 26, 2026 17:51
60fd060 to
92cb856
Compare
Merge activity
|
The harness can now answer "is the elicited model complete?" from the capture store alone, and the SDCPN plugin is code that the harness loads, not a spec the interviewer is told about. Read path (FE-1497), five modules in `packages/core/src`: - `plugin-file.ts` parses an ADR-0006 plugin file: the six fixed headings in order, the version, the three machine-read tables (kinds, must-know rows with typed precision and the not-applicable flag, patterns indexed by the kinds their trigger names), and the static floor as per-kind counts. Contract violations throw `PluginFileError` at load. - `slot-assertion.ts` is the one proposal payload a kind-and-slot plugin needs: `{kind, node, slot, precision?, assertion: value | absence}`, restricted by `createSlotAssertionSchema(file)` to the kinds and slots the file names. It rides inside `content.value`; the envelope-level `{absence}` variant has no address and stays unusable for slots. - `elicited-model.ts` folds active captures into nodes keyed `kind:node` and slots in one of four states (value, absence, conflict, divergence). Unreadable payloads are recorded as unmapped, never interpreted. A prescribed/practiced pair that differs is a divergence; anything else that differs is a conflict; an open conflicting issue pins a slot in conflict even when readings agree. The revision digests the active set. - `completion.ts` is `evaluateCompletion(model, demands)`: pure, no persisted status, refuses foreign plugin versions, checks the floor, then the objective's dependency slice, then every row inside the slice in the diagnostic order the spec fixes. Nodes outside every objective's slice are reported, not demanded. `precisionSatisfies` encodes the ladder (named < number < range < spread; spelled out satisfies itself and named). Accepted statuses default to `explicit`; "inferred and confirmed" is an explicit superseding capture. - `cue.ts` turns a report into a sweep list (failing slots plus the patterns indexed on their kinds) and a completion-cue signal that states the verdict and never decides whether to continue. `definePlugin` gains `file?: PluginFile`; `targetDomain` is renamed `targetFormalism` to match ADR-0006. The Flue binding threads the file through: slot-assertion guidance in the extraction prompt, the cue in the sweep tool's result, the file's prose appended to the instructions. Harness facts still reach the model only through tool results and signals. Plugin package (FE-1482), `packages/plugin-sdcpn`: `docs/specs/sdcpn- plugin.md` moves to `plugin.md` and is imported `?raw`, parsed at module load, and declared as the single `slot-asserted` proposal type. Depends on `@hashintel/brunch-agent` and valibot only; named in kernel §12.2 so the topology gate admits it. Every document that linked the spec is repointed. Tests: the fixture plugin file plus contract-violation cases; the real SDCPN file (version, ten kinds, floor, 24 rows, 13 patterns, no domain words); fold semantics; completion invariants 1–16 mapped one test each (17–19 are session-control and not implemented here); the cue; and an end-to-end fold + completion over the SDCPN rows in the plugin package. Gates: core 232/232, binding-flue 16/16, plugin-gherkin 2/2, plugin-sdcpn 4/4, apps/brunch-agent 32/32; tsgo clean; oxlint 0 errors; oxfmt applied. Dependents resolve the harness through `dist/`, so a stale build fails their tests with the old field name until rebuilt. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The target gallery gains its second entry. `src/agents/sdcpn-elicitor.ts` is a thin `'use agent'` module, shaped like the gherkin one: it mounts `useElicitation` with the `sdcpn` plugin and a session whose history reader resolves conversations through the agent's own route. What the interviewer asks, demands, and treats as complete comes from `plugin.md` through the binding; this module holds none of it. `routes.ts` names the routes as a record (`gherkin`, `sdcpn`) and the session factory takes the target, so each agent's reader speaks to its own mount. The browser UI selects the agent with `?target=sdcpn`; gherkin stays the default tracer. Petrinaut's chat transport still drives the gherkin elicitor and is unchanged. `test/build-artifact.test.ts` now reads every module under `src/agents` for pinned identities, so a second agent that silently failed to register would fail the bundle check rather than pass it by omission. Gates: dev app build + 32/32 tests (both identities bound in the bundle); core architecture gates 119/119, including first-statement directive, pinned literal, and no duplicated identity; tsgo clean; oxlint 0 errors; oxfmt clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
STEERING gains a gate row for completion-spec rules 17–19 (deferral licensing): E1 now supplies the report and revision, but rule 18 makes licensing false until a durable projection delivery exists, so the work is gated on FE-1480 rather than opened as an issue. The belief "the controller read path is small" carried its own tripwire — stop and look if E1 exceeds the plugin file in size. It did. The row now records the measured sizes, splits the parser from the engine, and names the look: whether a stricter plugin-file format would shrink the parser to a schema. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lunelson
force-pushed
the
ln/fe-1497-controller-read-path
branch
from
August 27, 2026 08:47
92cb856 to
f8d631e
Compare
kostandinang
approved these changes
Aug 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

🌟 What is the purpose of this PR?
Brunch is an elicitation harness: an AI interviewer that draws a model of a process out of a human expert and projects it into a Petrinaut net. Until this branch the harness could store what the expert said but could not answer the question the interview turns on — is the model complete yet? — and the SDCPN plugin existed only as a specification document the interviewer was told about. This pull request builds the two epicentres that S-007 put first: the controller read path (FE-1497), which folds stored captures into an elicited model and evaluates it against the plugin file's demands, and the SDCPN plugin as code (FE-1482), a workspace package that loads
plugin.mdat module load and declares the one proposal type a kind-and-slot plugin needs. The dev app mounts an SDCPN elicitor alongside the Gherkin one, so FE-1404's condition-3 run has an agent to talk to once this stack merges.Draft: the code and gates are complete; it is stacked above #9322 and inherits its merge order.
🔗 Related links
libs/@hashintel/brunch-agent/docs/adr/0006-plugins-per-target-formalism.mdlibs/@hashintel/brunch-agent/docs/specs/elicitation-completion.mddocs/specs/sdcpn-plugin.md):libs/@hashintel/brunch-agent/packages/plugin-sdcpn/plugin.mddocs/control/STRATEGY-LOG.mdS-007; frontier:docs/control/STEERING.md🚫 Blocked by
🔍 What does this change?
Read path — five new modules in
packages/core/src, all exported from@hashintel/brunch-agent:plugin-file.tsparses an ADR-0006 plugin file: six fixed headings in order, the version, the three machine-read tables (Kinds; Must know rows with typed precision and the "not applicable" flag; Patterns indexed by the kinds their trigger names), and the static floor as per-kind counts. A contract break throwsPluginFileErrorat load.slot-assertion.tsis the one proposal payload a kind-and-slot plugin needs —{kind, node, slot, precision?, assertion: value | absence}— narrowed bycreateSlotAssertionSchema(file)to the kinds and (kind, slot) pairs the file names.elicited-model.tsfolds active captures into nodes keyedkind:node, each slot in one of four states (value, absence, conflict, divergence). Unreadable payloads go tounmapped; nothing is interpreted. A prescribed/practiced pair that differs is a divergence; anything else that differs is a conflict; an open conflicting issue pins a slot in conflict even when the readings agree.completion.tsisevaluateCompletion(model, demands): pure, no persisted status; refuses a foreign plugin version; checks the floor, then the objective's dependency slice, then every row inside the slice in the spec's diagnostic order. Nodes outside every objective's slice are reported, not demanded.precisionSatisfiesencodes the ladder (named < number < range < spread;spelled outsatisfies itself andnamed).cue.tsturns a report into a sweep list (failing slots plus the patterns indexed on their kinds) and acompletion-cuesignal that states the verdict and never decides whether to continue.SDK and binding —
definePlugingainsfile?: PluginFile;targetDomainis renamedtargetFormalismto match ADR-0006. The Flue binding threads the file through: slot-assertion guidance in the extraction prompt, the completion cue in thebrunch_sweeptool result, the file's prose appended to the instructions. Harness facts still reach the model only through tool results and signals.Plugin package —
packages/plugin-sdcpn:plugin.md(moved fromdocs/specs/sdcpn-plugin.mdwithgit mv) is imported?raw, parsed at module load, and declared as the singleslot-assertedproposal type. Dependencies are exactly@hashintel/brunch-agentand valibot. Named in kernel §12.2 so the topology gate admits it; the fourteen documents that linked the spec are repointed; the context README lists the package.Dev app —
src/agents/sdcpn-elicitor.tsis a thin'use agent'module mounted at/agents/sdcpn, shaped like the Gherkin one. Routes are a record, the session factory takes the target so each agent's history reader speaks to its own mount, and the browser UI selects the agent with?target=sdcpn(Gherkin stays the default). Petrinaut's chat transport is unchanged.🏗️ Agent notes
Stacked above #9322 (
ln/fe-1404-run-condition-3). One branch, two issues, per the branch-to-issue rule stated on the parent branch: FE-1497 and FE-1482 are built together because the plugin package is the read path's first consumer and the read path is what makes the plugin file more than prose.Commits
9878a182d0Build the controller read path and the SDCPN plugin package01d088723eMount the SDCPN elicitor in the dev appInterface decisions to look at
content.value. The capture envelope's own{absence}variant has no address (no kind, node, or slot), so it cannot carry a per-slot absence. A slot absence isassertion: {absence, pointer?}inside the assertion payload. The envelope variant stays unusable for slots; that is a finding for the capture-store spec, not fixed here.ordering/flowMust-know row, not the floor parser.completionDemands(file, { acceptedStatuses })defaults to["explicit"]; a confirmation is a new explicit capture that supersedes the inferred one.objectiveby convention (ANCHOR_KIND); itsat least Nrow is the dependency slice. The fold does not read the anchor from the file.evaluateCompletion; the test file records this.Gates
Verified at the tip: core 232/232 (topology, docs-index, control-surfaces, Flue-placement gates included); binding-flue 16/16; plugin-gherkin 2/2; plugin-sdcpn 4/4; dev app build + 32/32 (the bundle check now reads every agent module and confirms both identities are bound);
tsgo --noEmitclean in every package; Oxlint 0 errors (inherited warnings only); oxfmt clean; lefthook pre-commit passed on both commits.Dependents resolve
@hashintel/brunch-agentthroughdist/, so a stale build fails their tests with the old field name even when the type-check passes.turbo run test:unitorders the builds; runningvitestdirectly does not.Pre-Merge Checklist 🚀
🚢 Has this modified a publishable library?
This PR:
📜 Does this require a change to the docs?
The changes in this PR:
🕸️ Does this require a change to the Turbo Graph?
The changes in this PR:
turbo.json's have been updated to reflect this@hashintel/brunch-agent-plugin-sdcpn, with its ownturbo.json(copied from the Gherkin plugin), and a new dependency edge from@apps/brunch-agentto it.brunch_sweeptool result, so the model sees it only after a sweep. No harness-side settlement or stop decision consumes the report yet; that is the session-control work of invariants 17–19.{absence}variant in the capture store has no slot address and is not used by the fold (see agent notes).apps/brunch-agenthas no faux-provider integration test for the SDCPN agent yet; registration and mounting are covered, an end-to-end SDCPN conversation is not.🐾 Next steps
/agents/sdcpnfrom a branch above this one.project/validate, ADR-0005) for the plugin package, once the read path has been exercised by a real run.🛡 What tests cover this?
packages/core/test/plugin-file.test.ts— the synthetic fixture file, ten contract-violation cases, and the real SDCPN file (version, ten kinds, floor, 24 rows, 13 patterns, no domain words).packages/core/test/elicited-model.test.ts— active-only fold, supersession and retraction, unmapped payloads, conflict vs divergence vs merge, revision stability.packages/core/test/completion.test.ts— spec invariants 1–16, one or more tests each.packages/core/test/cue.test.ts— sweep list matching, signal body, truncation.packages/plugin-sdcpn/test/plugin.test.ts— the parsed file, proposal schema acceptance and refusal, an end-to-end fold + completion over the SDCPN rows.packages/core/test/architecture/*— topology, docs index, Flue placement, and control surfaces, all green with the new package and agent module.apps/brunch-agent/test/build-artifact.test.ts— both agent identities bound in the emitted bundle.❓ How to test this?
turbo run test:unit --filter @hashintel/brunch-agent --filter @hashintel/brunch-agent-plugin-sdcpn --filter @apps/brunch-agentturbo run dev --filter @apps/brunch-agent) and open/?target=sdcpn; the interviewer's instructions carry the plugin file's sections, and after the firstbrunch_sweepthe tool result includes acompletionblock naming the unsatisfied slots./(Gherkin), which is unchanged.📹 Demo
None yet; the SDCPN conversation is the FE-1404 run.