Status: accepted (partial) (2026-06-23) — the Verified Action quad is
implemented on tool nodes: layers A (postcondition), E (policy
spine), and C (self-repair) ship enabled; B (agent recovery) is
wired but opt-in and OFF by default (recovery.max_agent_attempts: 0).
Layer D (robust primitive library, e.g. iterion __commit) and
extending the quad to compute nodes are deferred follow-ups. Originally
proposed / exploratory the same day (kept below).
The quad is goal + command/script (recipe) + postcondition +
policy on a tool node — all optional and fully backward-compatible (a
node with no postcondition behaves exactly as before: recipe only, exit
code = success).
tool properties goal:, postcondition:,
policy: (required | recover | best_effort), and a recovery:
block (max_repair_attempts, max_agent_attempts, model,
agent_tools). Parser + AST + IR + unparse round-trip them.tool_called
events; the per-node outcome is a node_verified_action event.recipe == postcondition), C106 (recovery bounds under a
non-recover policy — dead config). A gate stays the degenerate quad
(recipe == postcondition, no rungs 3–4, policy: required).bots/secured-renovacy’s commit_changes node now
carries a postcondition (“working tree has no relevant uncommitted changes
left, caches excluded”) + policy: recover, so it self-heals past the two
brittle failures cited below instead of hard-blocking.Status (original): proposed / exploratory (2026-06-23) — reflection, no implementation yet.
Deterministic tool/compute nodes are iterion’s anti-Goodhart / anti-façade
backbone: scan_health, coverage gates, streak_check, cap_findings (see
docs/workflow_authoring_pitfalls.md). They are
fast, cheap, auditable, and never drift — that is why review loops are
trustworthy.
But on the action side they are brittle, and brittleness hard-blocks a run. Evidence from the 2026-06-23 re-test, on a single node, twice in a row:
secured-renovacy commit step failed on a stray Go module cache overflowing
Node’s 1 MB execSync maxBuffer (…1050864 more characters, exit 1);:!__pycache__ — git short-form
pathspec magic mis-parsing a _-leading pattern (Unimplemented pathspec magic
'_', exit 128).Each needed a code patch. An LLM asked to “commit the relevant changes, excluding
caches” would have adapted past both without a human in the loop.
sec-audit-source’s voters similarly hard-failed on one missing structured-output
field. The deterministic path’s strength (no drift) is its weakness (no
adaptation → hard block on the unanticipated).
So the answer to “should non-LLM nodes have an LLM fallback?” is: yes for action nodes, no for gates. The reflex to “just let a model handle it” is right for the commit and wrong for the coverage gate.
A. Adaptive action + deterministic postcondition (recommended where a clean
postcondition exists). An agent achieves the goal (commit), then a cheap
deterministic node verifies the postcondition (e.g. “exactly one commit on
iterion/run/* whose tree excludes go/, target/, __pycache__”). You get
adaptability AND safety — and the verify is mechanical (a property check, not a
quality judgment), so it stays Goodhart-safe. This is the cleanest split:
adaptive doing, deterministic checking.
B. Deterministic-first, LLM-recovery-on-failure — a DSL affordance like
on_error: agent("commit the changed manifests/lockfiles/vendor, exclude caches").
The fast deterministic command stays the default; only on a hard exit does the
runtime hand the agent {command, stderr, goal} + real tools to finish, then
continue. 95% cheap/predictable path, 5% adaptive recovery instead of a block.
Minimal, general, opt-in. (Generalizes the engine’s existing resumable-failure
idea into an in-run recovery step.)
C. LLM-repairs-command, determinism-executes (self-healing, auditable middle ground). On failure an LLM proposes a corrected command (it sees the error); the runtime re-runs it deterministically, bounded attempts. Execution stays deterministic + visible in events (you see the fixed command); the model only fixes the recipe, never does the side effect blind. Good for “the command was almost right” cases (both Renovacy failures were exactly this).
D. Robust-primitive library (complements A–C). Most brittleness is ad-hoc
shell: git pathspecs, JSON shape handling, buffer limits, glob portability
(cf. the dash/bash pitfalls doc). Ship tested iterion primitives — e.g. an
iterion __commit --exclude-caches subcommand, a json-shape normalizer,
a __git-status-clean — that nodes call instead of fragile inline shell.
Robust by construction; shrinks the surface A–C have to cover.
E. Per-node failure semantics — a node declares required | best_effort |
recover. Today most hard-fail; the commit should recover (B/C), an optional
enrichment should best_effort, a security gate stays required. Also
subsumes the campaign’s “inconsistent rate-limit handling” finding (some nodes
graceful-pause, others hard-fail — make it a declared policy).
Determinism is kept exactly where it guarantees trust (gates) and softened exactly
where it only adds brittleness (actions). Bots get materially more reliable —
fewer hard blocks on unanticipated inputs — without weakening the anti-façade
guarantees. Cost: a recovery agent invocation on the rare failure path, and a new
DSL surface (on_error: / failure-semantics) to design carefully so it can’t be
abused to paper over a real gate failure (recovery is for actions, never gates).
The five approaches are not alternatives; they are layers that compose, and they sit at different levels:
The load-bearing insight: A is what makes B and C safe. On their own, an LLM recovery / self-repair reintroduces the façade risk (the model can claim it committed). With a cheap deterministic postcondition checked after every rung, adaptive recovery becomes trustworthy — the agent cannot fake success past a property check. So A is not “one option among five”; it is the enabler that lets you mix in the adaptive ones without losing the anti-Goodhart guarantee.
Every node becomes a quad: goal + recipe + postcondition + policy.
goal — the outcome, in words (fuel for the agent rungs).recipe — the deterministic command (the fast path; what self-repair fixes).postcondition — a cheap deterministic property check (the single source of truth).policy — required | recover | best_effort.Runtime escalation (cheapest → most adaptive, postcondition is truth at each rung):
goal with real tools, bounded.
Postcondition met → done.required→fail, best_effort→warn+continue.recipe == postcondition, no rungs 3–4, policy: required
(pure determinism — the Goodhart firewall, unchanged);nothing to commit exits 1 though the goal may be met; a command can exit 0
yet not achieve the outcome). Keying success on the postcondition + the
idempotent skip makes retries/resumes naturally correct. Both Renovacy failures
this session were “recipe almost right” → caught at rung 3 with zero human/code.iterion is already “adaptive generate + deterministic verify” — that is what a review loop is (agent generates, judges + gates verify). Action nodes are the lone exception: deterministic generate, no verify → brittle and unguarded. This pattern simply brings action nodes onto the same spine the rest of the engine already runs on. The synergy isn’t five features bolted together; it’s making one existing principle universal.
Recommended first cut: implement A+E as the spine (postcondition + policy on action nodes), add C (self-repair) as the cheap first recovery rung, B (agent) as the opt-in second rung, and grow D (primitives) for the operations that keep breaking (commit-with-cache-exclusion first). Prototype on the commit node.