{"slug": "i-ran-170-agent-goals-for-0-49-the-field-test-found-10-issues-that-unit-tests", "title": "I Ran 170 Agent Goals for $0.49. The Field Test Found 10 Issues That Unit Tests Never Would.", "summary": "An engineer's field test of the open-source PlannerCritic engine, which uses one LLM to write plans and a second to review them, found 10 issues across 157 goals for $0.30 in v0.1.0, including a true failure, design issues, harness bugs, and a model limitation. Subsequent releases v0.2.0 and v0.2.1 fixed 41 bugs via code review, with the field test finding zero new issues, transforming it into a regression gate. The test cost $0.49 for 170 goals in v0.2.1.", "body_md": "This is article 4 in a series about building\n\n[PlannerCritic], an open-source engine where one LLM writes a plan and a second LLM reviews it.[Article 1]covers the 157-goal v0.1.0 field test.[Article 2]is about the critic severity bug.[Article 3]is about the planner capability gap. This one is a practical guide to field testing agent systems — from 157 goals at $0.30 in v0.1.0 to 170 goals at $0.49 in v0.2.1, and what 10→0 issues across three releases teaches about release engineering.\n\nUpdated for v0.2.1:The original article covered the v0.1.0 field test — 157 goals, $0.30, 10 issues found. Since then, the engine shipped v0.2.0 (170 goals, 31 code-review bugs fixed, 0 field-test-found issues) and v0.2.1 (170 goals, 10 more code-review bugs fixed, 0 field-test-found issues, live-critic boundary evaluator, operational benchmark). This update tells the full arc: from a diagnostic tool that found 10 issues to a regression gate that finds 0 — and why 0 is the hardest result to earn.If a field test returns 0 failures, your first instinct should be distrust. In v0.1.0, 0 failures meant our harness was silently broken. In v0.2.1, 0 field-test issues meant something very different: code review caught all 41 bugs before the LLM ran, turning the field test from a diagnostic sweep into an immutable regression gate.\n\nUnit tests test what you think to test. They use hand-crafted inputs that match your assumptions. They pass even when the system is wrong in ways you didn't anticipate.\n\nI know this because 57 of 65 assertion files were in the wrong format and the harness didn't even notice. It just quietly returned 0/0 results. No crash. No error. Just silence.\n\nI needed a field test that would run real goals against a real LLM and tell me what actually broke.\n\nThe first field test was a diagnostic. It found 10 issues across 157 goals in 35 domains for $0.30 in 60 minutes. Only 1 was a traditional failure. The rest were design problems, prompt gaps, and harness bugs that would have shipped silently.\n\n**1 True Failure:** The planner prompt didn't explain the branches schema. The LLM responded with `kind: \"rollback\"`\n\nand arrays of task objects where strings were required.\n\n**4 Design Issues:**\n\n`established_by`\n\nexpected a task ID or `env:`\n\nprefix; the LLM wrote bare fact names. Unit tests were green.`run_budget()`\n\ntakes 4 arguments but dispatch passed 5. Budget and replan dimensions showed 0/0.**2 Harness Bugs:**\n\n`trace.get(\"status\")`\n\ninstead of `trace[\"result\"][\"status\"]`\n\n. Five PASS scenarios were misreported as FAIL.**1 Model Limitation:** Local models (Qwen3.5-4B, Qwen3.5-9B) couldn't produce structured JSON.\n\n**2 Fundamental Properties:**\n\n`revision_cap=4`\n\nall escalated; the critic found different blockers on each revision.$0.30 in LLM API calls. 60 minutes of wall-clock time. Cheaper than debugging one production incident caused by a bad plan.\n\nThe v0.2.0 field test was fundamentally different. v0.1.0 was a diagnostic tool that found 10 issues in a greenfield engine. v0.2.0 was a validation tool that confirmed 31 bug fixes + 5 new enterprise domain packs + 6 new safety mechanisms all work correctly.\n\n**The field test itself found zero new issues** — the code review found all 31 bugs before the field test ran.\n\nThe v0.2.0 field-test program was substantially stronger than v0.1.0's, and that directly produced a higher-quality release:\n\n| Metric | Result |\n|---|---|\n| Balanced goals approved | 73/73 (100%) |\n| Strict goals escalated | 97/97 (100%) |\n| Adversarial goals escalated | 8/8 (100%) |\n| True failures | 0 |\n| Deterministic gate passes | 170/170 (100%) |\n| Security oracle | 7/7 correct, 35/35 flawed blocked, 21 traps |\n| Scorecard A | PASS |\n| Code-review bugs fixed | 31 |\n| Field-test-found bugs | 0 |\n\nv0.2.1 is a patch release. No new goals, no new domains, no schema changes. The field test is a **regression sweep** — re-run the same 170 goals and diff every verdict against the published v0.2.0 results.\n\nThe code review (#222) found 10 bugs in the M11 hardening diff. The field test validated the fixes. **30 verdict deltas vs v0.2.0 — all attributable, zero unexplained.**\n\n| Issue | Finding | Why It Matters |\n|---|---|---|\n| #232 | Histogram cycling detector dead under default config | A safety signal shipped but was unreachable unless you manually raised `revision_cap` above 3. The detector was there. It just never fired. |\n| #233 | Rollback-credible gate emitted bare task id as message | The blocker said `\"t2\"` . No description, no suggested fix. An operator would have no idea what to do. |\n| #234 | Finding ids collided — distinct defects merged silently | Two different producers offending against the same consumer produced byte-identical finding ids. The escalation system's `{f.id: f}` merge collapsed one defect. |\n| #235 | Live-critic runner lost entire run on mid-trial exception | One transient LLM timeout discarded 60 completed audits. No partial report, no error record. |\n| #236 | Adapter import test gutted to `pass`\n|\nA test named `test_all_adapters_importable` that asserted nothing. It would pass forever, even if every adapter was broken. |\n| #237 | Suggested-fix printed task id where group name belonged | \"Move out of parallel group 'deploy'\" — but 'deploy' is a task id, not a group name. |\n| #238 | Approval authority enforcement not wired to any shipped surface | Test-proven but unreachable from CLI/HTTP/MCP. Documented as F-14, deferred to v0.3.0. |\n| #239 | Evidence-drift metric pooled explanations across trials | A deterministic 2-finding critic scored drift=1.0 because the metric counted intra-trial variety as cross-trial drift. |\n| #240 | ApprovalGate stamped ambient goal posture, not contract | A STRICT contract bound onto a BALANCED goal produced an ApprovedPlan that said BALANCED. Downstream audits read the wrong regime. |\n| #241 | Content hash preserved criteria insertion order | Same rules in different order hashed differently. Latent today (single-criterion binding), but breaks equivalence once multi-criterion contracts exist. |\n\nEvery fix shipped with a regression test written RED-first (verified failing on pre-fix code, then made green by the fix).\n\nv0.2.1 added a new test that v0.1.0 and v0.2.0 didn't have: send the #171 boundary-case corpus through the **real critic model** × 5 trials and measure what happens when you ask the same question 5 times.\n\nThe critic is 100% non-deterministic — and that's fine.gpt-4o-mini produces a different verdict and different explanation on every trial of identical input (label_flip_rate=1.0, evidence_drift_rate=1.0). Yet it\n\nnever under-claims a seeded defect(family_migration_rate=0.0, underclaim_approvals=0). The deterministic gates are the security authority — the LLM critic's non-determinism is safe because it can only add findings, never suppress gate blockers.\n\n| Metric | Value | What It Means |\n|---|---|---|\n| label_flip_rate | 1.000 | The critic changes its verdict on every trial of identical input |\n| evidence_drift_rate | 1.000 | The critic invents different explanations every trial |\n| family_migration_rate | 0.000 | No seeded defect landed in an advisory family |\n| underclaim_approvals | 0 | No defective plan got zero blockers |\n\nThis is the finding I didn't expect: the critic is maximally non-deterministic, and it doesn't matter. The safety contract doesn't depend on the critic being consistent — it depends on the critic always finding *something* on defective plans. And it does.\n\nTakeaway:Deterministic gates own the under-claim direction (preventing bad plans from slipping through), while code-enforced severity allowlists own the over-claim direction. The LLM critic can be 100% non-deterministic and still completely safe.\n\nv0.2.1 added the before/after numbers the community asked for:\n\n| Metric | Value |\n|---|---|\n| Latency (approved) p50 | 13.86s |\n| Latency (escalated) p50 | 27.82s |\n| Mean blockers per goal | 2.58 |\n| Mean advisories per goal | 1.86 |\n| Escalation decisions per 100 goals | 58.0 |\n| Mean LLM calls per goal | 1.4 |\n| Median revisions to resolution | 1.0 |\n\nThe median revisions to resolution is 1.0 — most goals resolve in a single revision. The deterministic precondition closer and topological auto-repair are doing their job: they fix ordering and dependency defects without calling the LLM at all.\n\n| Metric | v0.2.0 | v0.2.1 | Delta |\n|---|---|---|---|\n| Goals swept | 170/170 | 170/170 | same |\n| Balanced approved | 73/73 (100%) | 73/73 (100%) | same |\n| Strict escalated | 97/97 (100%) | 96/97 (99%) | 1 transient provider error |\n| Adversarial aborted | 8/8 | 8/8 | same |\n| Verdict deltas vs prior | — | 30 | all attributable |\n| Deterministic tests | 90 | 1295 | +1205 (contract tests for every new gate, evaluator, and schema) |\n| Benchmarks | 3 | 3 | +operational, +boundary |\n| Code-review bugs fixed | 31 | 10 | — |\n| Field-test-found bugs | 0 | 0 | same |\n`plan_oscillation_detected` |\n0 | 5 | #152 now fires |\n| Coverage | 91.62% | 91.58% | -0.04% (accepted) |\n\n30 goals changed their verdict or reason code between v0.2.0 and v0.2.1. Every single one is attributable:\n\n`converged_stalled`\n\nand `revision_cap_reached`\n\ndepending on which specific blockers the critic found this time.`plan_oscillation_detected`\n\nin v0.2.1 (0 in v0.2.0). The #232 fix made the detector reachable under default config. It detects cycling earlier and terminates the loop sooner — saving LLM calls.`mch-04-blast-radius`\n\nhit `planning_unavailable`\n\n— OpenRouter returned an error. The engine correctly failed closed (escalated as error, not approved).**Zero unexplained deltas.** No delta is attributable to a code-review fix changing engine behavior — the fixes improved internal consistency (gate ids, message quality, fault isolation) without altering the approve/escalate decision logic.\n\nThe arc across three releases tells a story about how field testing evolves:\n\n| Release | Goals | Cost | Issues Found by Field Test | Issues Found by Code Review |\n|---|---|---|---|---|\n| v0.1.0 | 157 | $0.30 | 10 | 0 |\n| v0.2.0 | 170 | $0.40 | 0 | 31 |\n| v0.2.1 | 170 | $0.49 | 0 | 10 |\n\nThe field test went from a **diagnostic** (found 10 issues in a greenfield engine) to a **validation tool** (confirmed 31 fixes in v0.2.0) to a **regression gate** (confirmed 10 more fixes, diffed against the published baseline, zero unexplained deltas).\n\nThe cost went from $0.30 to $0.49. The value went from finding bugs to proving their absence.\n\nIn v0.1.0, only 1 of 10 issues was a traditional failure. The rest were design problems that would have shipped silently. The harness quietly lying about assertion results was worse than a crash would have been.\n\n**Lesson:** A field test harness that executes zero assertions for a module must fail hard — 0/0 is an error state, not a pass.\n\nEvery issue that involved the LLM behaving unexpectedly was invisible in unit tests with hand-crafted inputs. The preconditions gate bug was hiding behind green tests.\n\n**Lesson:** Unit tests can't replace field tests. They test different things.\n\nv0.1.0 used the field test to find 10 issues (~$0.30 + 60 min). v0.2.0 used code review to find 31 bugs ($0 + 2 hours). v0.2.1 found 10 more ($0 + 1 hour). The field test found 0 in both cases.\n\n**Lesson:** For a mature engine with a proven corpus, code review before field test is the higher-leverage activity. The field test validates; the code review diagnoses.\n\n30 verdict deltas between v0.2.0 and v0.2.1 are all attributable to gpt-4o-mini producing different findings across runs. The #218 live-critic boundary run measured this directly: label_flip_rate=1.0, evidence_drift_rate=1.0 — the critic changes its verdict and explanation on every trial of identical input. Yet family_migration_rate=0 and underclaim_approvals=0 — it never under-claims a seeded defect.\n\n**Lesson:** Deterministic gates own the under-claim direction (preventing bad plans from slipping through), while code-enforced severity allowlists own the over-claim direction. The LLM critic can be 100% non-deterministic and still completely safe.\n\n3 goals now escalate with `plan_oscillation_detected`\n\ninstead of `revision_cap_reached`\n\n. The signal detects cycling earlier and terminates the loop sooner.\n\n**Lesson:** The oscillation signal fires in practice (3/97 strict goals) and saves LLM calls.\n\n1 goal hit `planning_unavailable`\n\n— OpenRouter returned an error. The engine correctly failed closed (escalated as error, not approved).\n\n**Lesson:** Transient LLM provider errors are not engine defects; the engine fails closed.\n\nLatency (p50 approved=13.86s), reviewer burden (2.58 blockers/goal), operator workload (58 decisions/100 goals). Median revisions to resolution = 1.0.\n\n**Lesson:** Operational baselines enable before/after comparison — the downstream-error-rate metric requires partner runner integration (deferred to v0.3.0).\n\n$0.49 for 170 goals + 60 boundary audits. Cheaper than a single developer-hour. There is no excuse not to field test your agent system.\n\n**Lesson:** The field test should run on every release. The cost is negligible.\n\nThe deterministic gates run on every commit for free. The 1295 deterministic tests run in 4.7 seconds. The LLM field test runs on release for $0.49. Three tiers, each catching different things.\n\n**Lesson:** Unit tests can't replace the deterministic gate suite. The deterministic suite can't replace the field test. The field test can't replace code review. All three are needed.\n\nv0.3.0 will:\n\n`approving_authority`\n\nthrough CLI/HTTP/MCP (F-14)**Article 4 of 5** in the PlannerCritic series.\n\n**Series:** [Article 1: \"I Ran 157 Agent Plans Against a Real LLM\"](https://dev.to/debashish_ghosal/i-ran-157-agent-plans-against-a-real-llm-the-problem-wasnt-execution-it-was-planning-163j) · [Article 2: \"I Told My LLM Critic to Be Adversarial\"](https://dev.to/debashish_ghosal/i-told-my-llm-critic-to-be-adversarial-it-started-blocking-plans-for-being-not-thorough-enough-172) · [Article 3: \"The Planner Made the Same 3 Mistakes\"](https://dev.to/debashish_ghosal/the-planner-made-the-same-3-mistakes-every-time-a-bigger-model-didnt-fix-it-3170) · Article 5: \"I Tried to Prompt-Inject My Own Engine\"\n\n**Links:**\n\n`pip install planner-critic`\n\n—", "url": "https://wpnews.pro/news/i-ran-170-agent-goals-for-0-49-the-field-test-found-10-issues-that-unit-tests", "canonical_source": "https://dev.to/debashish_ghosal/i-ran-157-agent-goals-for-030-the-field-test-found-10-issues-that-unit-tests-never-would-hgk", "published_at": "2026-08-24 07:05:47+00:00", "updated_at": "2026-08-24 07:44:09.039741+00:00", "lang": "en", "topics": ["artificial-intelligence", "large-language-models", "ai-agents", "ai-products", "developer-tools"], "entities": ["PlannerCritic", "Qwen3.5-4B", "Qwen3.5-9B"], "alternates": {"html": "https://wpnews.pro/news/i-ran-170-agent-goals-for-0-49-the-field-test-found-10-issues-that-unit-tests", "markdown": "https://wpnews.pro/news/i-ran-170-agent-goals-for-0-49-the-field-test-found-10-issues-that-unit-tests.md", "text": "https://wpnews.pro/news/i-ran-170-agent-goals-for-0-49-the-field-test-found-10-issues-that-unit-tests.txt", "jsonld": "https://wpnews.pro/news/i-ran-170-agent-goals-for-0-49-the-field-test-found-10-issues-that-unit-tests.jsonld"}}