<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Q00</title>
    <description>The latest articles on DEV Community by Q00 (@q00).</description>
    <link>https://dev.to/q00</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4069359%2Fafec57b4-b30b-4c0d-9414-4965efa2a1e1.png</url>
      <title>DEV Community: Q00</title>
      <link>https://dev.to/q00</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/q00"/>
    <language>en</language>
    <item>
      <title>Your agent loop is teaching the model to cheat</title>
      <dc:creator>Q00</dc:creator>
      <pubDate>Sun, 09 Aug 2026 01:43:54 +0000</pubDate>
      <link>https://dev.to/q00/your-agent-loop-is-teaching-the-model-to-cheat-48oa</link>
      <guid>https://dev.to/q00/your-agent-loop-is-teaching-the-model-to-cheat-48oa</guid>
      <description>&lt;p&gt;Wrapping a loop around an AI coding agent is the obvious next move once single-shot prompting stops working. Run it, score it, retry if the score is low.&lt;/p&gt;

&lt;p&gt;Two walls show up right after:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The score goes up and the work is still wrong.&lt;/strong&gt; The agent learned to satisfy the grader, not the task.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failure is a dead end.&lt;/strong&gt; Every piece of the loop exists, but a failed run never feeds the next one.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both are environment design problems rather than model problems, and they don't go away as models get stronger. A stronger model finds the shortcut faster.&lt;/p&gt;

&lt;p&gt;I want to walk through both using a concrete, readable example: an RFC that recently landed in &lt;a href="https://github.com/Q00/ouroboros" rel="noopener noreferrer"&gt;Ouroboros&lt;/a&gt;, an open-source Agent OS. The design is written up in &lt;a href="https://github.com/Q00/ouroboros/issues/1917" rel="noopener noreferrer"&gt;issue #1917&lt;/a&gt;, implemented in &lt;a href="https://github.com/Q00/ouroboros/pull/1916" rel="noopener noreferrer"&gt;#1916&lt;/a&gt;. You can read the whole thing yourself, which is why I'm using it rather than describing something abstract.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wall 1: you handed the answer key to the candidate
&lt;/h2&gt;

&lt;p&gt;Most agent harnesses render the acceptance criteria straight into the worker's prompt, including the command that will grade it and the assertion it will be graded against. The motive is reasonable: if the agent knows how it will be checked, it can aim at the right target.&lt;/p&gt;

&lt;p&gt;Ouroboros did exactly this. &lt;code&gt;_build_success_contract_block&lt;/code&gt; rendered &lt;code&gt;verify_command&lt;/code&gt; and &lt;code&gt;Expected output: &amp;lt;assertion&amp;gt;&lt;/code&gt; into the worker's instructions. A second leak was harder to spot: on retry, the failure reason carried the assertion's &lt;code&gt;repr()&lt;/code&gt;, which rode &lt;code&gt;result.error&lt;/code&gt; back into the next prompt.&lt;/p&gt;

&lt;p&gt;Once the agent can see the assertion, satisfying the assertion is cheaper than satisfying the requirement. The RFC names this directly: a struggling worker's cheapest path is to game the assertion string rather than implement the acceptance criterion, with a postmortem (&lt;code&gt;seed_2be2907edc07&lt;/code&gt;) attached. That is textbook reward hacking. You think you are measuring capability; you are measuring the ability to copy an answer.&lt;/p&gt;

&lt;h3&gt;
  
  
  The fix: hide it unconditionally
&lt;/h3&gt;

&lt;p&gt;Both leak paths have to close. Closing the forward one alone accomplishes nothing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Forward: &lt;code&gt;_build_success_contract_block&lt;/code&gt; now renders only the AC description and &lt;code&gt;expected_artifacts&lt;/code&gt;. The harness verifies independently, so the worker never sees the grading logic.&lt;/li&gt;
&lt;li&gt;Backward: the verify-gate failure reason no longer embeds the assertion &lt;code&gt;repr()&lt;/code&gt;. Retry hints come from a dedicated assertion-safe builder (&lt;code&gt;orchestrator/retry_hints.py&lt;/code&gt;) that filters the assertion string out of every fragment, including the 2000-character tail of command output.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That 2000-character tail is the part worth copying. Plug the main path, forget the log tail, and the leak is still open.&lt;/p&gt;

