cd /news/ai-agents/a-json-receipt-for-ai-handoffs-and-a… · home › topics › ai-agents › article
[ARTICLE · art-160392] src=dev.to ↗ pub= topic=ai-agents verified=true sentiment=↑ positive

A JSON receipt for AI handoffs, and a small check for "done"

A developer running a personal website in nine languages with ChatGPT/Codex and Claude built an open-source "Multi-AI Handoff & Approval Card," an eight-field JSON receipt that travels with work between AI agents, plus a Python checker that returns DONE only when every field is filled, all evidence passed with a named non-same-run checker, rollback specifies both how and who, and no open items remain. The card assigns each task a risk tier — auto, auto-then-verify, or human-approval — so routine steps like drafting translations run automatically while publishing, deleting, spending, or touching credentials stops for a human.

by read3 min views1 publishedOct 29, 2026

An AI saying "done" is a claim. Work is complete only when something outside that AI has checked it.

I run my personal website, akiohirayama.com, in nine languages with ChatGPT/Codex and Claude. Several AIs touch the same work: one drafts, another reviews, a third checks how pages actually render. The weak point is the handoff. The next AI usually receives a summary it has to believe.

This post shows the fix I use: a small, structured receipt that travels with the work, and a check that reads it.

The Multi-AI Handoff & Approval Card has eight fields. If any of them is empty, the work has only been reported. It is not done yet.

{
  "task": "what was asked, in one sentence",
  "claimed_result": "what the AI says it did",
  "risk_tier": "auto | auto-then-verify | human-approval",
  "risk_reason": "why this tier",
  "evidence": [
    { "check": "what was checked", "checked_by": "script / fresh-context run / human (not the same AI run)", "result": "pass | fail | unclear" }
  ],
  "changed": ["pages, files or systems touched"],
  "rollback": { "how": "how to undo it", "who": "who can undo it" },
  "open_items": ["anything unverified, ambiguous or skipped"],
  "approved_by": "a human, or the rule that allowed auto-approval"
}

Three fields do most of the work:

Not every step needs a human. Approving everything trains people to click "yes" without reading.

Tier Examples Default
auto Draft a translation, generate a design option Run automatically, log it
auto-then-verify Change a live page, update shared text Run, then verify before it counts as done
human-approval Publish, delete, spend, touch credentials, message anyone Stop and ask a human, with a one-screen summary

A receipt is only useful if something reads it. This script returns DONE only when every field is filled, every piece of evidence passed and has a named checker, the rollback says both how and who, and no open items remain.

import json
import sys

REQUIRED = ["task", "claimed_result", "risk_tier", "risk_reason", "evidence",
            "changed", "rollback", "open_items", "approved_by"]
TIERS = {"auto", "auto-then-verify", "human-approval"}

def check(card):
    problems = []
    for key in REQUIRED:
        if key not in card:
            problems.append(f"missing field: {key}")
    for key in ["task", "claimed_result", "risk_reason", "approved_by"]:
        if key in card and not str(card[key]).strip():
            problems.append(f"empty field: {key}")
    if card.get("risk_tier") not in TIERS:
        problems.append("risk_tier must be auto, auto-then-verify or human-approval")
    evidence = card.get("evidence") or []
    if not evidence:
        problems.append("no evidence: the work is reported, not done")
    for item in evidence:
        if item.get("result") != "pass":
            problems.append(f"evidence not passing: {item.get('check', '?')}")
        if not str(item.get("checked_by", "")).strip():
            problems.append(f"evidence without a checker: {item.get('check', '?')}")
    rollback = card.get("rollback") or {}
    if not rollback.get("how") or not rollback.get("who"):
        problems.append("rollback needs both how and who")
    if card.get("open_items"):
        problems.append(f"{len(card['open_items'])} open item(s) still listed")
    return problems

if __name__ == "__main__":
    with open(sys.argv[1], encoding="utf-8") as f:
        card = json.load(f)
    found = check(card)
    for p in found:
        print("-", p)
    print("DONE" if not found else "NOT DONE")
    sys.exit(0 if not found else 1)

Here is a card that fails:

{"task":"Publish the article","claimed_result":"Published","risk_tier":"human-approval","risk_reason":"public","evidence":[{"check":"links work","checked_by":"","result":"unclear"}],"changed":["note.com"],"rollback":{"how":"unpublish"},"open_items":["image credits"],"approved_by":""}
- empty field: approved_by
- evidence not passing: links work
- evidence without a checker: links work
- rollback needs both how and who
- 1 open item(s) still listed
NOT DONE

The exit code is non-zero, so the same check can gate a pipeline step or a handoff to the next agent.

The card, card.json and a longer field guide are free under CC BY 4.0:

I am also hiring an AI Engineer, at a monthly salary of USD 30,000, to build this kind of control layer across multiple AIs: role details.

— AKIO HIRAYAMA (akiohirayama.com)

── more in #ai-agents 4 stories · sorted by recency
── more on @chatgpt 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/a-json-receipt-for-a…] indexed:0 read:3min 2026-10-29 · —