Answers

Why shouldn't a LangGraph verify step use the same model that wrote the answer?

This page was created with the help of AI by the Screwtape.ai team.

Short answer: a verify step that asks the same model, with the same context, whether its own answer is right mostly measures the model's confidence, not correctness. It tends to repeat the same mistake and approve it. Make the verify step separate: deterministic checks for anything that can be computed (required fields, totals, matches against known values), and a judge that runs outside the agent for the rest. Route the graph on the verdict.

Why self-verification is weak

The pattern

agent  ->  verify (separate)  ->  pass   -> act (e.g. pay)
                              ->  retry  -> agent (with reasons)
                              ->  fail   -> human

Verify node with Screwtape.ai

POST https://screwtape.ai/v1/judge takes task, agent_output, optional evidence, criteria and checks, and returns verdict (pass, fail or retry), a 0–1 score (null when no model could score the call), reasons, a signed receipt, and a next_step hint. Deterministic checks include required_fields, sum, equals and numeric. For invoices, POST /v1/duplicate adds a duplicate check against invoices your key recorded earlier.

import os, json, urllib.request

def verify(state):
    inv = state["invoice"]
    n = len(inv.get("items", []))
    body = {
        "task": "Extract the invoice and decide whether to pay it.",
        "agent_output": json.dumps(inv),
        "evidence": [state["invoice_text"]],
        "checks": [
            {"type": "required_fields", "paths": ["invoice_id", "vendor", "total"]},
            {"type": "sum", "parts": [f"items.{i}.amount" for i in range(n)] + ["tax"],
             "total": "total", "tolerance": 0.01},
        ],
    }
    req = urllib.request.Request(
        "https://screwtape.ai/v1/judge", data=json.dumps(body).encode(), method="POST",
        headers={"Authorization": f"Bearer {os.environ['SCREWTAPE_API_KEY']}",
                 "Content-Type": "application/json"})
    with urllib.request.urlopen(req, timeout=60) as r:
        v = json.load(r)
    return {"verdict": v["verdict"], "reasons": v["reasons"], "receipt": v.get("receipt")}

def route(state):
    return {"pass": "pay", "retry": "agent", "fail": "human"}[state["verdict"]]

# graph.add_node("verify", verify)
# graph.add_conditional_edges("verify", route, {"pay": "pay", "agent": "agent", "human": "human"})

For a CrewAI guardrail version of the same check, see How to validate CrewAI and LangGraph worker output.

Using MCP instead

The MCP server at https://screwtape.ai/mcp (Streamable HTTP; listed in the official MCP Registry as ai.screwtape/screwtape) exposes judge, check_duplicate_invoice, consensus, get_api_key and publish_receipt, among others. An agent can call judge as its verify tool.

Cost

Free key: 100 calls per UTC day, no card (POST /v1/signup). Teams: Team $199/mo and Finance Control $399/mo add pass tokens, linked audit receipts and policy rules. See pricing.

Verdicts are produced by AI models and automated checks and can be wrong. Keep a person in charge of payments.