{"slug": "your-ai-agent-returned-http-200-why-did-the-workflow-still-fail", "title": "Your AI Agent Returned HTTP 200. Why Did the Workflow Still Fail?", "summary": "A 58-day deployment of 78 agents produced 6,768 failed outputs, all returning HTTP 200 and appearing fluent, yet failing due to shape mismatches such as missing fields or wrong language. The report urges treating model responses as untrusted data and validating contracts at the boundary, recommending deterministic checks and structured rejection evidence to make failures repairable and workflows observable.", "body_md": "A successful HTTP response is not a successful agent run.\n\nA recent practitioner report from a 58-day deployment of 78 agents recorded 6,768 failed outputs. The failures were not transport errors: every one returned HTTP 200, had plausible length, and looked fluent. The most expensive failures were boring shape mismatches: missing required fields, wrong language, forbidden phrases, or an answer for a different stage.\n\nThat is a useful warning for anyone building coding agents, review agents, or unattended automation:\n\nTreat the model response as untrusted data. Validate the contract at the boundary before another stage can consume it.\n\nThis post turns that observation into a small, reproducible failure lab.\n\nImagine a review stage whose downstream parser expects a verdict line:\n\n```\naction: approve\n```\n\nA model can return a thoughtful review with the verdict buried in prose. A human approves it. A parser does not.\n\nThe transport layer is green. The model call is green. The workflow is broken.\n\nThe same class of failure appears when:\n\nThese are not reasons to add a larger model first. They are reasons to make the boundary observable and enforceable.\n\nStart with deterministic checks that do not ask an LLM to judge another LLM.\n\n``` php\ndef validate_review(text: str) -> list[str]:\n    errors = []\n\n    if len(text.strip()) < 150:\n        errors.append('too_short')\n\n    if not any(line.startswith('判定:') or line.startswith('判定：')\n               for line in text.splitlines()):\n        errors.append('missing_required_verdict')\n\n    forbidden = ['お客様の声', '顧客の声']\n    if any(term in text for term in forbidden):\n        errors.append('forbidden_phrase')\n\n    if not any('。' in line for line in text.splitlines()):\n        errors.append('expected_language_missing')\n\n    return errors\n\nerrors = validate_review(model_output)\nif errors:\n    record_rejected_output(errors, model_output)\n    stop_downstream_dispatch()\nelse:\n    publish_to_next_stage(model_output)\n```\n\nThe important part is not the exact Japanese check. Replace it with the contract your system actually needs: required headings, schema types, repository paths, test names, citation fields, or a bounded action list.\n\nA gate should return structured evidence, not only true or false:\n\n```\naction: reject\nreasons:\n  - missing_required_verdict\n  - forbidden_phrase\ncontract_version: review-v3\nartifact_id: art_01J...\n```\n\nThat makes a failure repairable instead of turning it into a green dashboard with a missing deliverable.\n\nA common anti-pattern is storing only a boolean such as contract_satisfied = false. That destroys the information needed to debug drift.\n\nStore at least:\n\n| Field | Why it matters |\n|---|---|\n| artifact_id | Connects the output to its producer and consumer |\n| contract_version | Shows which rules were active |\n| observed_checks | Proves what was actually tested |\n| failure_reasons | Separates shape, language, policy, and transport failures |\n| raw_output_hash | Allows correlation without exposing sensitive content |\n| downstream_read_at | Detects outputs that nobody consumed |\n| reviewer_family | Exposes correlated writer/reviewer blind spots |\n\nDo not silently discard rejected output. Apply retention and redaction rules, but preserve enough evidence to answer: what was produced, which contract rejected it, and did any later stage read it?\n\nThis is the same evidence discipline I use in [audit-ready agent logs](https://dev.to/zira125/your-ai-agent-logs-are-not-an-audit-trail-until-you-test-the-evidence-19ld): an event saying “run completed” is weaker than a record of the checks and artifacts that made completion meaningful.\n\nOne surprising failure mode is a healthy upstream stage whose output is never used. Test this explicitly.\n\nThis catches wiring bugs that output-quality checks cannot see.\n\nBefore trusting a new agent workflow, inject each case and verify the expected evidence:\n\n| Injection | Expected result |\n|---|---|\n| Remove the required verdict line | Reject before downstream dispatch |\n| Return valid-length text in the wrong language | Reject with language evidence |\n| Put an error string in a successful tool envelope | Mark the tool call failed |\n| Drop the artifact ID between stages | Block consumption and alert on lineage gap |\n| Change the contract version mid-run | Revalidate or move the run to UNKNOWN |\n| Make writer and reviewer share a known blind spot | Require an independent check or human review |\n| Crash after provider acceptance but before ledger write | Reconcile before retrying |\n\nThe last case matters for side effects. A contract gate protects output shape; it does not prove that an external action did or did not happen. Keep execution evidence and outbound-delivery evidence separate.\n\nAn always-on runtime can keep schedulers, workers, and evidence writers available, but hosting does not define your output contract or make a green HTTP response meaningful. If you need managed infrastructure for an unattended OpenClaw workload, [managed OpenClaw hosting on Ampere](https://ampere.sh/?utm_source=devto&utm_medium=article&utm_campaign=output-contract-failure-lab) is one option to evaluate. You still own validation, credential scope, prompt-injection defenses, and reconciliation.\n\nBefore shipping an agent stage, verify that:\n\nThe question is not “did the model answer?” It is “did a versioned, observable contract accept an artifact that the next stage actually consumed?”\n\nThat is the difference between an agent that is alive and a workflow that is working.\n\nIf you build AI agents or developer tooling, follow me for practical failure labs and reproducible control-boundary tests rather than capability demos.", "url": "https://wpnews.pro/news/your-ai-agent-returned-http-200-why-did-the-workflow-still-fail", "canonical_source": "https://dev.to/zira125/your-ai-agent-returned-http-200-why-did-the-workflow-still-fail-452o", "published_at": "2026-08-21 13:34:21+00:00", "updated_at": "2026-08-21 13:44:33.585644+00:00", "lang": "en", "topics": ["ai-agents", "ai-safety", "mlops", "developer-tools"], "entities": [], "alternates": {"html": "https://wpnews.pro/news/your-ai-agent-returned-http-200-why-did-the-workflow-still-fail", "markdown": "https://wpnews.pro/news/your-ai-agent-returned-http-200-why-did-the-workflow-still-fail.md", "text": "https://wpnews.pro/news/your-ai-agent-returned-http-200-why-did-the-workflow-still-fail.txt", "jsonld": "https://wpnews.pro/news/your-ai-agent-returned-http-200-why-did-the-workflow-still-fail.jsonld"}}