Chapter 9 — Address It From the DSL
# Chapter 9 — Address It From the DSL
Everything exists; nothing is addressable. paradigm.Registry carries the
critic’s contract, the agent runs under governance, the prompt file
validates — and agent fixer uses critic still fails at load with
unsupported agent paradigm in any runtime that has not linked the
package. This chapter closes that gap: four small edits that teach the
framework to believe, the goldens that pin the grammar, the linter that
replays the loader, and the payoff — a recipe, loaded and run against
stringkit, with the telemetry receipts to prove which route the runtime
took. Part III ends here: critic is a DSL-addressable paradigm.
# Where you are
Thirty-two tests green; the paradigm complete. This chapter is the one
sanctioned exception to the book’s self-containment rule (D13) — the
registration wiring cannot live in cognitionzoo/critic/, because it is
precisely the code outside the package that must know the package exists.
It ships through the overlay at the files’ true repository paths, applied
to a scratch clone; the main repository is never modified.
# The composition site, pinned by reading
Chapter 1 carried a [NEEDS CONFIRMATION]: the constructor’s callers,
pinned by reading rather than grep. Here is what the reading found, and
it is worth understanding as architecture, not just location.
Euclo’s runtime never switches on paradigms at the top. instantiateAgent
(app/relurpish/runtime/runtime.go) builds exactly one agent — Euclo
itself, from paradigmDeps(): the fifteen-field Deps you met in
Chapter 8, wired field by field (Model: r.Model,
PermissionChecker: r.permissionChecker(),
Registry: r.Tools, …). Paradigms are reached one level down, when a
recipe step executes. The lowering lives in
named/euclo/thoughtrecipes/step_core.go, in buildAgent — a switch
with one case per shipped paradigm and a default that returns
ErrContractViolation (“reachable only when a caller constructs an
ExecutionStep bypassing both [the loader and registration validation]”).
Each case does the same three things: build the delegate, lower the
step’s typed directive payloads into typed options, construct the agent.
So the wiring is four edits, each following the shipped pattern:
lexer.go—"critique": {}joins the reserved keyword set. The DSL’s directive names are lexer keywords, one per paradigm directive;critiqueslots betweenreviewandrevise, its declaration-order neighbors.parser.go—critiquejoins the directive-item case list. The grammar itself is generic:parseDirectiveItemreads any directive name, an optionalwhenpredicate, and arguments, and the contract check decides later whether the bound paradigm declares it.paradigm_options.go—criticOptions(step, deps)mirrorsreflectionOptionsline for line: at most onecritique(its text becomesWithCritiqueCriteria), at most onerevise(predicate required, at least one body step, lowered throughnewCriticReviseBody— authored body steps run in-process viaExecuteDelegateCore). Absent directives yield no options; the library behavior survives (FR-9, again).step_core.go— the case, mirroring reflection’s:
case "critic":
delegate := reactagent.New(deps, c.streamOptions()...)
criticOpts, err := criticOptions(c.step, deps)
if err != nil {
return nil, ¶digm.ErrContractViolation{Step: c.step.ID, Paradigm: c.step.Paradigm, Cause: err}
}
return criticagent.New(deps, delegate, criticOpts...), nilNotice what the runtime hands the critic without being asked: the full
Deps, including the permission checker whose absence the agent refuses
to run without. Nobody wires governance for the critic; the runtime wires
it for everyone, and the paradigm enforces it.
# The goldens: byte-identical, then one deliberate diff
The DSL goldens (named/euclo/thoughtrecipes/testdata/golden/) are
plan/graph/trace triples — one per fixture recipe — and the policy is
stated in the Makefile: each slice after Slice 1 must keep these goldens
byte-identical; UPDATE_GOLDEN=1 re-baselines. A golden that changes
without a deliberate decision is a behavior change nobody approved.
With the wiring applied, every existing triple passes byte-identical —
the edits are purely additive, which is what “mirroring the shipped
pattern” buys. Then the new fixture (11_critic.erpe: two agents, a
critique directive, a bounded revise delegating to the react agent) gets
its triple, deliberately:
$ UPDATE_GOLDEN=1 go test ./named/euclo/thoughtrecipes/ -run TestGolden
$ go test ./named/euclo/thoughtrecipes/ -run TestGolden -v # all PASSAnd the new trace’s content is the chapter’s best surprise. The golden
harness runs with minimal deps — no model, no permission checker — and
records what actually happens with the honesty the harness is known for
(the shipped traces already record react agent missing language model
for the same reason):
{
"graph_valid": true,
"start_node": "run.10.5.reviewer.0.execute",
"exec_success": false,
"exec_error": "critic agent requires a permission checker (fail closed)"
}Read that as the integration proof it is: the recipe lowered, the graph built and validated, the step dispatched to the critic agent through the new case — and the governance bar, which Chapter 8 stated as two lines in a paradigm, reached all the way through the DSL and refused in the runtime’s own failure protocol. The bar is real end to end. A golden that recorded success here would mean somebody bent the harness; this one records the truth.
# Lint parity, both directions
relurplint validates recipes through the same contract registry the
loader uses (check_recipes.go validates every recipe via
thoughtrecipe.ValidateAgainstContracts, “so a retired directive or an
unknown paradigm surfaces at lint time with the same errors the loader
would surface at boot”). Because the linter links thoughtrecipes, the
critic’s contract rides along — no separate linter registration exists,
which is the parity guarantee by construction.
Prove it in both directions. Positive: a workspace with the critic recipe lints clean.
$ relurplint --check recipes --workspace ~/code/stringkit
relurplint: all checks passed (0 diagnostics)Negative: a revise block with no critique before it — the D1 mixing
rule — is rejected by the linter, with the loader’s own error and a
position:
relurpify_cfg/euclo/critic_broken.erpe: [error recipes.contract]
…:11:3: directive "revise" requires directive "critique"
in the critic paradigm contractThe same text the loader would emit at boot. Lint parity is not a goal here; it is a consequence of one registry.
# The payoff: a pinned offline run
The workspace recipe (critic_review.erpe) is book 1’s DSL fluency spent
on your own paradigm: investigation family, keyword triggers, a read-only
tool scope, the critique criterion, and a revise body that delegates back
to the react drafter with the critique’s issues interpolated into its
goal. Materialize the workspace, boot offline, and instruct:
explain and critique the slugify function in stringkit.py
The deterministic route does its job — investigation family (from
explain), and the telemetry route record names the winner:
family_id:investigation recipe_id:euclo.thoughtrecipe.critic_review
route_kind:capability name:euclo.thoughtrecipe.critic_reviewOne approval banner — kind=execution | risk=medium — answered [s] for
the session, because governed mode pauses the run for a human regardless
of how deterministic the route was. Then the graph executes, and the run
completes with the loop-close honesty guard firing — the same flag
Chapter 1 met on this same workspace. The reason is the honest one: the
offline backend’s scripted responses are not valid verdict JSON, so the
critic’s strict decode fails, the D6 retry fires exactly once, and the
failure protocol records the outcome. The receipts say all of it:
node euclo.execute_thoughtrecipe, the route record naming
critic_review, critic as the executing paradigm.
The honest reading, stated plainly: addressability is proven end to
end — load, lint, route, approve, execute, record. Critique quality
under the offline backend is not, and cannot be: a scripted model that
cannot emit verdict JSON cannot pass strict decoding, and the receipts
say so rather than hiding it. The deterministic behavioral proof remains
critic-runner (Chapter 6); real-model behavior is Chapter 12’s subject.
[NEEDS CONFIRMATION: recipe dispatch selection rules — an instruction with no family keyword routes to the default recipe regardless of recipe keywords; the dispatcher's exact keyword-vs-family mechanics get pinned during Chapter 10 research.]
# Gotchas
- The wiring is four files, not one. A new directive name touches the
lexer, the parser’s case list, the options lowering, and the factory
switch. Miss the lexer and
critiqueis a bare identifier that parses but never checks; miss the switch and the loader validates a paradigm the runtime cannot build — surfacing only asErrContractViolationat execution, the defense-in-depth branch. - Re-baseline deliberately, and only after the additive proof. Run
the goldens without
UPDATE_GOLDENfirst: every existing triple must pass byte-identical before you create a new one. A re-baseline that changes an existing golden is a behavior change, not a baseline. - The golden harness’s failures are honest failures. Minimal deps mean shipped traces record missing models and the critic records its missing checker. Do not “fix” the harness to make a golden green; the recorded refusal is the proof.
- Routing is deterministic and keyword-shaped. A recipe is selected within a classified family; an instruction with no family keyword goes to the default route no matter what your recipe’s keywords say. Write instructions (and keywords) the dispatcher can actually match.
- Governed mode pauses for approval even for read-only routes. The
banner is the work permit working;
[s](session) is the usual answer during active work.
# Summary
- The composition site is
buildAgent’s switch: one case per paradigm, delegate → options → construct, withErrContractViolationguarding the unreachable default. The critic’s case mirrors reflection’s, and the runtime’s ownDpswiring hands it the governance bar. - Four files change, all following the shipped pattern; the edits are
purely additive, proven by every existing golden passing
byte-identical before the new triple was baselined with
UPDATE_GOLDEN=1. - The new trace records the fail-closed refusal reaching the DSL — the
governance bar as an integration proof — while the offline run proves
addressability end to end: load, lint (both directions, one registry),
route (the telemetry record names
critic_review), approve, execute, record. - End of Part III:
criticis a DSL-addressable paradigm. What remains is proof at the framework’s own test layers — Part IV.
# Check yourself
- Why can the registration wiring not live in
cognitionzoo/critic/? Say it in terms of the dependency direction, not the book’s rule. - Name the four files the wiring touches and the one job of each.
- Why does the critic’s golden trace record an exec error, and why is that the stronger proof?
- Your teammate deletes the
critiquekeyword from the lexer but leaves the rest. What breaks, where — at parse time, load time, or execution time — and with which error? - The offline run’s critique failed strict decoding. Which three mechanisms handled it, in order, and what did each contribute?
Answers. 1. cognitionzoo paradigms are imported by
named/euclo/thoughtrecipes (named imports cognitionzoo, never the
reverse); the switch that constructs agents lives above the paradigm, so
the knowledge of a new paradigm must live above it too. 2. lexer.go
(reserve the directive name), parser.go (route it to the generic
directive-item grammar), paradigm_options.go (lower typed payloads into
typed options), step_core.go (construct the agent per step, with the
contract-violation guard behind it). 3. The harness’s minimal deps have
no permission checker, so the agent’s fail-closed bar fired through the
full DSL path; it is stronger because it proves the bar survives load,
lowering, dispatch, and execution — a success would have proven less. 4.
critique lexes as a bare identifier; the generic directive-item
fallback may still parse it, but the keyword-based paths (and any grammar
position where an identifier is invalid) become unpredictable — the
failure class is parse/load, and the honest fix is restoring the keyword,
not relying on the fallback. 5. Strict decode
(ErrInvalidCritique), the D6 model-invalid-output retry (exactly one),
and the run-node failure protocol (recorded outcome, run continues) —
malformed model output became a recorded, bounded, honest failure instead
of a garbage verdict.
# Try it yourself
- The order rule, from the linter. In a scratch workspace, write a
critic recipe whose
reviseappears before itscritiqueand runrelurplint --check recipes. Match the error againstErrDirectiveOrder’s format from Chapter 7 — the linter quotes the directive and the predecessor it must precede. - A second fixture. Add
12_critic_goal_only.erpe— a goal-only critic run with no directives — and baseline its triple. Read the plan JSON: the directives list should be empty and the paradigm stillcritic. That is FR-9, pinned in a golden.
# Where next
The paradigm is addressable; now it must survive the framework’s own
proof layers. Chapter 10 applies the three test layers to your agent:
unit (done, but now formalized), framework integration through the
testsuite/ builders, and agenttest — the OSB assertion model with
Outcome and Security fail cases and advisory Benchmarks. You will write
euclo.critic.testsuite.yaml, add a subject adapter, and record a tape.
The lab lives at devdocs/agent-construction-relurpify/labs/ —
verify.sh gates ch09 now includes make test-euclo-golden in the
scratch clone, and transcripts/ch09-registration.md holds the wiring
summary, the golden re-baseline, the lint captures, and the offline run’s
receipts.