&lt;p&gt;The RFC also records that a "disclosure level" config knob was proposed and rejected. An information barrier that can be turned off will get turned off on some afternoon when someone is behind schedule, and nobody will notice, because the scores will look better afterward.&lt;/p&gt;

&lt;h3&gt;
  
  
  What a stuck agent gets instead
&lt;/h3&gt;

&lt;p&gt;Hiding everything would leave the worker flailing, so the RFC pairs it with a hint loop. The next round's instructions are reconstructed from what the session actually did: the tool-call trace, the evidence manifest (reusing &lt;code&gt;deliver_gate.load_ac_evidence_manifest&lt;/code&gt;, read-only), and the verifier's outcome. Not from the assertion.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Disclose the answer&lt;/th&gt;
&lt;th&gt;Trace-based hints&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Agent sees&lt;/td&gt;
&lt;td&gt;"the assertion requires output == &lt;code&gt;X&lt;/code&gt;"&lt;/td&gt;
&lt;td&gt;"you called A and B, artifact C is missing, verification failed at step 3"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cheapest path&lt;/td&gt;
&lt;td&gt;fabricate &lt;code&gt;X&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;actually produce C&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Does a rising score mean rising capability?&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is information asymmetry, the same arrangement human exams use: the examiner knows the answer, the candidate only learns where they went wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wall 2: failure is a dead end
&lt;/h2&gt;

&lt;p&gt;From the RFC: every piece already existed (the verify gate, the run-to-eval chain, &lt;code&gt;evolve_step&lt;/code&gt;, the Ralph driver, &lt;code&gt;focus.select_evolution_focus&lt;/code&gt;), but nothing connected them.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A failed run never entered formal evaluation. Failure was terminal and surfaced as BLOCKED.&lt;/li&gt;
&lt;li&gt;A rejected evaluation never entered evolution. Also terminal.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The loop existed as three disconnected segments. Failures were reported, not digested.&lt;/p&gt;

&lt;h3&gt;
  
  
  The fix: chain run to eval to evolve
&lt;/h3&gt;

&lt;p&gt;Three constraints carry the weight:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Failed runs also chain into evaluation.&lt;/strong&gt; The &lt;code&gt;_run_succeeded&lt;/code&gt; gate is relaxed, so any run that produced a session chains into formal evaluation. Fail-open is preserved: an enqueue failure never flips the run's result.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. A rejected evaluation triggers a budgeted evolution loop.&lt;/strong&gt; Nobody reimplemented a convergence loop here. The evaluate job's terminal path enqueues the existing evolution machinery when &lt;code&gt;final_approved is False&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The new piece is a Gen1 bridge: the run's seed plus the chained evaluation's multi-AC checklist get projected into lineage events, so &lt;code&gt;evolve_step&lt;/code&gt; replays the plain run as Generation 1 and starts Generation 2 already focused.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Only failed ACs go to the next generation; passing ones freeze.&lt;/strong&gt; This one buys the most.&lt;/p&gt;

&lt;p&gt;Making it work required a checklist-to-&lt;code&gt;ACResult&lt;/code&gt; converter that satisfies a strict bar: complete index coverage, verbatim &lt;code&gt;ac_content&lt;/code&gt;, and &lt;code&gt;semantic_ac_key&lt;/code&gt; identity. That strictness is what lets &lt;code&gt;focus.select_evolution_focus&lt;/code&gt; freeze the passing ACs.&lt;/p&gt;

&lt;p&gt;A loop that does not freeze what already passed will redo work it already got right, burning tokens and breaking correct implementations along the way. The symptom is a score that oscillates between generations.&lt;/p&gt;

&lt;h2&gt;
  
  
  The loop has to be able to stop
&lt;/h2&gt;

&lt;p&gt;A loop will not stop on its own.&lt;/p&gt;

&lt;p&gt;Ouroboros reuses Ralph's existing stop conditions: QA pass, convergence, oscillation detection, grade regression, and wall clock. &lt;code&gt;execution.auto_evolve_max_generations&lt;/code&gt; defaults to 3, clamped to 1..10. BLOCKED only happens after the budget is exhausted.&lt;/p&gt;

&lt;p&gt;Oscillation detection and grade regression are the two that catch false convergence: a score bouncing A to B to A to B, or a generation worse than its parent. Both halt the loop instead of burning more tokens.&lt;/p&gt;

