Chapter 6 — Line Cooks: The Linear Paradigms
# Chapter 6 — Line Cooks: The Linear Paradigms
Every recipe you have written binds its agent to one paradigm — react —
and every turn you have watched ran the same shape: think, act, observe,
repeat, bounded. It works. It is also one knife in a drawer of eight.
The recipe language declares the full set, and each paradigm is a different shape of thinking: an iteration loop, a staged line, a chain of isolated prompts, a review cycle, planners of three kinds, a shared board. This chapter teaches the four linear ones — the paradigms whose control flow you can draw as a line — with their mechanics verified in the framework’s own source, three real recipes you will write, and the captured truth about what each costs on a small local model.
# Where we left off
triage and gate_demo are registered; the full toolkit — types, routes,
gates, annotations — is in your hands. Today adds the paradigm directive
grammar and a choosing table you will use for the rest of the book.
# Technical requirements
- The end-of-Chapter-5 state: binaries, green workspace, Ollama serving
gemma4:e4b. - About forty minutes. Model-dependent steps say what they cost.
- Captured on the verification machine as always — including one runtime failure with a named cause, one infrastructure lock error, and one receipt this book refuses to pretend it understands.
# What a paradigm is
A paradigm is a reusable reasoning strategy: the control flow an agent
runs inside a step, independent of what the step is for. The recipe
declares agent inspector uses react; the loader lowers the step into an
execution graph whose nodes think, act, and observe; the model fills the
thoughts. Eight paradigms ship: react, rewoo, planner, htn,
reflection, chainer, blackboard, pipeline — and the set is closed.
A misspelled one is a load error listing the valid set.
Two facts make this more than vocabulary. First, each paradigm is a
contract — data in cognitionzoo/<paradigm>/contract.go declaring its
directives, their grammar, their order, and the runtime behaviors it
guarantees. The DSL directive table is generated from those contracts, so
what you can write is exactly what the paradigm promises to execute.
Second, some paradigms compose others: reflection’s contract says
Composes: ["react"] — its review loop runs over a react delegate. You
are not choosing eight rivals; you are choosing how they snap together.
# react, deeply (you have been running it all along)
The bounded think-act-observe loop, now with its engine exposed. The
package doc splits it across eight files with self-explanatory names:
react_think_node.go generates the decision, react_act_node.go invokes
the tool, react_observe_node.go captures the observation,
react_decision.go routes between them, react_recovery.go handles tool
errors, react_completion.go detects done.
The decision. Each think step asks the model for a JSON decision —
thought, action, tool, arguments, complete — the same JSON you
watched stream in Chapter 2’s feed. Parsing that decision is where the
runtime earns its honesty.
Decision repair. When a decision arrives unparseable, the runtime
does not guess and does not spin silently. The captured flow, from
react_think_node.go as of the pin:
- Parse the response text. Parsed with a tool or a completion? Done.
- Thinking-model support: if the response spent everything in the reasoning channel, parse the decision out of the thinking — the reasoning is first-class material, never discarded.
- If the model profile declares
repair.strategy: llm(the shippedgemma4profile does), make one bounded repair call that asks the model to fix its own malformed output — and the repair must produce actual JSON. The source is emphatic: prose that looks like an apology is not blessed as a completed decision. - Still nothing? The step fails with the error you met in Chapter 2:
react decision unparseable (finish_reason=…, strategy=…). - And one comment from the source worth taping to your monitor: an unparseable decision is never a completion. A truncated think-model response once ended a step “successfully” with no work done; the guard exists so it cannot again.
The bound. until <n> caps the loop — a react-only directive, a
hard stop, a positive integer. Without it the runtime’s default budget
applies. Chapter 2’s “bounded” finally has its mechanism.
# Hands-on
Three recipes, one per remaining linear paradigm. Each is small by design — Chapter 5 taught what ambition costs on a CPU.
# 1. pipeline — a plan stage and an implement stage
relurpify_cfg/euclo/add_truncate.erpe:
thoughtrecipe euclo.thoughtrecipe.add_truncate
"Add a truncate function to stringkit, planned first, implemented and verified second."
trigger as capability:
may read workspace
may write workspace
may invoke ["file_read", "file_list"]
family ["implementation"]
keyword ["truncate"]
input workspace: "**/*"
input prompt: user.request
agent planner uses pipeline
agent implementer uses pipeline
pipeline:
stage plan:
run planner:
may invoke ["file_read", "file_list"]
from input.prompt
goal """Read stringkit.py and test_stringkit.py. State where a truncate(text, limit) function fits and which behaviors a test should assert. Do not write files. The user request is: {{ index . "task.instruction" }}"""
capture result -> state.plan
stage implement:
run implementer:
may invoke ["file_read", "file_write", "python_pytest"]
from state.plan
goal """Implement truncate(text, limit) in stringkit.py per the plan, add a test to test_stringkit.py, then run python_pytest on test_stringkit.py and report the real result. The plan is: {{ index . "state.plan" }}"""
capture result -> state.implementationRead the shape: stages run in document order — the contract’s one guarantee is “stages execute in declared order” — and the second stage reads the first stage’s capture. This is Chapter 2’s R&D discipline (reduce, delegate) rendered as DSL: planning explores in one context; implementation starts from the clean plan, not the exploration debris.
# 2. chainer — three links, isolated prompts
relurpify_cfg/euclo/summarize_funcs.erpe:
thoughtrecipe euclo.thoughtrecipe.summarize_funcs
"Summarize stringkit in three ordered links: inventory, per-function behavior, test coverage."
trigger as capability:
may read workspace
may invoke ["file_read", "file_list"]
family ["investigation"]
keyword ["summarize"]
input workspace: "**/*"
input prompt: user.request
agent surveyor uses chainer
run surveyor:
may invoke ["file_read", "file_list"]
from input.prompt
link:
from input.prompt
prompt "You are a precise code surveyor. List every public function in the Python module named in the user content, each with a one-line purpose. Output a plain list."
capture result -> state.inventory
link:
from state.inventory
prompt "You are a precise code analyst. For each function in the inventory given in the user content, describe its behavior in one sentence and name the input class it handles (plain text, whitespace-heavy, punctuated). Output the list."
capture result -> state.behaviors
link:
from state.behaviors
prompt "You are a test-coverage reviewer. The user content lists behaviors of a string module. For each behavior, state whether it is likely covered by a test suite that focuses on slugify punctuation and whitespace handling, and name the gap if not. Output the list."
capture result -> state.coverageThe chainer is a chain of isolated prompts: each link: block carries a
prompt "…" (the link’s system prompt), from keys (fed into the
prompt context), and a capture (the output key). No goal — links
do not take one; the prompt clause is the instruction. Write the version
above, not the goal-shaped one the grammar tempts you toward — the
captured story of why is the next section’s best lesson.
# 3. reflection — review, then revise when it says so
relurpify_cfg/euclo/polish_docstring.erpe:
thoughtrecipe euclo.thoughtrecipe.polish_docstring
"Review stringkit's docstrings against the code, then revise the weakest one."
trigger as capability:
may read workspace
may write workspace
may invoke ["file_read", "file_list"]
family ["refactor"]
keyword ["docstring"]
input workspace: "**/*"
input prompt: user.request
agent reviewer uses reflection
agent fixer uses react
run reviewer:
may invoke ["file_read", "file_write", "python_pytest"]
from input.prompt
review """Read stringkit.py and test_stringkit.py. Judge whether every public function's docstring matches its actual behavior. Write a verdict listing any docstring that is wrong, stale, or missing. The user request is: {{ index . "task.instruction" }}"""
revise when scratch.review contains issues:
run fixer:
may invoke ["file_read", "file_write", "python_pytest"]
goal """Fix the docstring issues named in the review verdict, changing only docstrings and comments. Then run python_pytest on test_stringkit.py and report the real result. The verdict is: {{ index . "scratch.review" }}"""
capture result -> state.revisionThe reflection contract pins the mechanics: review writes its verdict
into the ephemeral scratch namespace — scratch.review and
scratch.review_issues — and revise when <predicate>: (whose body may
hold run or delegate) executes when the predicate reads true, then
re-reviews, bounded. Order is enforced: review before revise, and a
revise without a review is a load error. This is self-critique as
control flow — and note what it is not: not a second model
double-checking the first (that is the critic pattern, built in the
companion builder’s book), but one model’s structured look-before-you-
leap.
# 4. Lint — and let it catch you
~/relurpify/relurplint --check recipes --workspace .On the road to these three passing, the linter caught the author once, and the catch is permanent documentation:
# polish_docstring.erpe — a nested run's agent must exist:
[error recipes.lower] ...:21:9: unknown agent "fixer"run fixer: inside a revise block still needs agent fixer uses react
declared at the top — nesting does not inherit declarations. The other
near-miss never reached the linter because the contract system caught it
at authoring time: the first draft carried until 2 inside the
reflection block, and reflection’s contract (checked against the
DirectiveSpec table before writing) declares review and revise only —
until is react’s directive. Reflection bounds its own loop; a foreign
directive in a paradigm block is a load error named by paradigm and
directive. Your lint should end:
relurplint: all checks passed (0 diagnostics)# 5. Register, pin, dispatch — and read the receipts
Restart relurpish so all three register. Pin and dispatch the chainer (read-only, no approval expected):
/recipe euclo.thoughtrecipe.summarize_funcsPinned recipe for this session: euclo.thoughtrecipe.summarize_funcs (dispatch runs exactly this recipe; /recipe clear to unpin)Then the task:
Summarize stringkit: list its public functions, describe their behavior, and check test coverage.And now the honest part — the captured session log from this chapter’s verification runs, told straight:
-
The first chainer draft failed in three seconds, with a name. The draft used
goal-shaped links (grammar-legal — the linter passed it — and the boot loaded it). The runtime failed the step immediately:{"outcome": "failure", "llm_calls": "0", "outcome_kind": "execution failed: chainer link \"link\" requires a `prompt` clause"}This is the recipe system’s three-layer validation story, now complete: the lint checks grammar, types, and declarations; the boot resolves vocabulary and prompt imports; the runtime enforces what only execution can know — here, that every link carries its prompt clause (
named/euclo/thoughtrecipes/chain_builder.gonames the exact requirement). The failure cost one second and named the fix. Compare that with any framework where the equivalent mistake is a silent empty response. -
The corrected chainer was then rejected at the door — by infrastructure. The re-run’s boot reported
boot.degradedand the task was rejected:Cannot acquire directory lock on ".../.relurpify_state/memory/ast_index". Another process is using this Badger database. err: resource temporarily unavailableThe cause: an earlier relurpish instance was still alive, holding the state directory’s single-writer lock. One workspace, one relurpish at a time. When a task is rejected with a lock error, look for the zombie session before you suspect your recipe.
-
With a clean instance, the corrected chain dispatched pinned and completed — and the completion receipt read
outcome: "success"withllm_calls: "0", which is not what three model-backed links should produce. This book does not print explanations it has not verified: the zero-call success is flagged[NEEDS CONFIRMATION: chainer link execution and counters at your pin], and it is exactly the kind of anomaly your receipts now make you able to catch. Chapter 9’s deterministic harness exists partly for this. -
Cost expectations, stated plainly: each react turn on the verification CPU ran one to five minutes per model call; a two-stage pipeline or a review-then-revise reflection multiplies that. The recipes are written and linted regardless; run them on your hardware and read the receipts — that practice, not this chapter’s capture log, is the transferable skill.
# Gotchas
- Directives are paradigm-typed.
untilis react-only (its contract declares it; reflection’s declaresreview/revise); putting a foreign directive in a paradigm block is a load error named by paradigm and directive. Check the contract before you reach for a directive. - Nested runs still declare their agents.
run fixer:inside a revise body needsagent fixer uses reactat recipe top level. - Chainer links take
prompt, nevergoal. Grammar-legalgoallinks pass the lint and the boot, then fail at execution naming the missing clause. When the runtime names a requirement, fix the recipe it names — the error message is the manual. - One workspace, one relurpish. The state directory is single-writer; a second instance degrades boot and rejects tasks with a Badger lock error naming the path. Kill the zombie first.
- A paradigm is a contract, not a suggestion. What a paradigm
guarantees is data (
contract.go), checked by its own conformance tests — when you wonder “what does reflection actually promise,” the contract answers, and Chapter 2’scomposesinsight (reflection runs react underneath) comes from reading it. - Cost scales with shape. Stages, links, and review cycles are multiplicative on model time. Small local models make two-phase recipes a minutes-per-turn affair; that is arithmetic, not a defect.
# Summary
- Eight paradigms ship; the four linear ones — react, pipeline, chainer, reflection — cover loop, line, chain, and review shapes.
- react’s mechanics are now yours: the JSON decision, the thinking- channel parse, the one bounded LLM repair, and the guard that keeps an unparseable decision from ever reading as completion.
- pipeline’s guarantee is order; chainer’s links are isolated
prompt+from+capture units; reflection reviews into
scratch.reviewand revises on a predicate, bounded. - The three-layer validation story — lint, boot, runtime — catches different classes of mistakes, and each layer’s failure names its fix.
- The receipts catch what the layers cannot: a lock rejection, and a success receipt that looks wrong. You are now equipped to notice both.
# Check yourself
- Your react step ends with
react decision unparseable (finish_reason="stop", strategy="llm"). Walk the four things the runtime tried before failing, and name the profile key that armed step three. - Why does a
revise when scratch.review contains issues:predicate readscratch.reviewrather thanstate.review— and what does the reflection contract say lives in that namespace? - A teammate’s pipeline’s second stage does not see the first stage’s result. What two recipe lines are probably wrong, and what does the pipeline contract guarantee about stages that do work?
- The chainer v1 recipe passed the lint and loaded at boot, then failed at execution. What does that tell you about which layer checks which class of requirement — and why is a three-second named failure still better than a silently empty response?
- A task is rejected with a Badger directory-lock error on
memory/ast_index. What is the actual problem, what is the fix, and why does the lock exist at all?
Answers. 1. Parse the response text; parse the decision out of the
thinking channel (thinking models put intent there); one bounded LLM
repair call (armed by repair.strategy: llm in the model profile — the
shipped gemma4 profile); then the named failure. 2. The contract
guarantees authored review verdicts live in the ephemeral scratch
namespace — scratch.review and scratch.review_issues — because the
verdict is step-local material, not durable recipe state. 3. The first
stage must capture result -> state.plan (or any state name) and the
second must from state.plan (its own from line); the contract
guarantees stages execute in declared order — the data flow is the
recipe’s job. 4. The lint checks grammar, types, and declarations
without a runtime; boot resolves vocabulary and imports against live
registries; only execution can enforce requirements that are about
execution itself (every link needs its prompt clause). A named failure
beats a silent one because it is actionable — the error is the fix. 5.
Another relurpish instance is still running against this workspace,
holding the state directory’s single-writer lock; find and close the
zombie (or restart cleanly). The lock protects the integrity of the
embedded stores (the AST index, the event log) — two writers to a Badger
database would corrupt it.
# Try it yourself
- The choosing table, filled by you. For each of the four linear paradigms, write one sentence naming a stringkit task it fits — and one it does not. Compare with the chapter’s verdicts; disagreement with a reason is the point.
- The lock error, deliberately. With a turn running in one
terminal, start a second
relurpish chat --workspace .in another. Read the doctor/boot output for the lock message, then exit the second instance. You have now seen the single-writer rule from the safe side.
# Where next
Four line cooks trained. But some work is not a line: it decomposes, or
it plans before it moves, or it needs a shared board where specialists
contribute. Chapter 7 takes the planning family — planner, rewoo,
htn, blackboard — with the same method: contract first, recipe
second, receipts always. And it answers the craft question the linear
set leaves open: when does planning beat doing?
The lab for this chapter lives in
docs/cooking-agentic-thoughts/labs/ch06-linear-paradigms/ — all three
recipes, the captured failures and receipts, and the paradigm contract
pointers.