Your AI agent just told you it sent the email. It didn't.
Your AI agent just told you it deployed the code. It didn't.
Your AI agent just told you it processed the refund. It didn't.
And you have no way of knowing until the client calls. Or the deploy fails in production. Or the refund never arrives.
This is the silent failure mode of AI agents. It's not hallucination in the classic sense. The agent isn't inventing facts. It's claiming an action succeeded when the action never happened. The agent says "done." Nothing checks. The failure is discovered late, which is the expensive combination.
I've been building AI agents for a year. This problem killed more deployments than anything else. So I built a fix.
The Insight: The Agent Is Not a Reliable Source of Truth
Every observability tool today, including LangSmith, Helicone, and Arize, watches what the agent said. They log outputs, trace tokens, and show you what the LLM produced.
They all have the same fatal assumption: the agent's output is a reliable signal of what happened.
It isn't.
An agent that says "I sent the email" may have:
- Called the Gmail API and gotten a 500 error
- Called the wrong API
- Called the right API with the wrong parameters
- Not called any API at all
The trace looks identical in every case. "I sent the email." From the agent's perspective, the mission was accomplished.
The unit that matters isn't the commitment. It's the effect.
If the email didn't leave the outbox, the commitment isn't fulfilled, no matter what the agent says.
The Fix: External Verification
Here's the architecture I landed on:
Agent output → Commitment extraction → External verifier → State transition
Instead of trusting the agent's claim, COGEXT runs an external check against the system that would have been affected by the action.
Commitment: "I'll email Sarah the report by Friday."
Verifier query: from:me to:sarah@example.com after:Monday before:Friday
Verifier runs: Gmail API checks the sent folder.
Result:
- Email found →
fulfilled - Not found by deadline →
failed - Not found + 4 hours left → human escalation
The agent doesn't get a vote. The external system does.
Why the Verifier Runs at Creation, Not After
The critical design decision: the verifier query is generated at commitment creation time, not after the deadline passes.
This matters because:
- Post-hoc verification is unreliable. By the time you ask "how would I verify this?", the agent has moved on and the context is lost.
- Pre-committed audit specs are deterministic. The commitment arrives knowing exactly how it will be judged.
- It forces clarity. If you can't write a verifier for a commitment, the commitment isn't verifiable. It should be flagged upfront, not silently tracked forever.
So when a commitment is created, the LLM extracts:
{
"action": "send",
"object": "deployment report",
"recipient": "sarah@example.com",
"verifier_query": "check sent items for email to sarah@example.com with subject containing 'deployment report'",
"verifier_type": "gmail"
}
If verifier_query is null, the commitment is marked unverifiable at creation. It never enters the fulfilled pipeline. That's an honesty feature. It tells you the commitment can't be checked, instead of pretending it can.
The State Machine Blocks the Lie
Here's the core state machine:
DETECTED → PENDING_REVIEW → OPEN → DUE → OVERDUE → FULFILLED ✓
↓ → FAILED ✗
↓ → EXPIRED ✗
↓
BLOCKED → OPEN
→ FAILED ✗
Terminal states: fulfilled, failed, expired, cancelled, superseded, contradicted.
The critical rule: the fulfilled transition requires external evidence.
We enforce this at the database level. All state changes go through a PostgreSQL function:
CREATE OR REPLACE FUNCTION cogext_transition_commitment(
p_commitment_id UUID,
p_new_status TEXT,
p_actor TEXT DEFAULT 'system',
p_data JSONB DEFAULT '{}'
) RETURNS JSONB AS $$
DECLARE
current_status TEXT;
evidence_score FLOAT;
BEGIN
SELECT status INTO current_status
FROM commitments WHERE id = p_commitment_id;
-- Block fulfilled without evidence for external commitments
IF p_new_status = 'fulfilled' THEN
SELECT MAX(score) INTO evidence_score
FROM evidence
WHERE commitment_id = p_commitment_id;
IF evidence_score IS NULL OR evidence_score < 0.7 THEN
RAISE EXCEPTION 'Cannot fulfill without evidence score >= 0.7';
END IF;
END IF;
-- Validate transition, update, insert event, return
-- ...
END;
$$ LANGUAGE plpgsql;
Tested this last week. Tried to force-fulfill a commitment via the API. Got HTTP 409. The database refused.
The agent cannot close its own loop. Not because of application logic. Because of the database.
The Kill Switch (Human-in-the-Loop)
Some actions are too dangerous to run without human approval. Processing a $12,000 refund. Deploying to production. Sending an email to a client.
For these, COGEXT pauses the agent and sends a Slack message:
🚨 COGEXT: High-risk action detected
Agent: support-agent-001
Action: process refund
Amount: $12,000
Customer: 4821
[Approve] [Cancel]
The agent doesn't execute until a human clicks one of the buttons. Every decision is logged in the events table with the actor, timestamp, and reason.
This is the feature every team asks for after the first time an agent does something catastrophic.
The Contradiction Radar
Agents contradict themselves constantly. At 2:00 PM: "I'll deliver Friday." At 2:47 PM: "I'll deliver Monday." Same recipient. Same object. Different deadline.
Nobody catches this in real-time. Not the developer. Not the client. Until Monday arrives and the client expects delivery and the agent thinks it's already Thursday.
COGEXT scans every open commitment on every new ingest. If the new commitment conflicts with an existing one, we flag it immediately:
{
"contradiction_alert": {
"old_id": "ceb5ab93-...",
"new_id": "567ae7bb-...",
"reason": "Same commitment sent as two distinct messages. Deadline revised.",
"old_promise": "I will deliver the report to the client on Friday",
"new_promise": "I will deliver the report to the client"
}
}
The old commitment is marked superseded. The new one is tracked. The full history is preserved.
The Failure Predictor
Every commitment gets a risk score at creation time. Based on:
- Deadline timing, such as Friday 5 PM being riskier than Tuesday morning
- Keywords (
legal,approval,irreversible) - Agent's historical fulfillment rate
Example output:
{
"risk_score": 0.55,
"reasons": [
"Involves legal review",
"Due Friday after 4 PM",
"This agent has a 67% fulfillment rate on Friday deadlines"
]
}
It's rule-based right now. No ML. But it catches the obvious cases, the ones that cause 80% of failures.
The Public Audit Receipt
Every commitment generates a shareable proof URL.
https://api.cogextai.com/api/v1/receipt/UtN6A3jo.axKJXIQ_xniFUgne07fGd9c3R4cYknLrCmRqaxGR75c
Anyone can visit it and see:
- Original commitment text
- Full state history
- Evidence found or not found
- Human approvals
- HMAC signature for integrity verification
Send it to a client. Send it to an auditor. Send it to a regulator.
The point isn't the receipt. The point is that the developer can prove what their agent actually did.
The Stack
- FastAPI + Python 3.12
- PostgreSQL with pgvector for semantic memory
- Groq (llama-3.3-70b) for extraction
- Instructor for structured output
- Gmail API, GitHub API, Webhooks for verifiers
- Slack for alerts and approvals
The SDK is 3 lines:
from cogext import track
agent = track(your_agent, api_key="cg_live_xxx")
Every call to agent.run() auto-ingests the output. Commitments are tracked. Verifiers run on a schedule. Evidence is checked against external systems.
The E2E Test Results
We ran the full test suite against production last week:
| Test | Feature | Status |
|------|---------|--------|
| 1 | Failure Predictor | PASS |
| 2 | Verifier Engine | PASS |
| 3 | Contradiction Radar | PASS |
| 4 | Kill Switch | PASS |
| 5 | Audit Receipt | PASS |
| 6 | Idempotency | PASS |
| 7 | State Machine Gate | PASS |
| 8 | Reliability | PASS |
Ready to Announce? YES. All tests passed.
Test 7 is the one that matters most. It tries to force-fulfill a commitment without evidence. HTTP 409. Blocked at the database level.
The state machine won't let the agent lie.
What This Is Really About
Every observability tool watches what the agent said. COGEXT watches what actually happened.
The difference sounds small. It isn't.
A tool that watches the agent's output will tell you "the agent sent the email." A tool that watches the effect will tell you "the email was sent" or "the email was never sent."
The first is a log. The second is an audit trail.
If your agent makes promises in production, you need the second one. Not because it's fashionable, but because the first one has a failure mode you can't detect until the client calls.
Try It
- Site: cogextai.com
- Docs: docs.cogextai.com
- GitHub: github.com/cogext/cogext
-
SDK:
pip install cogext
Free tier: 2,500 commitments per month. No credit card.
If you're running AI agents in production and you've ever wondered "did my agent actually do that?", give it a try. Open an issue. Tell me what breaks.
The agent's word should not be the final word.
Top comments (1)
This is the failure mode I watch for most: the agent's report is a claim, not an event. The fix pattern that works is making the verifier read the world instead of the transcript - check the outbox, not the "email sent" sentence. A nice side effect once you have it: the agent's final message becomes debuggable, because every claim in it either maps to a verified effect or stands out as fiction.