&lt;p&gt;One more fix. &lt;code&gt;evolution/loop.py&lt;/code&gt; had a bare &lt;code&gt;except&lt;/code&gt; with three silent paths to &lt;code&gt;evaluation_summary=None&lt;/code&gt;. It now records a rejected summary carrying the failure reason, which preserves fail-closed focus semantics while making the failure durable.&lt;/p&gt;

&lt;p&gt;Swallow an exception in a single run and you are wrong once. Swallow it in a loop and the error is amplified across N generations while your logs show nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cheap gates before expensive ones
&lt;/h2&gt;

&lt;p&gt;The same pattern runs through the rest of the design.&lt;/p&gt;

&lt;p&gt;Evaluation is tiered: Mechanical (free, deterministic checks), then Semantic, then Multi-Model Consensus. Anything rejected at layer one never reaches an LLM judge.&lt;/p&gt;

&lt;p&gt;The interview stage uses a number instead of a tier. Ambiguity is quantified as the inverse of weighted clarity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ambiguity = 1 - Sum(clarity_i * weight_i)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A seed spec cannot be generated until it is &amp;lt;= 0.2, and convergence waits for ontology similarity &amp;gt;= 0.95. Two mathematical gates, and the README states the idea behind both: don't write until it's clear, don't stop until it's stable.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to check in your own loop
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Don't show the grading criteria to the candidate.&lt;/strong&gt; Audit whether your harness leaks assert strings back through error messages, log tails, or retry prompts. Close the forward and backward paths, and don't make it a config option.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failure needs a next step.&lt;/strong&gt; Failed runs should reach evaluation, rejected evaluations should reach evolution, and passing work should freeze.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The loop must halt and must detect false convergence.&lt;/strong&gt; Oscillation, grade regression, wall clock, generation budget.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Full RFC: &lt;a href="https://github.com/Q00/ouroboros/issues/1917" rel="noopener noreferrer"&gt;Q00/ouroboros#1917&lt;/a&gt;. Implementation: &lt;a href="https://github.com/Q00/ouroboros/pull/1916" rel="noopener noreferrer"&gt;#1916&lt;/a&gt;. Design docs live in-tree under &lt;code&gt;docs/hidden-checklist-convergence/&lt;/code&gt; (requirements, architecture, implementation). The project is &lt;a href="https://github.com/Q00/ouroboros" rel="noopener noreferrer"&gt;github.com/Q00/ouroboros&lt;/a&gt;: MIT, local-first, and it sits in front of 13 runtime families.&lt;/p&gt;

&lt;p&gt;If you're running multi-generation agent loops in production, how do you handle false convergence? That's the part I've seen the fewest good answers to.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>architecture</category>
      <category>testing</category>
      <category>ai</category>
    </item>
    <item>
      <title>Specification-first AI development with Ouroboros</title>
      <dc:creator>Q00</dc:creator>
      <pubDate>Sun, 09 Aug 2026 00:03:30 +0000</pubDate>
      <link>https://dev.to/q00/specification-first-ai-development-with-ouroboros-22m6</link>
      <guid>https://dev.to/q00/specification-first-ai-development-with-ouroboros-22m6</guid>
      <description>&lt;p&gt;Most AI coding tools fail before they write a single line of code. The prompt was vague, and the model quietly filled the gaps with assumptions you never agreed to.&lt;/p&gt;

&lt;p&gt;You ask for "a task management CLI." The model picks a data model, a priority scheme, a persistence layer — all reasonable, none of them yours. You find out three files in, during review, and you rework it. That's the loop most of us are stuck in: prompt, guess, rework, repeat.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Q00/ouroboros" rel="noopener noreferrer"&gt;Ouroboros&lt;/a&gt; is an open-source Agent OS that fixes the input instead of the output. It's a local-first runtime layer that sits in front of Claude Code, Codex CLI, OpenCode, Gemini CLI, GitHub Copilot CLI, Kiro, Hermes, Pi, and Zcode, and replaces ad-hoc prompting with a five-stage, replayable workflow: &lt;strong&gt;interview, seed, execute, evaluate, evolve.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The real problem is unclear intent
&lt;/h2&gt;

&lt;p&gt;Ouroboros' own framing of this is a simple table:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Problem&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;th&gt;Ouroboros fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Vague prompts&lt;/td&gt;
&lt;td&gt;AI guesses, you rework&lt;/td&gt;
&lt;td&gt;Socratic interview exposes hidden assumptions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No spec&lt;/td&gt;
&lt;td&gt;Architecture drifts mid-build&lt;/td&gt;
&lt;td&gt;Immutable seed spec locks intent before code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manual QA&lt;/td&gt;
&lt;td&gt;"Looks good" isn't verification&lt;/td&gt;
&lt;td&gt;3-stage automated evaluation gate&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The fix targets clarity, not capability.&lt;/p&gt;

