Skip to content

Produce → deterministic gate → feed diagnostics to LLM fixer → bounded cycle

State: open/active · Genre: analysis

Use case: deterministic-gate-fix-cycle RFC mapping: UC4 (+UC3) Source: cross-repo survey of sksizer/* (2026-07-05), companion to the workflow-constellation RFC (PR #682).

The single most replicated pattern in the entire survey: run deterministic checks (cargo fmt/clippy/test, just full-check, prd validate), on failure splice the captured diagnostics into a fixer prompt, invoke the agent, re-run the check, bounded at MAX_RETRIES. It exists as a byte-identical vendored bash script across ~10 Rust repos, and is independently re-implemented in Python (DarkFactory), Rust (ontological), and prose (dev, ralph).

quality_check.sh: ordered CHECKS tuples, per-check while-loop with MAX_RETRIES=3, role.md + fix_*.md

  • raw check output composed into a fixer prompt piped to claude-code, stop-on-first-unrecoverable — vendored byte-identically (md5-verified) into rust-cmd-spec, rust-mcp-proxy, rust-dir-aspect, rust-repo-checkout, rust-markdown, rust-obsidian, rust-agent-adapt, rust-ontogen, and rust-evented-worker.

Where: scripts/quality_check.sh · scripts/quality_check/fix_lint.md · scripts/downstream.txt

Pain: The retry counter, diagnostics splicing, gate ordering, and dry-run are hand-rolled once and then copy-distributed to ~10 repos because the workflow cannot be a library dependency.

on_failure=‘retry_agent’: the shell-task dispatcher captures failed-check output, binds it to {{CHECK_OUTPUT}}, synthesizes a retry AgentTask from the last agent step, re-invokes once, re-runs the check — with implicit last-agent-task coupling threaded through the dispatch loop.

Where: python/darkfactory/runner.py · python/darkfactory/workflow/definitions/prd/default/workflow.py

Pain: The cycle is entangled in dispatcher state instead of being a declarative combinator.

Default workflow setup→delegate→validate with CycleBack policy: cargo/pnpm check failures aggregate into failure_context, string-appended to the next agent prompt (‘Previous attempt failed… fix the issues above’), bounded by max_attempts=3.

Where: src-tauri/src/engine/validation.rs · src-tauri/src/engine/agent.rs · src-tauri/src/engine/session.rs

Pain: UC4 as index-jumping inside a while loop, with diagnostics-to-prompt plumbing by string concatenation.

bring_up_to_date.sh run_with_quality_gate: LLM apply, then a just full-check loop feeding errors back via claude —continue up to 3 attempts, then PR; the deps scripts push the same gate-retry-revert loop entirely into prompt prose (MAX_RETRIES declared but unused).

Where: scripts/bring_up_to_date.sh · scripts/deps_update/task.md

Pain: Where the harness gives up, the retry loop migrates into the prompt and is enforced only by model compliance.

task-work Step 7: run the quality executor, on FAIL re-run with —log, fix, re-invoke, ‘do not proceed until OK’ — unbounded and in prose — with SHA-keyed finding baselines built solely to scope the gate to branch-introduced drift.

Where: plugin/lib/services/quality/run-checks.ts · plugin/lib/services/quality/baseline.ts · plugin/skills/task-work/SKILL.md

Pain: The cycle has no enforced bound, and the baseline machinery exists to compensate for the gate lacking typed, scoped diagnostics.

The typecheck/lint/test-before-commit gate exists only as prompt prose inside one opaque agent turn; the README concedes broken code compounds without real feedback loops.

Where: CLAUDE.md · prompt.md · README.md

Pain: The gate is honor-system: nothing outside the LLM verifies the checks ran.

@flow/workflow’s cycle combinator with a deterministic gate task is UC4 verbatim. Evidence-driven requirements: a typed diagnostics channel from gate to fixer (today it is string splicing everywhere), per-attempt structured reporting and cost bounds (absent in every implementation), and an enforced iteration cap (dev’s prose version is unbounded).

Builds on: 04 Introduces: cycle()

The implement → lint/test → feed-diagnostics-back → re-check loop as one combinator:

import { cycle } from "flowline/workflow";
const implementLoop = cycle("implement-loop", {
do: implement, // agent step (tools: Read/Edit/Bash) -> writes Diff
check: lintAndTest, // deterministic -> { ok, diagnostics }
onFail: fixFromDiagnostics, // agent step: reads diagnostics, edits, loops to check
maxIterations: 5, // stops on first passing check, else typed failure
});

The iteration counter, diagnostics threading, and stop-on-first-pass exit — the parts every repo rebuilds — live in the combinator, not the call site.


← Back to Notes