&lt;h2&gt;
  
  
  The loop
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Interview -&amp;gt; Seed -&amp;gt; Execute -&amp;gt; Evaluate
    ^                           |
    +---- Evolutionary Loop ----+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Interview&lt;/strong&gt;: Socratic questioning surfaces the assumptions you didn't know you were making.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Seed&lt;/strong&gt;: your answers crystallize into an immutable specification: acceptance criteria, ontology, constraints.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execute&lt;/strong&gt;: the seed runs through a Double Diamond decomposition (Discover → Define → Design → Deliver).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evaluate&lt;/strong&gt;: a 3-stage gate: Mechanical (free, deterministic checks) → Semantic → Multi-Model Consensus.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evolve&lt;/strong&gt;: the evaluation output feeds back into the next generation's seed, and the cycle repeats until the system stops learning anything new.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each cycle is meant to converge, not just repeat. The stopping condition isn't a timer or a step count. It's math.&lt;/p&gt;

&lt;h2&gt;
  
  
  The interview ends when the math says so
&lt;/h2&gt;

&lt;p&gt;This is the part I found most concrete. Ouroboros scores ambiguity as the inverse of weighted clarity across four dimensions (goal, constraints, success criteria, and context for existing codebases):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ambiguity = 1 - Sum(clarity_i * weight_i)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A greenfield example from the README:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Goal:       0.9 * 0.4  = 0.36
Constraint: 0.8 * 0.3  = 0.24
Success:    0.7 * 0.3  = 0.21
                        ------
Clarity                = 0.81
Ambiguity = 1 - 0.81   = 0.19  &amp;lt;= 0.2 -&amp;gt; Ready for Seed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A Seed, the immutable spec that execution is built from, can't be generated until ambiguity drops to 0.2 or below. Above that threshold, you're still guessing at architecture, so the system keeps asking questions instead of letting you (or the agent) start writing code on a shaky foundation.&lt;/p&gt;

&lt;p&gt;The evolutionary loop has a matching gate on the way out: it stops when ontology similarity between consecutive generations reaches 0.95, with separate detection for stagnation, oscillation, and repetitive feedback so it doesn't spin forever on a question it already answered.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trying it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://raw.githubusercontent.com/Q00/ouroboros/main/scripts/install.sh | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, inside a supported AI coding agent session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ooo interview &lt;span class="s2"&gt;"I want to build a task management CLI"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The installer auto-detects which supported runtime you're using (Claude Code, Codex CLI, GitHub Copilot CLI, OpenCode, Hermes, Gemini, Kiro CLI, Pi CLI, Zcode) and registers the MCP server where the host supports it. Everything after &lt;code&gt;ooo interview&lt;/code&gt; (seed generation, execution, evaluation, the evolve loop) is driven from inside that same session. There's also a plain &lt;code&gt;ouroboros&lt;/code&gt; CLI for running things from the terminal directly (&lt;code&gt;ouroboros run seed.yaml&lt;/code&gt;, &lt;code&gt;ouroboros status executions&lt;/code&gt;, and so on).&lt;/p&gt;

&lt;p&gt;There's also &lt;code&gt;ooo ralph&lt;/code&gt;, which runs the evolutionary loop persistently across session boundaries. If your machine restarts mid-loop, it reconstructs the lineage from an event store and picks up where it left off rather than starting over.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this changes in practice
&lt;/h2&gt;

&lt;p&gt;Ouroboros makes the input to your first try something you actually agreed to — a paper trail (the seed, the ledger, the evaluation stages) you can inspect afterward instead of reconstructing intent from a diff. It won't guarantee better code on the first attempt; it guarantees you know what you asked for.&lt;/p&gt;

&lt;p&gt;If you've felt the specific pain of "the AI built something reasonable, just not the thing I meant," a spec-first loop like this is worth a look. It's MIT-licensed, Python 3.12+, and the repo has runtime guides for each supported CLI: &lt;a href="https://github.com/Q00/ouroboros" rel="noopener noreferrer"&gt;github.com/Q00/ouroboros&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>cli</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
