[{"data":1,"prerenderedAt":6205},["ShallowReactive",2],{"navigation":3,"page-\u002Fchapters\u002Fresumability":102,"surround-\u002Fchapters\u002Fresumability":6200},[4,8],{"title":5,"path":6,"stem":7},"Home","\u002F","index",{"title":9,"path":10,"stem":11,"children":12,"page":101},"Chapters","\u002Fchapters","2.chapters",[13,17,21,25,29,33,37,41,45,49,53,57,61,65,69,73,77,81,85,89,93,97],{"title":14,"path":15,"stem":16},"Chapter 1. What an Agent Actually Is","\u002Fchapters\u002Fwhat-an-agent-actually-is","2.chapters\u002F01.what-an-agent-actually-is",{"title":18,"path":19,"stem":20},"Chapter 2. The Minimum Viable Loop","\u002Fchapters\u002Fminimum-viable-loop","2.chapters\u002F02.minimum-viable-loop",{"title":22,"path":23,"stem":24},"Chapter 3. Messages, Turns, and the Transcript","\u002Fchapters\u002Fmessages-turns-transcript","2.chapters\u002F03.messages-turns-transcript",{"title":26,"path":27,"stem":28},"Chapter 4. The Tool Protocol","\u002Fchapters\u002Ftool-protocol","2.chapters\u002F04.tool-protocol",{"title":30,"path":31,"stem":32},"Chapter 5. Streaming, Interruption, and Error Handling","\u002Fchapters\u002Fstreaming-interruption-errors","2.chapters\u002F05.streaming-interruption-errors",{"title":34,"path":35,"stem":36},"Chapter 6. Safe Tool Execution","\u002Fchapters\u002Fsafe-tool-execution","2.chapters\u002F06.safe-tool-execution",{"title":38,"path":39,"stem":40},"Chapter 7. The Context Window Is a Resource","\u002Fchapters\u002Fcontext-window-as-resource","2.chapters\u002F07.context-window-as-resource",{"title":42,"path":43,"stem":44},"Chapter 8. Compaction","\u002Fchapters\u002Fcompaction","2.chapters\u002F08.compaction",{"title":46,"path":47,"stem":48},"Chapter 9. External State: The Scratchpad","\u002Fchapters\u002Fscratchpad","2.chapters\u002F09.scratchpad",{"title":50,"path":51,"stem":52},"Chapter 10. Retrieval","\u002Fchapters\u002Fretrieval","2.chapters\u002F10.retrieval",{"title":54,"path":55,"stem":56},"Chapter 11. Designing Tools Models Can Actually Use","\u002Fchapters\u002Fdesigning-tools","2.chapters\u002F11.designing-tools",{"title":58,"path":59,"stem":60},"Chapter 12. The Tool Cliff and Dynamic Loading","\u002Fchapters\u002Ftool-cliff","2.chapters\u002F12.tool-cliff",{"title":62,"path":63,"stem":64},"Chapter 13. MCP: Tools From the Outside World","\u002Fchapters\u002Fmcp","2.chapters\u002F13.mcp",{"title":66,"path":67,"stem":68},"Chapter 14. Sandboxing and Permissions","\u002Fchapters\u002Fsandboxing-permissions","2.chapters\u002F14.sandboxing-permissions",{"title":70,"path":71,"stem":72},"Chapter 15. Sub-agents","\u002Fchapters\u002Fsub-agents","2.chapters\u002F15.sub-agents",{"title":74,"path":75,"stem":76},"Chapter 16. Structured Plans and Verified Completion","\u002Fchapters\u002Fplans-verified-completion","2.chapters\u002F16.plans-verified-completion",{"title":78,"path":79,"stem":80},"Chapter 17. Parallelism and Shared State","\u002Fchapters\u002Fparallelism-shared-state","2.chapters\u002F17.parallelism-shared-state",{"title":82,"path":83,"stem":84},"Chapter 18. Observability","\u002Fchapters\u002Fobservability","2.chapters\u002F18.observability",{"title":86,"path":87,"stem":88},"Chapter 19. Evals","\u002Fchapters\u002Fevals","2.chapters\u002F19.evals",{"title":90,"path":91,"stem":92},"Chapter 20. Cost Control","\u002Fchapters\u002Fcost-control","2.chapters\u002F20.cost-control",{"title":94,"path":95,"stem":96},"Chapter 21. Resumability and Durable State","\u002Fchapters\u002Fresumability","2.chapters\u002F21.resumability",{"title":98,"path":99,"stem":100},"Chapter 22. What Transfers, Where to Go","\u002Fchapters\u002Fwhat-transfers","2.chapters\u002F22.what-transfers",false,{"id":103,"title":94,"body":104,"description":117,"extension":6195,"meta":6196,"navigation":6197,"path":95,"seo":6198,"stem":96,"__hash__":6199},"content\u002F2.chapters\u002F21.resumability.md",{"type":105,"value":106,"toc":6185},"minimark",[107,111,118,121,128,134,144,147,159,279,282,287,290,328,335,337,341,2635,2638,2648,2654,2686,2688,2692,2705,3560,3574,3577,3583,3750,3757,3776,3786,3788,3792,3795,4069,4080,5144,5170,5179,6009,6026,6029,6031,6035,6038,6041,6061,6067,6069,6073,6082,6085,6087,6091,6139,6143,6166,6168,6181],[108,109,94],"h1",{"id":110},"chapter-21-resumability-and-durable-state",[112,113,114],"p",{},[115,116,117],"em",{},"Previously: cost control. A budget-enforced harness can't run away. What it still can't do is survive a crash. The machine reboots, the process is killed, the laptop lid closes — and the session is gone.",[112,119,120],{},"Durability for agent harnesses has a specific shape, different from databases or web services. Three problems.",[112,122,123,127],{},[124,125,126],"strong",{},"Crash during a turn."," The harness dies mid-LLM-call or mid-tool-execution. On restart, we need to know where we were without duplicating work — especially side-effecting work.",[112,129,130,133],{},[124,131,132],{},"Restart across processes."," The user comes back tomorrow. The harness starts a new process, reads a checkpoint, and picks up. The full transcript, the plan, the scratchpad, all restored.",[112,135,136,139,140,143],{},[124,137,138],{},"Idempotency for side effects."," Tool calls that ",[115,141,142],{},"did"," succeed before the crash must not re-execute on resume. A payment charged once must not be charged twice.",[112,145,146],{},"This chapter builds the checkpointer that addresses all three. It's a SQLite-backed system with pre-execution and post-result durability, idempotency keys for mutating tools, and a verify-before-retry protocol for irreversible operations.",[112,148,149,150,154,155,158],{},"The foundational reference here is Pat Helland's 2012 ACM Queue article \"Idempotence Is Not a Medical Condition,\" which remains the canonical treatment of idempotency in distributed systems. Helland's core argument — that any system built to survive restart must treat every side-effecting operation as potentially replayable and design its protocol so replays are safe — is the principle this chapter's ",[151,152,153],"code",{},"Checkpointer"," implements specifically for agent tool dispatch. LangGraph's ",[151,156,157],{},"AsyncPostgresSaver"," is the contemporary production reference for this pattern in LLM harness code; we build the SQLite version because it's simpler to read and the interface ports to Postgres without code changes when you need to scale across machines.",[160,161,165,272],"figure",{"className":162},[163,164],"not-prose","my-8",[166,167,172,193,249],"div",{"className":168},[169,170,171],"flex","flex-col","gap-2",[166,173,181,184,187,190],{"className":174},[175,176,171,177,178,179,180],"grid","grid-cols-4","text-xs","text-center","text-muted","font-mono",[166,182,183],{},"checkpoint: transcript",[166,185,186],{},"+ plan",[166,188,189],{},"+ budget",[166,191,192],{}," ",[166,194,196,216,226,238],{"className":195},[175,176,171],[166,197,206,212],{"className":198},[199,200,201,202,203,204,205,178],"rounded-lg","border","border-default","bg-elevated","px-3","py-2","text-sm",[166,207,211],{"className":208},[209,210],"text-default","font-semibold","Turn 1",[166,213,215],{"className":214},[177,179],"persisted",[166,217,219,223],{"className":218},[199,200,201,202,203,204,205,178],[166,220,222],{"className":221},[209,210],"Turn 2",[166,224,215],{"className":225},[177,179],[166,227,229,233],{"className":228},[199,200,201,202,203,204,205,178],[166,230,232],{"className":231},[209,210],"Turn 3",[166,234,237],{"className":235},[177,236],"text-primary","[CRASH]",[166,239,241,245],{"className":240},[199,200,201,202,203,204,205,178],[166,242,244],{"className":243},[209,210],"Turn 4",[166,246,248],{"className":247},[177,179],"resumed",[166,250,256,260,268],{"className":251},[169,252,253,254,171,177,255],"flex-row","items-center","justify-center","mt-1",[166,257,259],{"className":258},[179],"← load checkpoint",[166,261,267],{"className":262},[263,264,265,203,266,236,180],"rounded-md","border-2","border-primary","py-1.5","idempotency key check",[166,269,271],{"className":270},[179],"skip completed tool calls →",[273,274,278],"figcaption",{"className":275},[177,179,276,178,277],"mt-3","italic","Crash-resume timeline: each turn checkpoints transcript + plan + budget; after a crash, the idempotency check prevents re-executing side-effecting tools.",[280,281],"hr",{},[283,284,286],"h2",{"id":285},"_211-what-must-be-checkpointed","21.1 What Must Be Checkpointed",[112,288,289],{},"The durable state of a session:",[291,292,293,300,306,316,322],"ol",{},[294,295,296,299],"li",{},[124,297,298],{},"The transcript."," Every message, every block, with IDs and timestamps.",[294,301,302,305],{},[124,303,304],{},"The plan."," Current steps, postconditions, evidence strings.",[294,307,308,311,312,315],{},[124,309,310],{},"The scratchpad."," Already on disk from §9.2 — the checkpointer records ",[115,313,314],{},"nothing"," about scratchpads because there's nothing to save. The scratchpad directory carries across process deaths on its own. If you vary the scratchpad root per session (one directory per session_id), record that root in the session metadata below so the next run knows where to look; otherwise a single shared root is fine and the checkpointer can stay out of it entirely.",[294,317,318,321],{},[124,319,320],{},"The budget."," How much has been spent so far this session.",[294,323,324,327],{},[124,325,326],{},"The tool-call log."," Which tool calls have been issued, which completed, which have pending results.",[112,329,330,331,334],{},"The last one is the novel bit. Chapter 6's registry maintained an in-memory ",[151,332,333],{},"_call_history"," for loop detection. For resumability, we need a persistent record of every side-effecting tool call with its idempotency state.",[280,336],{},[283,338,340],{"id":339},"_212-the-checkpointer-schema","21.2 The Checkpointer Schema",[342,343,348],"pre",{"className":344,"code":345,"language":346,"meta":347,"style":347},"language-python shiki shiki-themes material-theme-lighter github-light github-dark","# src\u002Fharness\u002Fcheckpoint\u002Fstore.py\nfrom __future__ import annotations\n\nimport asyncio\nimport json\nimport sqlite3\nfrom contextlib import contextmanager\nfrom dataclasses import dataclass\nfrom datetime import datetime, timezone\nfrom pathlib import Path\n\n\nSCHEMA = \"\"\"\nCREATE TABLE IF NOT EXISTS sessions (\n    session_id TEXT PRIMARY KEY,\n    created_at TIMESTAMP NOT NULL,\n    updated_at TIMESTAMP NOT NULL,\n    status TEXT NOT NULL  -- 'active', 'completed', 'failed', 'cancelled'\n);\n\nCREATE TABLE IF NOT EXISTS checkpoints (\n    session_id TEXT NOT NULL,\n    version INTEGER NOT NULL,\n    created_at TIMESTAMP NOT NULL,\n    transcript_json TEXT NOT NULL,\n    plan_json TEXT,\n    budget_spent_usd REAL NOT NULL DEFAULT 0,\n    PRIMARY KEY (session_id, version)\n);\n\nCREATE TABLE IF NOT EXISTS tool_calls (\n    session_id TEXT NOT NULL,\n    call_id TEXT NOT NULL,\n    tool_name TEXT NOT NULL,\n    args_json TEXT NOT NULL,\n    idempotency_key TEXT NOT NULL,\n    status TEXT NOT NULL,  -- 'issued', 'completed', 'failed'\n    result_text TEXT,\n    started_at TIMESTAMP NOT NULL,\n    completed_at TIMESTAMP,\n    PRIMARY KEY (session_id, call_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_tool_calls_idempotency\n    ON tool_calls(idempotency_key);\n\"\"\"\n\n\nclass Checkpointer:\n    def __init__(self, db_path: str | Path) -> None:\n        self._path = Path(db_path)\n        self._path.parent.mkdir(parents=True, exist_ok=True)\n        self._conn = sqlite3.connect(str(self._path), check_same_thread=False)\n        self._conn.row_factory = sqlite3.Row\n        self._conn.executescript(SCHEMA)\n        self._lock = asyncio.Lock()\n\n    @contextmanager\n    def _transaction(self):\n        try:\n            yield self._conn\n            self._conn.commit()\n        except Exception:\n            self._conn.rollback()\n            raise\n\n    async def start_session(self, session_id: str,\n                             user_message: str = \"\", system_prompt: str | None = None) -> None:\n        \"\"\"Record a new session or touch its updated_at.\n\n        user_message and system_prompt are accepted for API back-compat but\n        no longer stored on `sessions` — they live in the first checkpoint's\n        transcript_json. A resume with a new user message does not overwrite\n        the original.\n        \"\"\"\n        async with self._lock:\n            now = datetime.now(timezone.utc).isoformat()\n            with self._transaction() as conn:\n                # INSERT OR IGNORE preserves the original created_at\n                conn.execute(\"\"\"\n                    INSERT OR IGNORE INTO sessions\n                    (session_id, created_at, updated_at, status)\n                    VALUES (?, ?, ?, 'active')\n                \"\"\", (session_id, now, now))\n                conn.execute(\"\"\"\n                    UPDATE sessions SET updated_at = ?, status = 'active'\n                    WHERE session_id = ?\n                \"\"\", (now, session_id))\n\n    async def save_checkpoint(\n        self, session_id: str, transcript: list[dict],\n        plan: dict | None, budget_spent_usd: float,\n    ) -> int:\n        async with self._lock:\n            now = datetime.now(timezone.utc).isoformat()\n            with self._transaction() as conn:\n                row = conn.execute(\n                    \"SELECT COALESCE(MAX(version), 0) + 1 FROM checkpoints \"\n                    \"WHERE session_id = ?\", (session_id,)\n                ).fetchone()\n                version = row[0]\n                conn.execute(\"\"\"\n                    INSERT INTO checkpoints\n                    (session_id, version, created_at,\n                     transcript_json, plan_json, budget_spent_usd)\n                    VALUES (?, ?, ?, ?, ?, ?)\n                \"\"\", (session_id, version, now,\n                      json.dumps(transcript),\n                      json.dumps(plan) if plan else None,\n                      budget_spent_usd))\n                conn.execute(\"\"\"\n                    UPDATE sessions SET updated_at = ? WHERE session_id = ?\n                \"\"\", (now, session_id))\n                return version\n\n    async def record_tool_call_issued(\n        self, session_id: str, call_id: str, tool_name: str,\n        args: dict, idempotency_key: str,\n    ) -> None:\n        async with self._lock:\n            now = datetime.now(timezone.utc).isoformat()\n            with self._transaction() as conn:\n                conn.execute(\"\"\"\n                    INSERT OR IGNORE INTO tool_calls\n                    (session_id, call_id, tool_name, args_json,\n                     idempotency_key, status, started_at)\n                    VALUES (?, ?, ?, ?, ?, 'issued', ?)\n                \"\"\", (session_id, call_id, tool_name, json.dumps(args),\n                      idempotency_key, now))\n\n    async def record_tool_call_result(\n        self, session_id: str, call_id: str, result_text: str,\n        success: bool,\n    ) -> None:\n        async with self._lock:\n            now = datetime.now(timezone.utc).isoformat()\n            with self._transaction() as conn:\n                conn.execute(\"\"\"\n                    UPDATE tool_calls\n                    SET status = ?, result_text = ?, completed_at = ?\n                    WHERE session_id = ? AND call_id = ?\n                \"\"\", ('completed' if success else 'failed',\n                      result_text, now, session_id, call_id))\n\n    async def find_completed_call(\n        self, idempotency_key: str,\n    ) -> dict | None:\n        async with self._lock:\n            row = self._conn.execute(\"\"\"\n                SELECT call_id, tool_name, args_json, result_text, status\n                FROM tool_calls\n                WHERE idempotency_key = ? AND status = 'completed'\n                LIMIT 1\n            \"\"\", (idempotency_key,)).fetchone()\n            if row is None:\n                return None\n            return dict(row)\n\n    async def load_latest(self, session_id: str) -> dict | None:\n        async with self._lock:\n            row = self._conn.execute(\"\"\"\n                SELECT transcript_json, plan_json, budget_spent_usd, version\n                FROM checkpoints\n                WHERE session_id = ?\n                ORDER BY version DESC\n                LIMIT 1\n            \"\"\", (session_id,)).fetchone()\n            if row is None:\n                return None\n            return {\n                \"transcript\": json.loads(row[\"transcript_json\"]),\n                \"plan\": json.loads(row[\"plan_json\"]) if row[\"plan_json\"] else None,\n                \"budget_spent_usd\": row[\"budget_spent_usd\"],\n                \"version\": row[\"version\"],\n            }\n","python","",[151,349,350,359,377,384,393,401,409,422,435,455,468,473,478,492,499,505,511,517,523,529,534,540,546,552,557,563,569,575,581,586,591,597,602,608,614,620,626,632,638,644,650,656,661,666,672,678,684,689,694,708,756,782,824,870,893,913,936,941,952,967,975,989,1006,1017,1033,1039,1044,1072,1112,1123,1128,1134,1140,1146,1152,1158,1175,1208,1232,1238,1253,1259,1265,1271,1297,1310,1316,1322,1339,1344,1357,1389,1416,1429,1444,1471,1490,1506,1518,1538,1549,1569,1582,1588,1594,1600,1606,1628,1647,1676,1684,1697,1703,1720,1729,1734,1746,1779,1800,1811,1826,1853,1872,1885,1891,1897,1903,1909,1944,1956,1961,1973,2005,2018,2029,2044,2071,2090,2103,2109,2115,2121,2155,2175,2180,2192,2207,2222,2237,2259,2265,2271,2277,2283,2303,2319,2327,2342,2347,2381,2396,2417,2423,2429,2435,2441,2446,2463,2476,2483,2491,2526,2581,2605,2629],{"__ignoreMap":347},[351,352,355],"span",{"class":353,"line":354},"line",1,[351,356,358],{"class":357},"sutJx","# src\u002Fharness\u002Fcheckpoint\u002Fstore.py\n",[351,360,362,366,370,373],{"class":353,"line":361},2,[351,363,365],{"class":364},"sVHd0","from",[351,367,369],{"class":368},"s_hVV"," __future__",[351,371,372],{"class":364}," import",[351,374,376],{"class":375},"su5hD"," annotations\n",[351,378,380],{"class":353,"line":379},3,[351,381,383],{"emptyLinePlaceholder":382},true,"\n",[351,385,387,390],{"class":353,"line":386},4,[351,388,389],{"class":364},"import",[351,391,392],{"class":375}," asyncio\n",[351,394,396,398],{"class":353,"line":395},5,[351,397,389],{"class":364},[351,399,400],{"class":375}," json\n",[351,402,404,406],{"class":353,"line":403},6,[351,405,389],{"class":364},[351,407,408],{"class":375}," sqlite3\n",[351,410,412,414,417,419],{"class":353,"line":411},7,[351,413,365],{"class":364},[351,415,416],{"class":375}," contextlib ",[351,418,389],{"class":364},[351,420,421],{"class":375}," contextmanager\n",[351,423,425,427,430,432],{"class":353,"line":424},8,[351,426,365],{"class":364},[351,428,429],{"class":375}," dataclasses ",[351,431,389],{"class":364},[351,433,434],{"class":375}," dataclass\n",[351,436,438,440,443,445,448,452],{"class":353,"line":437},9,[351,439,365],{"class":364},[351,441,442],{"class":375}," datetime ",[351,444,389],{"class":364},[351,446,447],{"class":375}," datetime",[351,449,451],{"class":450},"sP7_E",",",[351,453,454],{"class":375}," timezone\n",[351,456,458,460,463,465],{"class":353,"line":457},10,[351,459,365],{"class":364},[351,461,462],{"class":375}," pathlib ",[351,464,389],{"class":364},[351,466,467],{"class":375}," Path\n",[351,469,471],{"class":353,"line":470},11,[351,472,383],{"emptyLinePlaceholder":382},[351,474,476],{"class":353,"line":475},12,[351,477,383],{"emptyLinePlaceholder":382},[351,479,481,484,488],{"class":353,"line":480},13,[351,482,483],{"class":368},"SCHEMA",[351,485,487],{"class":486},"smGrS"," =",[351,489,491],{"class":490},"sjJ54"," \"\"\"\n",[351,493,495],{"class":353,"line":494},14,[351,496,498],{"class":497},"s_sjI","CREATE TABLE IF NOT EXISTS sessions (\n",[351,500,502],{"class":353,"line":501},15,[351,503,504],{"class":497},"    session_id TEXT PRIMARY KEY,\n",[351,506,508],{"class":353,"line":507},16,[351,509,510],{"class":497},"    created_at TIMESTAMP NOT NULL,\n",[351,512,514],{"class":353,"line":513},17,[351,515,516],{"class":497},"    updated_at TIMESTAMP NOT NULL,\n",[351,518,520],{"class":353,"line":519},18,[351,521,522],{"class":497},"    status TEXT NOT NULL  -- 'active', 'completed', 'failed', 'cancelled'\n",[351,524,526],{"class":353,"line":525},19,[351,527,528],{"class":497},");\n",[351,530,532],{"class":353,"line":531},20,[351,533,383],{"emptyLinePlaceholder":382},[351,535,537],{"class":353,"line":536},21,[351,538,539],{"class":497},"CREATE TABLE IF NOT EXISTS checkpoints (\n",[351,541,543],{"class":353,"line":542},22,[351,544,545],{"class":497},"    session_id TEXT NOT NULL,\n",[351,547,549],{"class":353,"line":548},23,[351,550,551],{"class":497},"    version INTEGER NOT NULL,\n",[351,553,555],{"class":353,"line":554},24,[351,556,510],{"class":497},[351,558,560],{"class":353,"line":559},25,[351,561,562],{"class":497},"    transcript_json TEXT NOT NULL,\n",[351,564,566],{"class":353,"line":565},26,[351,567,568],{"class":497},"    plan_json TEXT,\n",[351,570,572],{"class":353,"line":571},27,[351,573,574],{"class":497},"    budget_spent_usd REAL NOT NULL DEFAULT 0,\n",[351,576,578],{"class":353,"line":577},28,[351,579,580],{"class":497},"    PRIMARY KEY (session_id, version)\n",[351,582,584],{"class":353,"line":583},29,[351,585,528],{"class":497},[351,587,589],{"class":353,"line":588},30,[351,590,383],{"emptyLinePlaceholder":382},[351,592,594],{"class":353,"line":593},31,[351,595,596],{"class":497},"CREATE TABLE IF NOT EXISTS tool_calls (\n",[351,598,600],{"class":353,"line":599},32,[351,601,545],{"class":497},[351,603,605],{"class":353,"line":604},33,[351,606,607],{"class":497},"    call_id TEXT NOT NULL,\n",[351,609,611],{"class":353,"line":610},34,[351,612,613],{"class":497},"    tool_name TEXT NOT NULL,\n",[351,615,617],{"class":353,"line":616},35,[351,618,619],{"class":497},"    args_json TEXT NOT NULL,\n",[351,621,623],{"class":353,"line":622},36,[351,624,625],{"class":497},"    idempotency_key TEXT NOT NULL,\n",[351,627,629],{"class":353,"line":628},37,[351,630,631],{"class":497},"    status TEXT NOT NULL,  -- 'issued', 'completed', 'failed'\n",[351,633,635],{"class":353,"line":634},38,[351,636,637],{"class":497},"    result_text TEXT,\n",[351,639,641],{"class":353,"line":640},39,[351,642,643],{"class":497},"    started_at TIMESTAMP NOT NULL,\n",[351,645,647],{"class":353,"line":646},40,[351,648,649],{"class":497},"    completed_at TIMESTAMP,\n",[351,651,653],{"class":353,"line":652},41,[351,654,655],{"class":497},"    PRIMARY KEY (session_id, call_id)\n",[351,657,659],{"class":353,"line":658},42,[351,660,528],{"class":497},[351,662,664],{"class":353,"line":663},43,[351,665,383],{"emptyLinePlaceholder":382},[351,667,669],{"class":353,"line":668},44,[351,670,671],{"class":497},"CREATE INDEX IF NOT EXISTS idx_tool_calls_idempotency\n",[351,673,675],{"class":353,"line":674},45,[351,676,677],{"class":497},"    ON tool_calls(idempotency_key);\n",[351,679,681],{"class":353,"line":680},46,[351,682,683],{"class":490},"\"\"\"\n",[351,685,687],{"class":353,"line":686},47,[351,688,383],{"emptyLinePlaceholder":382},[351,690,692],{"class":353,"line":691},48,[351,693,383],{"emptyLinePlaceholder":382},[351,695,697,701,705],{"class":353,"line":696},49,[351,698,700],{"class":699},"sbsja","class",[351,702,704],{"class":703},"sbgvK"," Checkpointer",[351,706,707],{"class":450},":\n",[351,709,711,714,718,721,725,727,731,734,738,741,744,747,750,754],{"class":353,"line":710},50,[351,712,713],{"class":699},"    def",[351,715,717],{"class":716},"sptTA"," __init__",[351,719,720],{"class":450},"(",[351,722,724],{"class":723},"smCYv","self",[351,726,451],{"class":450},[351,728,730],{"class":729},"sFwrP"," db_path",[351,732,733],{"class":450},":",[351,735,737],{"class":736},"sZMiF"," str",[351,739,740],{"class":486}," |",[351,742,743],{"class":375}," Path",[351,745,746],{"class":450},")",[351,748,749],{"class":450}," ->",[351,751,753],{"class":752},"s39Yj"," None",[351,755,707],{"class":450},[351,757,759,762,765,769,771,774,776,779],{"class":353,"line":758},51,[351,760,761],{"class":368},"        self",[351,763,764],{"class":450},".",[351,766,768],{"class":767},"skxfh","_path",[351,770,487],{"class":486},[351,772,743],{"class":773},"slqww",[351,775,720],{"class":450},[351,777,778],{"class":773},"db_path",[351,780,781],{"class":450},")\n",[351,783,785,787,789,791,793,796,798,801,803,807,810,813,815,818,820,822],{"class":353,"line":784},52,[351,786,761],{"class":368},[351,788,764],{"class":450},[351,790,768],{"class":767},[351,792,764],{"class":450},[351,794,795],{"class":767},"parent",[351,797,764],{"class":450},[351,799,800],{"class":773},"mkdir",[351,802,720],{"class":450},[351,804,806],{"class":805},"s99_P","parents",[351,808,809],{"class":486},"=",[351,811,812],{"class":752},"True",[351,814,451],{"class":450},[351,816,817],{"class":805}," exist_ok",[351,819,809],{"class":486},[351,821,812],{"class":752},[351,823,781],{"class":450},[351,825,827,829,831,834,836,839,841,844,846,849,851,853,855,857,860,863,865,868],{"class":353,"line":826},53,[351,828,761],{"class":368},[351,830,764],{"class":450},[351,832,833],{"class":767},"_conn",[351,835,487],{"class":486},[351,837,838],{"class":375}," sqlite3",[351,840,764],{"class":450},[351,842,843],{"class":773},"connect",[351,845,720],{"class":450},[351,847,848],{"class":736},"str",[351,850,720],{"class":450},[351,852,724],{"class":368},[351,854,764],{"class":450},[351,856,768],{"class":767},[351,858,859],{"class":450},"),",[351,861,862],{"class":805}," check_same_thread",[351,864,809],{"class":486},[351,866,867],{"class":752},"False",[351,869,781],{"class":450},[351,871,873,875,877,879,881,884,886,888,890],{"class":353,"line":872},54,[351,874,761],{"class":368},[351,876,764],{"class":450},[351,878,833],{"class":767},[351,880,764],{"class":450},[351,882,883],{"class":767},"row_factory",[351,885,487],{"class":486},[351,887,838],{"class":375},[351,889,764],{"class":450},[351,891,892],{"class":767},"Row\n",[351,894,896,898,900,902,904,907,909,911],{"class":353,"line":895},55,[351,897,761],{"class":368},[351,899,764],{"class":450},[351,901,833],{"class":767},[351,903,764],{"class":450},[351,905,906],{"class":773},"executescript",[351,908,720],{"class":450},[351,910,483],{"class":716},[351,912,781],{"class":450},[351,914,916,918,920,923,925,928,930,933],{"class":353,"line":915},56,[351,917,761],{"class":368},[351,919,764],{"class":450},[351,921,922],{"class":767},"_lock",[351,924,487],{"class":486},[351,926,927],{"class":375}," asyncio",[351,929,764],{"class":450},[351,931,932],{"class":773},"Lock",[351,934,935],{"class":450},"()\n",[351,937,939],{"class":353,"line":938},57,[351,940,383],{"emptyLinePlaceholder":382},[351,942,944,948],{"class":353,"line":943},58,[351,945,947],{"class":946},"stp6e","    @",[351,949,951],{"class":950},"sGLFI","contextmanager\n",[351,953,955,957,960,962,964],{"class":353,"line":954},59,[351,956,713],{"class":699},[351,958,959],{"class":950}," _transaction",[351,961,720],{"class":450},[351,963,724],{"class":723},[351,965,966],{"class":450},"):\n",[351,968,970,973],{"class":353,"line":969},60,[351,971,972],{"class":364},"        try",[351,974,707],{"class":450},[351,976,978,981,984,986],{"class":353,"line":977},61,[351,979,980],{"class":364},"            yield",[351,982,983],{"class":368}," self",[351,985,764],{"class":450},[351,987,988],{"class":767},"_conn\n",[351,990,992,995,997,999,1001,1004],{"class":353,"line":991},62,[351,993,994],{"class":368},"            self",[351,996,764],{"class":450},[351,998,833],{"class":767},[351,1000,764],{"class":450},[351,1002,1003],{"class":773},"commit",[351,1005,935],{"class":450},[351,1007,1009,1012,1015],{"class":353,"line":1008},63,[351,1010,1011],{"class":364},"        except",[351,1013,1014],{"class":736}," Exception",[351,1016,707],{"class":450},[351,1018,1020,1022,1024,1026,1028,1031],{"class":353,"line":1019},64,[351,1021,994],{"class":368},[351,1023,764],{"class":450},[351,1025,833],{"class":767},[351,1027,764],{"class":450},[351,1029,1030],{"class":773},"rollback",[351,1032,935],{"class":450},[351,1034,1036],{"class":353,"line":1035},65,[351,1037,1038],{"class":364},"            raise\n",[351,1040,1042],{"class":353,"line":1041},66,[351,1043,383],{"emptyLinePlaceholder":382},[351,1045,1047,1050,1053,1056,1058,1060,1062,1065,1067,1069],{"class":353,"line":1046},67,[351,1048,1049],{"class":699},"    async",[351,1051,1052],{"class":699}," def",[351,1054,1055],{"class":950}," start_session",[351,1057,720],{"class":450},[351,1059,724],{"class":723},[351,1061,451],{"class":450},[351,1063,1064],{"class":729}," session_id",[351,1066,733],{"class":450},[351,1068,737],{"class":736},[351,1070,1071],{"class":450},",\n",[351,1073,1075,1078,1080,1082,1084,1087,1089,1092,1094,1096,1098,1100,1102,1104,1106,1108,1110],{"class":353,"line":1074},68,[351,1076,1077],{"class":729},"                             user_message",[351,1079,733],{"class":450},[351,1081,737],{"class":736},[351,1083,487],{"class":486},[351,1085,1086],{"class":490}," \"\"",[351,1088,451],{"class":450},[351,1090,1091],{"class":729}," system_prompt",[351,1093,733],{"class":450},[351,1095,737],{"class":736},[351,1097,740],{"class":486},[351,1099,753],{"class":752},[351,1101,487],{"class":486},[351,1103,753],{"class":752},[351,1105,746],{"class":450},[351,1107,749],{"class":450},[351,1109,753],{"class":752},[351,1111,707],{"class":450},[351,1113,1115,1119],{"class":353,"line":1114},69,[351,1116,1118],{"class":1117},"s2W-s","        \"\"\"",[351,1120,1122],{"class":1121},"sithA","Record a new session or touch its updated_at.\n",[351,1124,1126],{"class":353,"line":1125},70,[351,1127,383],{"emptyLinePlaceholder":382},[351,1129,1131],{"class":353,"line":1130},71,[351,1132,1133],{"class":1121},"        user_message and system_prompt are accepted for API back-compat but\n",[351,1135,1137],{"class":353,"line":1136},72,[351,1138,1139],{"class":1121},"        no longer stored on `sessions` — they live in the first checkpoint's\n",[351,1141,1143],{"class":353,"line":1142},73,[351,1144,1145],{"class":1121},"        transcript_json. A resume with a new user message does not overwrite\n",[351,1147,1149],{"class":353,"line":1148},74,[351,1150,1151],{"class":1121},"        the original.\n",[351,1153,1155],{"class":353,"line":1154},75,[351,1156,1157],{"class":1117},"        \"\"\"\n",[351,1159,1161,1164,1167,1169,1171,1173],{"class":353,"line":1160},76,[351,1162,1163],{"class":364},"        async",[351,1165,1166],{"class":364}," with",[351,1168,983],{"class":368},[351,1170,764],{"class":450},[351,1172,922],{"class":767},[351,1174,707],{"class":450},[351,1176,1178,1181,1183,1185,1187,1190,1192,1195,1197,1200,1203,1206],{"class":353,"line":1177},77,[351,1179,1180],{"class":375},"            now ",[351,1182,809],{"class":486},[351,1184,447],{"class":375},[351,1186,764],{"class":450},[351,1188,1189],{"class":773},"now",[351,1191,720],{"class":450},[351,1193,1194],{"class":773},"timezone",[351,1196,764],{"class":450},[351,1198,1199],{"class":767},"utc",[351,1201,1202],{"class":450},").",[351,1204,1205],{"class":773},"isoformat",[351,1207,935],{"class":450},[351,1209,1211,1214,1216,1218,1221,1224,1227,1230],{"class":353,"line":1210},78,[351,1212,1213],{"class":364},"            with",[351,1215,983],{"class":368},[351,1217,764],{"class":450},[351,1219,1220],{"class":773},"_transaction",[351,1222,1223],{"class":450},"()",[351,1225,1226],{"class":364}," as",[351,1228,1229],{"class":375}," conn",[351,1231,707],{"class":450},[351,1233,1235],{"class":353,"line":1234},79,[351,1236,1237],{"class":357},"                # INSERT OR IGNORE preserves the original created_at\n",[351,1239,1241,1244,1246,1249,1251],{"class":353,"line":1240},80,[351,1242,1243],{"class":375},"                conn",[351,1245,764],{"class":450},[351,1247,1248],{"class":773},"execute",[351,1250,720],{"class":450},[351,1252,683],{"class":490},[351,1254,1256],{"class":353,"line":1255},81,[351,1257,1258],{"class":497},"                    INSERT OR IGNORE INTO sessions\n",[351,1260,1262],{"class":353,"line":1261},82,[351,1263,1264],{"class":497},"                    (session_id, created_at, updated_at, status)\n",[351,1266,1268],{"class":353,"line":1267},83,[351,1269,1270],{"class":497},"                    VALUES (?, ?, ?, 'active')\n",[351,1272,1274,1277,1279,1282,1285,1287,1290,1292,1294],{"class":353,"line":1273},84,[351,1275,1276],{"class":490},"                \"\"\"",[351,1278,451],{"class":450},[351,1280,1281],{"class":450}," (",[351,1283,1284],{"class":773},"session_id",[351,1286,451],{"class":450},[351,1288,1289],{"class":773}," now",[351,1291,451],{"class":450},[351,1293,1289],{"class":773},[351,1295,1296],{"class":450},"))\n",[351,1298,1300,1302,1304,1306,1308],{"class":353,"line":1299},85,[351,1301,1243],{"class":375},[351,1303,764],{"class":450},[351,1305,1248],{"class":773},[351,1307,720],{"class":450},[351,1309,683],{"class":490},[351,1311,1313],{"class":353,"line":1312},86,[351,1314,1315],{"class":497},"                    UPDATE sessions SET updated_at = ?, status = 'active'\n",[351,1317,1319],{"class":353,"line":1318},87,[351,1320,1321],{"class":497},"                    WHERE session_id = ?\n",[351,1323,1325,1327,1329,1331,1333,1335,1337],{"class":353,"line":1324},88,[351,1326,1276],{"class":490},[351,1328,451],{"class":450},[351,1330,1281],{"class":450},[351,1332,1189],{"class":773},[351,1334,451],{"class":450},[351,1336,1064],{"class":773},[351,1338,1296],{"class":450},[351,1340,1342],{"class":353,"line":1341},89,[351,1343,383],{"emptyLinePlaceholder":382},[351,1345,1347,1349,1351,1354],{"class":353,"line":1346},90,[351,1348,1049],{"class":699},[351,1350,1052],{"class":699},[351,1352,1353],{"class":950}," save_checkpoint",[351,1355,1356],{"class":450},"(\n",[351,1358,1360,1362,1364,1366,1368,1370,1372,1375,1377,1380,1383,1386],{"class":353,"line":1359},91,[351,1361,761],{"class":723},[351,1363,451],{"class":450},[351,1365,1064],{"class":729},[351,1367,733],{"class":450},[351,1369,737],{"class":736},[351,1371,451],{"class":450},[351,1373,1374],{"class":729}," transcript",[351,1376,733],{"class":450},[351,1378,1379],{"class":375}," list",[351,1381,1382],{"class":450},"[",[351,1384,1385],{"class":736},"dict",[351,1387,1388],{"class":450},"],\n",[351,1390,1392,1395,1397,1400,1402,1404,1406,1409,1411,1414],{"class":353,"line":1391},92,[351,1393,1394],{"class":729},"        plan",[351,1396,733],{"class":450},[351,1398,1399],{"class":736}," dict",[351,1401,740],{"class":486},[351,1403,753],{"class":752},[351,1405,451],{"class":450},[351,1407,1408],{"class":729}," budget_spent_usd",[351,1410,733],{"class":450},[351,1412,1413],{"class":736}," float",[351,1415,1071],{"class":450},[351,1417,1419,1422,1424,1427],{"class":353,"line":1418},93,[351,1420,1421],{"class":450},"    )",[351,1423,749],{"class":450},[351,1425,1426],{"class":736}," int",[351,1428,707],{"class":450},[351,1430,1432,1434,1436,1438,1440,1442],{"class":353,"line":1431},94,[351,1433,1163],{"class":364},[351,1435,1166],{"class":364},[351,1437,983],{"class":368},[351,1439,764],{"class":450},[351,1441,922],{"class":767},[351,1443,707],{"class":450},[351,1445,1447,1449,1451,1453,1455,1457,1459,1461,1463,1465,1467,1469],{"class":353,"line":1446},95,[351,1448,1180],{"class":375},[351,1450,809],{"class":486},[351,1452,447],{"class":375},[351,1454,764],{"class":450},[351,1456,1189],{"class":773},[351,1458,720],{"class":450},[351,1460,1194],{"class":773},[351,1462,764],{"class":450},[351,1464,1199],{"class":767},[351,1466,1202],{"class":450},[351,1468,1205],{"class":773},[351,1470,935],{"class":450},[351,1472,1474,1476,1478,1480,1482,1484,1486,1488],{"class":353,"line":1473},96,[351,1475,1213],{"class":364},[351,1477,983],{"class":368},[351,1479,764],{"class":450},[351,1481,1220],{"class":773},[351,1483,1223],{"class":450},[351,1485,1226],{"class":364},[351,1487,1229],{"class":375},[351,1489,707],{"class":450},[351,1491,1493,1496,1498,1500,1502,1504],{"class":353,"line":1492},97,[351,1494,1495],{"class":375},"                row ",[351,1497,809],{"class":486},[351,1499,1229],{"class":375},[351,1501,764],{"class":450},[351,1503,1248],{"class":773},[351,1505,1356],{"class":450},[351,1507,1509,1512,1515],{"class":353,"line":1508},98,[351,1510,1511],{"class":490},"                    \"",[351,1513,1514],{"class":497},"SELECT COALESCE(MAX(version), 0) + 1 FROM checkpoints ",[351,1516,1517],{"class":490},"\"\n",[351,1519,1521,1523,1526,1529,1531,1533,1535],{"class":353,"line":1520},99,[351,1522,1511],{"class":490},[351,1524,1525],{"class":497},"WHERE session_id = ?",[351,1527,1528],{"class":490},"\"",[351,1530,451],{"class":450},[351,1532,1281],{"class":450},[351,1534,1284],{"class":773},[351,1536,1537],{"class":450},",)\n",[351,1539,1541,1544,1547],{"class":353,"line":1540},100,[351,1542,1543],{"class":450},"                ).",[351,1545,1546],{"class":773},"fetchone",[351,1548,935],{"class":450},[351,1550,1552,1555,1557,1560,1562,1566],{"class":353,"line":1551},101,[351,1553,1554],{"class":375},"                version ",[351,1556,809],{"class":486},[351,1558,1559],{"class":375}," row",[351,1561,1382],{"class":450},[351,1563,1565],{"class":1564},"srdBf","0",[351,1567,1568],{"class":450},"]\n",[351,1570,1572,1574,1576,1578,1580],{"class":353,"line":1571},102,[351,1573,1243],{"class":375},[351,1575,764],{"class":450},[351,1577,1248],{"class":773},[351,1579,720],{"class":450},[351,1581,683],{"class":490},[351,1583,1585],{"class":353,"line":1584},103,[351,1586,1587],{"class":497},"                    INSERT INTO checkpoints\n",[351,1589,1591],{"class":353,"line":1590},104,[351,1592,1593],{"class":497},"                    (session_id, version, created_at,\n",[351,1595,1597],{"class":353,"line":1596},105,[351,1598,1599],{"class":497},"                     transcript_json, plan_json, budget_spent_usd)\n",[351,1601,1603],{"class":353,"line":1602},106,[351,1604,1605],{"class":497},"                    VALUES (?, ?, ?, ?, ?, ?)\n",[351,1607,1609,1611,1613,1615,1617,1619,1622,1624,1626],{"class":353,"line":1608},107,[351,1610,1276],{"class":490},[351,1612,451],{"class":450},[351,1614,1281],{"class":450},[351,1616,1284],{"class":773},[351,1618,451],{"class":450},[351,1620,1621],{"class":773}," version",[351,1623,451],{"class":450},[351,1625,1289],{"class":773},[351,1627,1071],{"class":450},[351,1629,1631,1634,1636,1639,1641,1644],{"class":353,"line":1630},108,[351,1632,1633],{"class":773},"                      json",[351,1635,764],{"class":450},[351,1637,1638],{"class":773},"dumps",[351,1640,720],{"class":450},[351,1642,1643],{"class":773},"transcript",[351,1645,1646],{"class":450},"),\n",[351,1648,1650,1652,1654,1656,1658,1661,1663,1666,1669,1672,1674],{"class":353,"line":1649},109,[351,1651,1633],{"class":773},[351,1653,764],{"class":450},[351,1655,1638],{"class":773},[351,1657,720],{"class":450},[351,1659,1660],{"class":773},"plan",[351,1662,746],{"class":450},[351,1664,1665],{"class":364}," if",[351,1667,1668],{"class":773}," plan ",[351,1670,1671],{"class":364},"else",[351,1673,753],{"class":752},[351,1675,1071],{"class":450},[351,1677,1679,1682],{"class":353,"line":1678},110,[351,1680,1681],{"class":773},"                      budget_spent_usd",[351,1683,1296],{"class":450},[351,1685,1687,1689,1691,1693,1695],{"class":353,"line":1686},111,[351,1688,1243],{"class":375},[351,1690,764],{"class":450},[351,1692,1248],{"class":773},[351,1694,720],{"class":450},[351,1696,683],{"class":490},[351,1698,1700],{"class":353,"line":1699},112,[351,1701,1702],{"class":497},"                    UPDATE sessions SET updated_at = ? WHERE session_id = ?\n",[351,1704,1706,1708,1710,1712,1714,1716,1718],{"class":353,"line":1705},113,[351,1707,1276],{"class":490},[351,1709,451],{"class":450},[351,1711,1281],{"class":450},[351,1713,1189],{"class":773},[351,1715,451],{"class":450},[351,1717,1064],{"class":773},[351,1719,1296],{"class":450},[351,1721,1723,1726],{"class":353,"line":1722},114,[351,1724,1725],{"class":364},"                return",[351,1727,1728],{"class":375}," version\n",[351,1730,1732],{"class":353,"line":1731},115,[351,1733,383],{"emptyLinePlaceholder":382},[351,1735,1737,1739,1741,1744],{"class":353,"line":1736},116,[351,1738,1049],{"class":699},[351,1740,1052],{"class":699},[351,1742,1743],{"class":950}," record_tool_call_issued",[351,1745,1356],{"class":450},[351,1747,1749,1751,1753,1755,1757,1759,1761,1764,1766,1768,1770,1773,1775,1777],{"class":353,"line":1748},117,[351,1750,761],{"class":723},[351,1752,451],{"class":450},[351,1754,1064],{"class":729},[351,1756,733],{"class":450},[351,1758,737],{"class":736},[351,1760,451],{"class":450},[351,1762,1763],{"class":729}," call_id",[351,1765,733],{"class":450},[351,1767,737],{"class":736},[351,1769,451],{"class":450},[351,1771,1772],{"class":729}," tool_name",[351,1774,733],{"class":450},[351,1776,737],{"class":736},[351,1778,1071],{"class":450},[351,1780,1782,1785,1787,1789,1791,1794,1796,1798],{"class":353,"line":1781},118,[351,1783,1784],{"class":729},"        args",[351,1786,733],{"class":450},[351,1788,1399],{"class":736},[351,1790,451],{"class":450},[351,1792,1793],{"class":729}," idempotency_key",[351,1795,733],{"class":450},[351,1797,737],{"class":736},[351,1799,1071],{"class":450},[351,1801,1803,1805,1807,1809],{"class":353,"line":1802},119,[351,1804,1421],{"class":450},[351,1806,749],{"class":450},[351,1808,753],{"class":752},[351,1810,707],{"class":450},[351,1812,1814,1816,1818,1820,1822,1824],{"class":353,"line":1813},120,[351,1815,1163],{"class":364},[351,1817,1166],{"class":364},[351,1819,983],{"class":368},[351,1821,764],{"class":450},[351,1823,922],{"class":767},[351,1825,707],{"class":450},[351,1827,1829,1831,1833,1835,1837,1839,1841,1843,1845,1847,1849,1851],{"class":353,"line":1828},121,[351,1830,1180],{"class":375},[351,1832,809],{"class":486},[351,1834,447],{"class":375},[351,1836,764],{"class":450},[351,1838,1189],{"class":773},[351,1840,720],{"class":450},[351,1842,1194],{"class":773},[351,1844,764],{"class":450},[351,1846,1199],{"class":767},[351,1848,1202],{"class":450},[351,1850,1205],{"class":773},[351,1852,935],{"class":450},[351,1854,1856,1858,1860,1862,1864,1866,1868,1870],{"class":353,"line":1855},122,[351,1857,1213],{"class":364},[351,1859,983],{"class":368},[351,1861,764],{"class":450},[351,1863,1220],{"class":773},[351,1865,1223],{"class":450},[351,1867,1226],{"class":364},[351,1869,1229],{"class":375},[351,1871,707],{"class":450},[351,1873,1875,1877,1879,1881,1883],{"class":353,"line":1874},123,[351,1876,1243],{"class":375},[351,1878,764],{"class":450},[351,1880,1248],{"class":773},[351,1882,720],{"class":450},[351,1884,683],{"class":490},[351,1886,1888],{"class":353,"line":1887},124,[351,1889,1890],{"class":497},"                    INSERT OR IGNORE INTO tool_calls\n",[351,1892,1894],{"class":353,"line":1893},125,[351,1895,1896],{"class":497},"                    (session_id, call_id, tool_name, args_json,\n",[351,1898,1900],{"class":353,"line":1899},126,[351,1901,1902],{"class":497},"                     idempotency_key, status, started_at)\n",[351,1904,1906],{"class":353,"line":1905},127,[351,1907,1908],{"class":497},"                    VALUES (?, ?, ?, ?, ?, 'issued', ?)\n",[351,1910,1912,1914,1916,1918,1920,1922,1924,1926,1928,1930,1933,1935,1937,1939,1942],{"class":353,"line":1911},128,[351,1913,1276],{"class":490},[351,1915,451],{"class":450},[351,1917,1281],{"class":450},[351,1919,1284],{"class":773},[351,1921,451],{"class":450},[351,1923,1763],{"class":773},[351,1925,451],{"class":450},[351,1927,1772],{"class":773},[351,1929,451],{"class":450},[351,1931,1932],{"class":773}," json",[351,1934,764],{"class":450},[351,1936,1638],{"class":773},[351,1938,720],{"class":450},[351,1940,1941],{"class":773},"args",[351,1943,1646],{"class":450},[351,1945,1947,1950,1952,1954],{"class":353,"line":1946},129,[351,1948,1949],{"class":773},"                      idempotency_key",[351,1951,451],{"class":450},[351,1953,1289],{"class":773},[351,1955,1296],{"class":450},[351,1957,1959],{"class":353,"line":1958},130,[351,1960,383],{"emptyLinePlaceholder":382},[351,1962,1964,1966,1968,1971],{"class":353,"line":1963},131,[351,1965,1049],{"class":699},[351,1967,1052],{"class":699},[351,1969,1970],{"class":950}," record_tool_call_result",[351,1972,1356],{"class":450},[351,1974,1976,1978,1980,1982,1984,1986,1988,1990,1992,1994,1996,1999,2001,2003],{"class":353,"line":1975},132,[351,1977,761],{"class":723},[351,1979,451],{"class":450},[351,1981,1064],{"class":729},[351,1983,733],{"class":450},[351,1985,737],{"class":736},[351,1987,451],{"class":450},[351,1989,1763],{"class":729},[351,1991,733],{"class":450},[351,1993,737],{"class":736},[351,1995,451],{"class":450},[351,1997,1998],{"class":729}," result_text",[351,2000,733],{"class":450},[351,2002,737],{"class":736},[351,2004,1071],{"class":450},[351,2006,2008,2011,2013,2016],{"class":353,"line":2007},133,[351,2009,2010],{"class":729},"        success",[351,2012,733],{"class":450},[351,2014,2015],{"class":736}," bool",[351,2017,1071],{"class":450},[351,2019,2021,2023,2025,2027],{"class":353,"line":2020},134,[351,2022,1421],{"class":450},[351,2024,749],{"class":450},[351,2026,753],{"class":752},[351,2028,707],{"class":450},[351,2030,2032,2034,2036,2038,2040,2042],{"class":353,"line":2031},135,[351,2033,1163],{"class":364},[351,2035,1166],{"class":364},[351,2037,983],{"class":368},[351,2039,764],{"class":450},[351,2041,922],{"class":767},[351,2043,707],{"class":450},[351,2045,2047,2049,2051,2053,2055,2057,2059,2061,2063,2065,2067,2069],{"class":353,"line":2046},136,[351,2048,1180],{"class":375},[351,2050,809],{"class":486},[351,2052,447],{"class":375},[351,2054,764],{"class":450},[351,2056,1189],{"class":773},[351,2058,720],{"class":450},[351,2060,1194],{"class":773},[351,2062,764],{"class":450},[351,2064,1199],{"class":767},[351,2066,1202],{"class":450},[351,2068,1205],{"class":773},[351,2070,935],{"class":450},[351,2072,2074,2076,2078,2080,2082,2084,2086,2088],{"class":353,"line":2073},137,[351,2075,1213],{"class":364},[351,2077,983],{"class":368},[351,2079,764],{"class":450},[351,2081,1220],{"class":773},[351,2083,1223],{"class":450},[351,2085,1226],{"class":364},[351,2087,1229],{"class":375},[351,2089,707],{"class":450},[351,2091,2093,2095,2097,2099,2101],{"class":353,"line":2092},138,[351,2094,1243],{"class":375},[351,2096,764],{"class":450},[351,2098,1248],{"class":773},[351,2100,720],{"class":450},[351,2102,683],{"class":490},[351,2104,2106],{"class":353,"line":2105},139,[351,2107,2108],{"class":497},"                    UPDATE tool_calls\n",[351,2110,2112],{"class":353,"line":2111},140,[351,2113,2114],{"class":497},"                    SET status = ?, result_text = ?, completed_at = ?\n",[351,2116,2118],{"class":353,"line":2117},141,[351,2119,2120],{"class":497},"                    WHERE session_id = ? AND call_id = ?\n",[351,2122,2124,2126,2128,2130,2133,2136,2138,2140,2143,2145,2148,2151,2153],{"class":353,"line":2123},142,[351,2125,1276],{"class":490},[351,2127,451],{"class":450},[351,2129,1281],{"class":450},[351,2131,2132],{"class":490},"'",[351,2134,2135],{"class":497},"completed",[351,2137,2132],{"class":490},[351,2139,1665],{"class":364},[351,2141,2142],{"class":773}," success ",[351,2144,1671],{"class":364},[351,2146,2147],{"class":490}," '",[351,2149,2150],{"class":497},"failed",[351,2152,2132],{"class":490},[351,2154,1071],{"class":450},[351,2156,2158,2161,2163,2165,2167,2169,2171,2173],{"class":353,"line":2157},143,[351,2159,2160],{"class":773},"                      result_text",[351,2162,451],{"class":450},[351,2164,1289],{"class":773},[351,2166,451],{"class":450},[351,2168,1064],{"class":773},[351,2170,451],{"class":450},[351,2172,1763],{"class":773},[351,2174,1296],{"class":450},[351,2176,2178],{"class":353,"line":2177},144,[351,2179,383],{"emptyLinePlaceholder":382},[351,2181,2183,2185,2187,2190],{"class":353,"line":2182},145,[351,2184,1049],{"class":699},[351,2186,1052],{"class":699},[351,2188,2189],{"class":950}," find_completed_call",[351,2191,1356],{"class":450},[351,2193,2195,2197,2199,2201,2203,2205],{"class":353,"line":2194},146,[351,2196,761],{"class":723},[351,2198,451],{"class":450},[351,2200,1793],{"class":729},[351,2202,733],{"class":450},[351,2204,737],{"class":736},[351,2206,1071],{"class":450},[351,2208,2210,2212,2214,2216,2218,2220],{"class":353,"line":2209},147,[351,2211,1421],{"class":450},[351,2213,749],{"class":450},[351,2215,1399],{"class":736},[351,2217,740],{"class":486},[351,2219,753],{"class":752},[351,2221,707],{"class":450},[351,2223,2225,2227,2229,2231,2233,2235],{"class":353,"line":2224},148,[351,2226,1163],{"class":364},[351,2228,1166],{"class":364},[351,2230,983],{"class":368},[351,2232,764],{"class":450},[351,2234,922],{"class":767},[351,2236,707],{"class":450},[351,2238,2240,2243,2245,2247,2249,2251,2253,2255,2257],{"class":353,"line":2239},149,[351,2241,2242],{"class":375},"            row ",[351,2244,809],{"class":486},[351,2246,983],{"class":368},[351,2248,764],{"class":450},[351,2250,833],{"class":767},[351,2252,764],{"class":450},[351,2254,1248],{"class":773},[351,2256,720],{"class":450},[351,2258,683],{"class":490},[351,2260,2262],{"class":353,"line":2261},150,[351,2263,2264],{"class":497},"                SELECT call_id, tool_name, args_json, result_text, status\n",[351,2266,2268],{"class":353,"line":2267},151,[351,2269,2270],{"class":497},"                FROM tool_calls\n",[351,2272,2274],{"class":353,"line":2273},152,[351,2275,2276],{"class":497},"                WHERE idempotency_key = ? AND status = 'completed'\n",[351,2278,2280],{"class":353,"line":2279},153,[351,2281,2282],{"class":497},"                LIMIT 1\n",[351,2284,2286,2289,2291,2293,2296,2299,2301],{"class":353,"line":2285},154,[351,2287,2288],{"class":490},"            \"\"\"",[351,2290,451],{"class":450},[351,2292,1281],{"class":450},[351,2294,2295],{"class":773},"idempotency_key",[351,2297,2298],{"class":450},",)).",[351,2300,1546],{"class":773},[351,2302,935],{"class":450},[351,2304,2306,2309,2312,2315,2317],{"class":353,"line":2305},155,[351,2307,2308],{"class":364},"            if",[351,2310,2311],{"class":375}," row ",[351,2313,2314],{"class":486},"is",[351,2316,753],{"class":752},[351,2318,707],{"class":450},[351,2320,2322,2324],{"class":353,"line":2321},156,[351,2323,1725],{"class":364},[351,2325,2326],{"class":752}," None\n",[351,2328,2330,2333,2335,2337,2340],{"class":353,"line":2329},157,[351,2331,2332],{"class":364},"            return",[351,2334,1399],{"class":736},[351,2336,720],{"class":450},[351,2338,2339],{"class":773},"row",[351,2341,781],{"class":450},[351,2343,2345],{"class":353,"line":2344},158,[351,2346,383],{"emptyLinePlaceholder":382},[351,2348,2350,2352,2354,2357,2359,2361,2363,2365,2367,2369,2371,2373,2375,2377,2379],{"class":353,"line":2349},159,[351,2351,1049],{"class":699},[351,2353,1052],{"class":699},[351,2355,2356],{"class":950}," load_latest",[351,2358,720],{"class":450},[351,2360,724],{"class":723},[351,2362,451],{"class":450},[351,2364,1064],{"class":729},[351,2366,733],{"class":450},[351,2368,737],{"class":736},[351,2370,746],{"class":450},[351,2372,749],{"class":450},[351,2374,1399],{"class":736},[351,2376,740],{"class":486},[351,2378,753],{"class":752},[351,2380,707],{"class":450},[351,2382,2384,2386,2388,2390,2392,2394],{"class":353,"line":2383},160,[351,2385,1163],{"class":364},[351,2387,1166],{"class":364},[351,2389,983],{"class":368},[351,2391,764],{"class":450},[351,2393,922],{"class":767},[351,2395,707],{"class":450},[351,2397,2399,2401,2403,2405,2407,2409,2411,2413,2415],{"class":353,"line":2398},161,[351,2400,2242],{"class":375},[351,2402,809],{"class":486},[351,2404,983],{"class":368},[351,2406,764],{"class":450},[351,2408,833],{"class":767},[351,2410,764],{"class":450},[351,2412,1248],{"class":773},[351,2414,720],{"class":450},[351,2416,683],{"class":490},[351,2418,2420],{"class":353,"line":2419},162,[351,2421,2422],{"class":497},"                SELECT transcript_json, plan_json, budget_spent_usd, version\n",[351,2424,2426],{"class":353,"line":2425},163,[351,2427,2428],{"class":497},"                FROM checkpoints\n",[351,2430,2432],{"class":353,"line":2431},164,[351,2433,2434],{"class":497},"                WHERE session_id = ?\n",[351,2436,2438],{"class":353,"line":2437},165,[351,2439,2440],{"class":497},"                ORDER BY version DESC\n",[351,2442,2444],{"class":353,"line":2443},166,[351,2445,2282],{"class":497},[351,2447,2449,2451,2453,2455,2457,2459,2461],{"class":353,"line":2448},167,[351,2450,2288],{"class":490},[351,2452,451],{"class":450},[351,2454,1281],{"class":450},[351,2456,1284],{"class":773},[351,2458,2298],{"class":450},[351,2460,1546],{"class":773},[351,2462,935],{"class":450},[351,2464,2466,2468,2470,2472,2474],{"class":353,"line":2465},168,[351,2467,2308],{"class":364},[351,2469,2311],{"class":375},[351,2471,2314],{"class":486},[351,2473,753],{"class":752},[351,2475,707],{"class":450},[351,2477,2479,2481],{"class":353,"line":2478},169,[351,2480,1725],{"class":364},[351,2482,2326],{"class":752},[351,2484,2486,2488],{"class":353,"line":2485},170,[351,2487,2332],{"class":364},[351,2489,2490],{"class":450}," {\n",[351,2492,2494,2497,2499,2501,2503,2505,2507,2510,2512,2514,2516,2518,2521,2523],{"class":353,"line":2493},171,[351,2495,2496],{"class":490},"                \"",[351,2498,1643],{"class":497},[351,2500,1528],{"class":490},[351,2502,733],{"class":450},[351,2504,1932],{"class":375},[351,2506,764],{"class":450},[351,2508,2509],{"class":773},"loads",[351,2511,720],{"class":450},[351,2513,2339],{"class":773},[351,2515,1382],{"class":450},[351,2517,1528],{"class":490},[351,2519,2520],{"class":497},"transcript_json",[351,2522,1528],{"class":490},[351,2524,2525],{"class":450},"]),\n",[351,2527,2529,2531,2533,2535,2537,2539,2541,2543,2545,2547,2549,2551,2554,2556,2559,2561,2563,2565,2567,2569,2571,2574,2577,2579],{"class":353,"line":2528},172,[351,2530,2496],{"class":490},[351,2532,1660],{"class":497},[351,2534,1528],{"class":490},[351,2536,733],{"class":450},[351,2538,1932],{"class":375},[351,2540,764],{"class":450},[351,2542,2509],{"class":773},[351,2544,720],{"class":450},[351,2546,2339],{"class":773},[351,2548,1382],{"class":450},[351,2550,1528],{"class":490},[351,2552,2553],{"class":497},"plan_json",[351,2555,1528],{"class":490},[351,2557,2558],{"class":450},"])",[351,2560,1665],{"class":364},[351,2562,1559],{"class":375},[351,2564,1382],{"class":450},[351,2566,1528],{"class":490},[351,2568,2553],{"class":497},[351,2570,1528],{"class":490},[351,2572,2573],{"class":450},"]",[351,2575,2576],{"class":364}," else",[351,2578,753],{"class":752},[351,2580,1071],{"class":450},[351,2582,2584,2586,2589,2591,2593,2595,2597,2599,2601,2603],{"class":353,"line":2583},173,[351,2585,2496],{"class":490},[351,2587,2588],{"class":497},"budget_spent_usd",[351,2590,1528],{"class":490},[351,2592,733],{"class":450},[351,2594,1559],{"class":375},[351,2596,1382],{"class":450},[351,2598,1528],{"class":490},[351,2600,2588],{"class":497},[351,2602,1528],{"class":490},[351,2604,1388],{"class":450},[351,2606,2608,2610,2613,2615,2617,2619,2621,2623,2625,2627],{"class":353,"line":2607},174,[351,2609,2496],{"class":490},[351,2611,2612],{"class":497},"version",[351,2614,1528],{"class":490},[351,2616,733],{"class":450},[351,2618,1559],{"class":375},[351,2620,1382],{"class":450},[351,2622,1528],{"class":490},[351,2624,2612],{"class":497},[351,2626,1528],{"class":490},[351,2628,1388],{"class":450},[351,2630,2632],{"class":353,"line":2631},175,[351,2633,2634],{"class":450},"            }\n",[112,2636,2637],{},"Three decisions worth naming.",[112,2639,2640,2643,2644,2647],{},[124,2641,2642],{},"Versioned checkpoints, not in-place updates."," Every ",[151,2645,2646],{},"save_checkpoint"," inserts a new row with an incremented version. Disk-cheap; debugging-priceless. If a session's final answer was wrong, you can load any earlier checkpoint and see what the state was at that moment.",[112,2649,2650,2653],{},[124,2651,2652],{},"Tool-call log has its own table."," Not inside the checkpoint — the checkpoint is the conversation snapshot, the tool-call log is the side-effect record. They have different update frequencies and different query patterns.",[112,2655,2656,2666,2667,2669,2670,2673,2674,2676,2677,2679,2680,2682,2683,2685],{},[124,2657,2658,2661,2662,2665],{},[151,2659,2660],{},"sessions"," is identity, ",[151,2663,2664],{},"checkpoints"," is content."," The first user message and the system prompt don't live on ",[151,2668,2660],{}," — they're part of ",[151,2671,2672],{},"checkpoints[version=1].transcript_json",". One session is a multi-turn conversation, so one row in ",[151,2675,2660],{}," represents N user messages; storing \"the original user message\" would require choosing which one. Keep identity (session_id, status, timing) on ",[151,2678,2660],{},"; keep content (transcript, plan, budget) on ",[151,2681,2664],{},". If a caller wants the original user message accessible without parsing JSON — a dashboard listing sessions by their opening question, say — persist it as a separate column on ",[151,2684,2660],{}," yourself; nothing in the schema below depends on it being there.",[280,2687],{},[283,2689,2691],{"id":2690},"_213-write-before-execute-for-side-effects","21.3 Write-Before-Execute for Side Effects",[112,2693,2694,2695,2698,2699,2701,2702,2704],{},"The idempotency discipline. Before a mutating tool runs, record that we're about to run it. When it completes, record the result. On resume, check the log: if the call is recorded as ",[151,2696,2697],{},"issued"," but not ",[151,2700,2135],{},", we don't know whether it actually executed. If it's ",[151,2703,2135],{},", we know the result and return it without re-running.",[342,2706,2708],{"className":344,"code":2707,"language":346,"meta":347,"style":347},"# src\u002Fharness\u002Ftools\u002Fregistry.py (checkpoint-aware addition)\n\n@dataclass\nclass ToolRegistry:\n    # ... existing fields\n    checkpointer: \"Checkpointer | None\" = None\n    session_id: str | None = None\n\n    async def adispatch(self, name: str, args: dict, call_id: str) -> ToolResult:\n        # ... existing validation, permission, loop checks\n\n        tool = self.tools[name]\n        is_mutating = \"mutate\" in tool.side_effects or \"write\" in tool.side_effects\n\n        idempotency_key = None\n        if is_mutating and self.checkpointer and self.session_id:\n            idempotency_key = self._compute_idempotency_key(name, args)\n\n            # Check for a prior completion with the same key.\n            prior = await self.checkpointer.find_completed_call(idempotency_key)\n            if prior is not None:\n                return ToolResult(\n                    call_id=call_id,\n                    content=(f\"[idempotent replay of {prior['call_id']}]: \"\n                             f\"{prior['result_text']}\"),\n                )\n\n            # Record intent to execute BEFORE executing.\n            await self.checkpointer.record_tool_call_issued(\n                self.session_id, call_id, name, args, idempotency_key\n            )\n\n        try:\n            content = tool.run(**args)\n            result = ToolResult(call_id=call_id, content=content)\n        except Exception as e:\n            result = ToolResult(\n                call_id=call_id,\n                content=f\"{name} raised {type(e).__name__}: {e}\",\n                is_error=True,\n            )\n\n        if is_mutating and self.checkpointer and self.session_id:\n            await self.checkpointer.record_tool_call_result(\n                self.session_id, call_id, result.content, not result.is_error\n            )\n\n        return result\n\n    def _compute_idempotency_key(self, name: str, args: dict) -> str:\n        import hashlib\n        payload = f\"{self.session_id}:{name}:{json.dumps(args, sort_keys=True)}\"\n        return hashlib.sha256(payload.encode()).hexdigest()\n",[151,2709,2710,2715,2719,2727,2736,2741,2760,2777,2781,2829,2834,2838,2859,2903,2907,2916,2945,2969,2973,2978,3005,3021,3029,3041,3078,3106,3111,3115,3120,3138,3164,3169,3173,3179,3202,3231,3244,3254,3265,3315,3326,3330,3334,3358,3375,3407,3411,3415,3423,3427,3462,3470,3530],{"__ignoreMap":347},[351,2711,2712],{"class":353,"line":354},[351,2713,2714],{"class":357},"# src\u002Fharness\u002Ftools\u002Fregistry.py (checkpoint-aware addition)\n",[351,2716,2717],{"class":353,"line":361},[351,2718,383],{"emptyLinePlaceholder":382},[351,2720,2721,2724],{"class":353,"line":379},[351,2722,2723],{"class":946},"@",[351,2725,2726],{"class":950},"dataclass\n",[351,2728,2729,2731,2734],{"class":353,"line":386},[351,2730,700],{"class":699},[351,2732,2733],{"class":703}," ToolRegistry",[351,2735,707],{"class":450},[351,2737,2738],{"class":353,"line":395},[351,2739,2740],{"class":357},"    # ... existing fields\n",[351,2742,2743,2746,2748,2751,2754,2756,2758],{"class":353,"line":403},[351,2744,2745],{"class":375},"    checkpointer",[351,2747,733],{"class":450},[351,2749,2750],{"class":490}," \"",[351,2752,2753],{"class":497},"Checkpointer | None",[351,2755,1528],{"class":490},[351,2757,487],{"class":486},[351,2759,2326],{"class":752},[351,2761,2762,2765,2767,2769,2771,2773,2775],{"class":353,"line":411},[351,2763,2764],{"class":375},"    session_id",[351,2766,733],{"class":450},[351,2768,737],{"class":736},[351,2770,740],{"class":486},[351,2772,753],{"class":752},[351,2774,487],{"class":486},[351,2776,2326],{"class":752},[351,2778,2779],{"class":353,"line":424},[351,2780,383],{"emptyLinePlaceholder":382},[351,2782,2783,2785,2787,2790,2792,2794,2796,2799,2801,2803,2805,2808,2810,2812,2814,2816,2818,2820,2822,2824,2827],{"class":353,"line":437},[351,2784,1049],{"class":699},[351,2786,1052],{"class":699},[351,2788,2789],{"class":950}," adispatch",[351,2791,720],{"class":450},[351,2793,724],{"class":723},[351,2795,451],{"class":450},[351,2797,2798],{"class":729}," name",[351,2800,733],{"class":450},[351,2802,737],{"class":736},[351,2804,451],{"class":450},[351,2806,2807],{"class":729}," args",[351,2809,733],{"class":450},[351,2811,1399],{"class":736},[351,2813,451],{"class":450},[351,2815,1763],{"class":729},[351,2817,733],{"class":450},[351,2819,737],{"class":736},[351,2821,746],{"class":450},[351,2823,749],{"class":450},[351,2825,2826],{"class":375}," ToolResult",[351,2828,707],{"class":450},[351,2830,2831],{"class":353,"line":457},[351,2832,2833],{"class":357},"        # ... existing validation, permission, loop checks\n",[351,2835,2836],{"class":353,"line":470},[351,2837,383],{"emptyLinePlaceholder":382},[351,2839,2840,2843,2845,2847,2849,2852,2854,2857],{"class":353,"line":475},[351,2841,2842],{"class":375},"        tool ",[351,2844,809],{"class":486},[351,2846,983],{"class":368},[351,2848,764],{"class":450},[351,2850,2851],{"class":767},"tools",[351,2853,1382],{"class":450},[351,2855,2856],{"class":767},"name",[351,2858,1568],{"class":450},[351,2860,2861,2864,2866,2868,2871,2873,2876,2879,2881,2884,2887,2889,2892,2894,2896,2898,2900],{"class":353,"line":480},[351,2862,2863],{"class":375},"        is_mutating ",[351,2865,809],{"class":486},[351,2867,2750],{"class":490},[351,2869,2870],{"class":497},"mutate",[351,2872,1528],{"class":490},[351,2874,2875],{"class":486}," in",[351,2877,2878],{"class":375}," tool",[351,2880,764],{"class":450},[351,2882,2883],{"class":767},"side_effects",[351,2885,2886],{"class":486}," or",[351,2888,2750],{"class":490},[351,2890,2891],{"class":497},"write",[351,2893,1528],{"class":490},[351,2895,2875],{"class":486},[351,2897,2878],{"class":375},[351,2899,764],{"class":450},[351,2901,2902],{"class":767},"side_effects\n",[351,2904,2905],{"class":353,"line":494},[351,2906,383],{"emptyLinePlaceholder":382},[351,2908,2909,2912,2914],{"class":353,"line":501},[351,2910,2911],{"class":375},"        idempotency_key ",[351,2913,809],{"class":486},[351,2915,2326],{"class":752},[351,2917,2918,2921,2924,2927,2929,2931,2934,2937,2939,2941,2943],{"class":353,"line":507},[351,2919,2920],{"class":364},"        if",[351,2922,2923],{"class":375}," is_mutating ",[351,2925,2926],{"class":486},"and",[351,2928,983],{"class":368},[351,2930,764],{"class":450},[351,2932,2933],{"class":767},"checkpointer",[351,2935,2936],{"class":486}," and",[351,2938,983],{"class":368},[351,2940,764],{"class":450},[351,2942,1284],{"class":767},[351,2944,707],{"class":450},[351,2946,2947,2950,2952,2954,2956,2959,2961,2963,2965,2967],{"class":353,"line":513},[351,2948,2949],{"class":375},"            idempotency_key ",[351,2951,809],{"class":486},[351,2953,983],{"class":368},[351,2955,764],{"class":450},[351,2957,2958],{"class":773},"_compute_idempotency_key",[351,2960,720],{"class":450},[351,2962,2856],{"class":773},[351,2964,451],{"class":450},[351,2966,2807],{"class":773},[351,2968,781],{"class":450},[351,2970,2971],{"class":353,"line":519},[351,2972,383],{"emptyLinePlaceholder":382},[351,2974,2975],{"class":353,"line":525},[351,2976,2977],{"class":357},"            # Check for a prior completion with the same key.\n",[351,2979,2980,2983,2985,2988,2990,2992,2994,2996,2999,3001,3003],{"class":353,"line":531},[351,2981,2982],{"class":375},"            prior ",[351,2984,809],{"class":486},[351,2986,2987],{"class":364}," await",[351,2989,983],{"class":368},[351,2991,764],{"class":450},[351,2993,2933],{"class":767},[351,2995,764],{"class":450},[351,2997,2998],{"class":773},"find_completed_call",[351,3000,720],{"class":450},[351,3002,2295],{"class":773},[351,3004,781],{"class":450},[351,3006,3007,3009,3012,3014,3017,3019],{"class":353,"line":536},[351,3008,2308],{"class":364},[351,3010,3011],{"class":375}," prior ",[351,3013,2314],{"class":486},[351,3015,3016],{"class":486}," not",[351,3018,753],{"class":752},[351,3020,707],{"class":450},[351,3022,3023,3025,3027],{"class":353,"line":542},[351,3024,1725],{"class":364},[351,3026,2826],{"class":773},[351,3028,1356],{"class":450},[351,3030,3031,3034,3036,3039],{"class":353,"line":548},[351,3032,3033],{"class":805},"                    call_id",[351,3035,809],{"class":486},[351,3037,3038],{"class":773},"call_id",[351,3040,1071],{"class":450},[351,3042,3043,3046,3048,3050,3053,3056,3059,3062,3064,3066,3068,3070,3072,3075],{"class":353,"line":554},[351,3044,3045],{"class":805},"                    content",[351,3047,809],{"class":486},[351,3049,720],{"class":450},[351,3051,3052],{"class":699},"f",[351,3054,3055],{"class":497},"\"[idempotent replay of ",[351,3057,3058],{"class":1564},"{",[351,3060,3061],{"class":773},"prior",[351,3063,1382],{"class":450},[351,3065,2132],{"class":490},[351,3067,3038],{"class":497},[351,3069,2132],{"class":490},[351,3071,2573],{"class":450},[351,3073,3074],{"class":1564},"}",[351,3076,3077],{"class":497},"]: \"\n",[351,3079,3080,3083,3085,3087,3089,3091,3093,3096,3098,3100,3102,3104],{"class":353,"line":559},[351,3081,3082],{"class":699},"                             f",[351,3084,1528],{"class":497},[351,3086,3058],{"class":1564},[351,3088,3061],{"class":773},[351,3090,1382],{"class":450},[351,3092,2132],{"class":490},[351,3094,3095],{"class":497},"result_text",[351,3097,2132],{"class":490},[351,3099,2573],{"class":450},[351,3101,3074],{"class":1564},[351,3103,1528],{"class":497},[351,3105,1646],{"class":450},[351,3107,3108],{"class":353,"line":565},[351,3109,3110],{"class":450},"                )\n",[351,3112,3113],{"class":353,"line":571},[351,3114,383],{"emptyLinePlaceholder":382},[351,3116,3117],{"class":353,"line":577},[351,3118,3119],{"class":357},"            # Record intent to execute BEFORE executing.\n",[351,3121,3122,3125,3127,3129,3131,3133,3136],{"class":353,"line":583},[351,3123,3124],{"class":364},"            await",[351,3126,983],{"class":368},[351,3128,764],{"class":450},[351,3130,2933],{"class":767},[351,3132,764],{"class":450},[351,3134,3135],{"class":773},"record_tool_call_issued",[351,3137,1356],{"class":450},[351,3139,3140,3143,3145,3147,3149,3151,3153,3155,3157,3159,3161],{"class":353,"line":588},[351,3141,3142],{"class":368},"                self",[351,3144,764],{"class":450},[351,3146,1284],{"class":767},[351,3148,451],{"class":450},[351,3150,1763],{"class":773},[351,3152,451],{"class":450},[351,3154,2798],{"class":773},[351,3156,451],{"class":450},[351,3158,2807],{"class":773},[351,3160,451],{"class":450},[351,3162,3163],{"class":773}," idempotency_key\n",[351,3165,3166],{"class":353,"line":593},[351,3167,3168],{"class":450},"            )\n",[351,3170,3171],{"class":353,"line":599},[351,3172,383],{"emptyLinePlaceholder":382},[351,3174,3175,3177],{"class":353,"line":604},[351,3176,972],{"class":364},[351,3178,707],{"class":450},[351,3180,3181,3184,3186,3188,3190,3193,3195,3198,3200],{"class":353,"line":610},[351,3182,3183],{"class":375},"            content ",[351,3185,809],{"class":486},[351,3187,2878],{"class":375},[351,3189,764],{"class":450},[351,3191,3192],{"class":773},"run",[351,3194,720],{"class":450},[351,3196,3197],{"class":486},"**",[351,3199,1941],{"class":773},[351,3201,781],{"class":450},[351,3203,3204,3207,3209,3211,3213,3215,3217,3219,3221,3224,3226,3229],{"class":353,"line":616},[351,3205,3206],{"class":375},"            result ",[351,3208,809],{"class":486},[351,3210,2826],{"class":773},[351,3212,720],{"class":450},[351,3214,3038],{"class":805},[351,3216,809],{"class":486},[351,3218,3038],{"class":773},[351,3220,451],{"class":450},[351,3222,3223],{"class":805}," content",[351,3225,809],{"class":486},[351,3227,3228],{"class":773},"content",[351,3230,781],{"class":450},[351,3232,3233,3235,3237,3239,3242],{"class":353,"line":622},[351,3234,1011],{"class":364},[351,3236,1014],{"class":736},[351,3238,1226],{"class":364},[351,3240,3241],{"class":375}," e",[351,3243,707],{"class":450},[351,3245,3246,3248,3250,3252],{"class":353,"line":628},[351,3247,3206],{"class":375},[351,3249,809],{"class":486},[351,3251,2826],{"class":773},[351,3253,1356],{"class":450},[351,3255,3256,3259,3261,3263],{"class":353,"line":634},[351,3257,3258],{"class":805},"                call_id",[351,3260,809],{"class":486},[351,3262,3038],{"class":773},[351,3264,1071],{"class":450},[351,3266,3267,3270,3272,3274,3276,3278,3280,3282,3285,3287,3290,3292,3295,3297,3300,3302,3305,3307,3309,3311,3313],{"class":353,"line":640},[351,3268,3269],{"class":805},"                content",[351,3271,809],{"class":486},[351,3273,3052],{"class":699},[351,3275,1528],{"class":497},[351,3277,3058],{"class":1564},[351,3279,2856],{"class":773},[351,3281,3074],{"class":1564},[351,3283,3284],{"class":497}," raised ",[351,3286,3058],{"class":1564},[351,3288,3289],{"class":736},"type",[351,3291,720],{"class":450},[351,3293,3294],{"class":773},"e",[351,3296,1202],{"class":450},[351,3298,3299],{"class":368},"__name__",[351,3301,3074],{"class":1564},[351,3303,3304],{"class":497},": ",[351,3306,3058],{"class":1564},[351,3308,3294],{"class":773},[351,3310,3074],{"class":1564},[351,3312,1528],{"class":497},[351,3314,1071],{"class":450},[351,3316,3317,3320,3322,3324],{"class":353,"line":646},[351,3318,3319],{"class":805},"                is_error",[351,3321,809],{"class":486},[351,3323,812],{"class":752},[351,3325,1071],{"class":450},[351,3327,3328],{"class":353,"line":652},[351,3329,3168],{"class":450},[351,3331,3332],{"class":353,"line":658},[351,3333,383],{"emptyLinePlaceholder":382},[351,3335,3336,3338,3340,3342,3344,3346,3348,3350,3352,3354,3356],{"class":353,"line":663},[351,3337,2920],{"class":364},[351,3339,2923],{"class":375},[351,3341,2926],{"class":486},[351,3343,983],{"class":368},[351,3345,764],{"class":450},[351,3347,2933],{"class":767},[351,3349,2936],{"class":486},[351,3351,983],{"class":368},[351,3353,764],{"class":450},[351,3355,1284],{"class":767},[351,3357,707],{"class":450},[351,3359,3360,3362,3364,3366,3368,3370,3373],{"class":353,"line":668},[351,3361,3124],{"class":364},[351,3363,983],{"class":368},[351,3365,764],{"class":450},[351,3367,2933],{"class":767},[351,3369,764],{"class":450},[351,3371,3372],{"class":773},"record_tool_call_result",[351,3374,1356],{"class":450},[351,3376,3377,3379,3381,3383,3385,3387,3389,3392,3394,3396,3398,3400,3402,3404],{"class":353,"line":674},[351,3378,3142],{"class":368},[351,3380,764],{"class":450},[351,3382,1284],{"class":767},[351,3384,451],{"class":450},[351,3386,1763],{"class":773},[351,3388,451],{"class":450},[351,3390,3391],{"class":773}," result",[351,3393,764],{"class":450},[351,3395,3228],{"class":767},[351,3397,451],{"class":450},[351,3399,3016],{"class":364},[351,3401,3391],{"class":773},[351,3403,764],{"class":450},[351,3405,3406],{"class":767},"is_error\n",[351,3408,3409],{"class":353,"line":680},[351,3410,3168],{"class":450},[351,3412,3413],{"class":353,"line":686},[351,3414,383],{"emptyLinePlaceholder":382},[351,3416,3417,3420],{"class":353,"line":691},[351,3418,3419],{"class":364},"        return",[351,3421,3422],{"class":375}," result\n",[351,3424,3425],{"class":353,"line":696},[351,3426,383],{"emptyLinePlaceholder":382},[351,3428,3429,3431,3434,3436,3438,3440,3442,3444,3446,3448,3450,3452,3454,3456,3458,3460],{"class":353,"line":710},[351,3430,713],{"class":699},[351,3432,3433],{"class":950}," _compute_idempotency_key",[351,3435,720],{"class":450},[351,3437,724],{"class":723},[351,3439,451],{"class":450},[351,3441,2798],{"class":729},[351,3443,733],{"class":450},[351,3445,737],{"class":736},[351,3447,451],{"class":450},[351,3449,2807],{"class":729},[351,3451,733],{"class":450},[351,3453,1399],{"class":736},[351,3455,746],{"class":450},[351,3457,749],{"class":450},[351,3459,737],{"class":736},[351,3461,707],{"class":450},[351,3463,3464,3467],{"class":353,"line":758},[351,3465,3466],{"class":364},"        import",[351,3468,3469],{"class":375}," hashlib\n",[351,3471,3472,3475,3477,3480,3482,3484,3486,3488,3490,3492,3494,3496,3498,3500,3502,3504,3507,3509,3511,3513,3515,3517,3520,3522,3524,3526,3528],{"class":353,"line":784},[351,3473,3474],{"class":375},"        payload ",[351,3476,809],{"class":486},[351,3478,3479],{"class":699}," f",[351,3481,1528],{"class":497},[351,3483,3058],{"class":1564},[351,3485,724],{"class":368},[351,3487,764],{"class":450},[351,3489,1284],{"class":767},[351,3491,3074],{"class":1564},[351,3493,733],{"class":497},[351,3495,3058],{"class":1564},[351,3497,2856],{"class":375},[351,3499,3074],{"class":1564},[351,3501,733],{"class":497},[351,3503,3058],{"class":1564},[351,3505,3506],{"class":375},"json",[351,3508,764],{"class":450},[351,3510,1638],{"class":773},[351,3512,720],{"class":450},[351,3514,1941],{"class":773},[351,3516,451],{"class":450},[351,3518,3519],{"class":805}," sort_keys",[351,3521,809],{"class":486},[351,3523,812],{"class":752},[351,3525,746],{"class":450},[351,3527,3074],{"class":1564},[351,3529,1517],{"class":497},[351,3531,3532,3534,3537,3539,3542,3544,3547,3549,3552,3555,3558],{"class":353,"line":826},[351,3533,3419],{"class":364},[351,3535,3536],{"class":375}," hashlib",[351,3538,764],{"class":450},[351,3540,3541],{"class":773},"sha256",[351,3543,720],{"class":450},[351,3545,3546],{"class":773},"payload",[351,3548,764],{"class":450},[351,3550,3551],{"class":773},"encode",[351,3553,3554],{"class":450},"()).",[351,3556,3557],{"class":773},"hexdigest",[351,3559,935],{"class":450},[112,3561,3562,3565,3566,3569,3570,3573],{},[124,3563,3564],{},"The key construction matters."," Our default idempotency key is a hash of ",[151,3567,3568],{},"session_id + tool_name + args",". Within a session, calling ",[151,3571,3572],{},"write_file(\u002Ftmp\u002Fplan.txt, \"v1\")"," twice is treated as one operation (the second call returns the cached result). Different sessions aren't deduped against each other — two sessions writing to the same file legitimately can both run.",[112,3575,3576],{},"This is conservative. A stricter key would not include session — \"email with this subject and body, ever, is one operation.\" For email-like tools where the real idempotency is vendor-side, pass the vendor's idempotency key as part of args so it's in the hash.",[112,3578,3579,3582],{},[124,3580,3581],{},"Verify before retry"," is what happens next session on interrupted calls:",[342,3584,3586],{"className":344,"code":3585,"language":346,"meta":347,"style":347},"# src\u002Fharness\u002Fcheckpoint\u002Fresume.py\n\nasync def check_pending_tool_calls(\n    checkpointer: Checkpointer, session_id: str\n) -> list[dict]:\n    \"\"\"Return tool calls that were issued but not completed — needs verification.\"\"\"\n    async with checkpointer._lock:\n        rows = checkpointer._conn.execute(\"\"\"\n            SELECT call_id, tool_name, args_json, started_at\n            FROM tool_calls\n            WHERE session_id = ? AND status = 'issued'\n        \"\"\", (session_id,)).fetchall()\n    return [dict(r) for r in rows]\n",[151,3587,3588,3593,3597,3609,3626,3641,3651,3666,3687,3692,3697,3702,3719],{"__ignoreMap":347},[351,3589,3590],{"class":353,"line":354},[351,3591,3592],{"class":357},"# src\u002Fharness\u002Fcheckpoint\u002Fresume.py\n",[351,3594,3595],{"class":353,"line":361},[351,3596,383],{"emptyLinePlaceholder":382},[351,3598,3599,3602,3604,3607],{"class":353,"line":379},[351,3600,3601],{"class":699},"async",[351,3603,1052],{"class":699},[351,3605,3606],{"class":950}," check_pending_tool_calls",[351,3608,1356],{"class":450},[351,3610,3611,3613,3615,3617,3619,3621,3623],{"class":353,"line":386},[351,3612,2745],{"class":729},[351,3614,733],{"class":450},[351,3616,704],{"class":375},[351,3618,451],{"class":450},[351,3620,1064],{"class":729},[351,3622,733],{"class":450},[351,3624,3625],{"class":736}," str\n",[351,3627,3628,3630,3632,3634,3636,3638],{"class":353,"line":395},[351,3629,746],{"class":450},[351,3631,749],{"class":450},[351,3633,1379],{"class":375},[351,3635,1382],{"class":450},[351,3637,1385],{"class":736},[351,3639,3640],{"class":450},"]:\n",[351,3642,3643,3646,3649],{"class":353,"line":403},[351,3644,3645],{"class":1117},"    \"\"\"",[351,3647,3648],{"class":1121},"Return tool calls that were issued but not completed — needs verification.",[351,3650,683],{"class":1117},[351,3652,3653,3655,3657,3660,3662,3664],{"class":353,"line":411},[351,3654,1049],{"class":364},[351,3656,1166],{"class":364},[351,3658,3659],{"class":375}," checkpointer",[351,3661,764],{"class":450},[351,3663,922],{"class":767},[351,3665,707],{"class":450},[351,3667,3668,3671,3673,3675,3677,3679,3681,3683,3685],{"class":353,"line":424},[351,3669,3670],{"class":375},"        rows ",[351,3672,809],{"class":486},[351,3674,3659],{"class":375},[351,3676,764],{"class":450},[351,3678,833],{"class":767},[351,3680,764],{"class":450},[351,3682,1248],{"class":773},[351,3684,720],{"class":450},[351,3686,683],{"class":490},[351,3688,3689],{"class":353,"line":437},[351,3690,3691],{"class":497},"            SELECT call_id, tool_name, args_json, started_at\n",[351,3693,3694],{"class":353,"line":457},[351,3695,3696],{"class":497},"            FROM tool_calls\n",[351,3698,3699],{"class":353,"line":470},[351,3700,3701],{"class":497},"            WHERE session_id = ? AND status = 'issued'\n",[351,3703,3704,3706,3708,3710,3712,3714,3717],{"class":353,"line":475},[351,3705,1118],{"class":490},[351,3707,451],{"class":450},[351,3709,1281],{"class":450},[351,3711,1284],{"class":773},[351,3713,2298],{"class":450},[351,3715,3716],{"class":773},"fetchall",[351,3718,935],{"class":450},[351,3720,3721,3724,3727,3729,3731,3734,3736,3739,3742,3745,3748],{"class":353,"line":480},[351,3722,3723],{"class":364},"    return",[351,3725,3726],{"class":450}," [",[351,3728,1385],{"class":736},[351,3730,720],{"class":450},[351,3732,3733],{"class":773},"r",[351,3735,746],{"class":450},[351,3737,3738],{"class":364}," for",[351,3740,3741],{"class":375}," r ",[351,3743,3744],{"class":364},"in",[351,3746,3747],{"class":375}," rows",[351,3749,1568],{"class":450},[112,3751,3752,3753,3756],{},"On session resume, ",[151,3754,3755],{},"check_pending_tool_calls"," returns the calls where we don't know the outcome. The caller — the harness's startup logic — handles them based on the tool's side effects:",[3758,3759,3760,3766],"ul",{},[294,3761,3762,3765],{},[124,3763,3764],{},"Read-only tool."," Safe to discard (it didn't mutate anything); the agent will re-run if needed.",[294,3767,3768,3771,3772,3775],{},[124,3769,3770],{},"Write or mutate."," The harness either: (a) marks the call as failed in the log and lets the agent decide what to do, (b) calls a tool-provided ",[151,3773,3774],{},"verify"," hook that checks whether the side effect landed, or (c) surfaces to the user for manual resolution.",[112,3777,3778,3779,3781,3782,3785],{},"Option (b) is the best when tools support it. Add an optional ",[151,3780,3774],{}," callable to the ",[151,3783,3784],{},"Tool"," dataclass; on resume, the harness calls it and records the result.",[280,3787],{},[283,3789,3791],{"id":3790},"_214-checkpointing-in-the-loop","21.4 Checkpointing in the Loop",[112,3793,3794],{},"The loop saves a checkpoint after each completed turn:",[342,3796,3798],{"className":344,"code":3797,"language":346,"meta":347,"style":347},"# src\u002Fharness\u002Fagent.py (checkpoint-aware sketch)\n\nasync def arun(\n    # ... existing parameters\n    checkpointer: \"Checkpointer | None\" = None,\n    session_id: str | None = None,\n) -> str:\n    # ... setup\n\n    if checkpointer and session_id:\n        await checkpointer.start_session(session_id, user_message, system)\n\n    for iteration in range(MAX_ITERATIONS):\n        # ... turn execution\n\n        if checkpointer and session_id:\n            await checkpointer.save_checkpoint(\n                session_id=session_id,\n                transcript=_serialize_transcript(transcript),\n                plan=_serialize_plan(plan_holder.plan) if plan_holder else None,\n                budget_spent_usd=budget_enforcer.spent_usd if budget_enforcer else 0.0,\n            )\n",[151,3799,3800,3805,3809,3820,3825,3843,3861,3871,3876,3880,3894,3922,3926,3946,3951,3955,3967,3979,3990,4006,4038,4065],{"__ignoreMap":347},[351,3801,3802],{"class":353,"line":354},[351,3803,3804],{"class":357},"# src\u002Fharness\u002Fagent.py (checkpoint-aware sketch)\n",[351,3806,3807],{"class":353,"line":361},[351,3808,383],{"emptyLinePlaceholder":382},[351,3810,3811,3813,3815,3818],{"class":353,"line":379},[351,3812,3601],{"class":699},[351,3814,1052],{"class":699},[351,3816,3817],{"class":950}," arun",[351,3819,1356],{"class":450},[351,3821,3822],{"class":353,"line":386},[351,3823,3824],{"class":357},"    # ... existing parameters\n",[351,3826,3827,3829,3831,3833,3835,3837,3839,3841],{"class":353,"line":395},[351,3828,2745],{"class":729},[351,3830,733],{"class":450},[351,3832,2750],{"class":490},[351,3834,2753],{"class":497},[351,3836,1528],{"class":490},[351,3838,487],{"class":486},[351,3840,753],{"class":752},[351,3842,1071],{"class":450},[351,3844,3845,3847,3849,3851,3853,3855,3857,3859],{"class":353,"line":403},[351,3846,2764],{"class":729},[351,3848,733],{"class":450},[351,3850,737],{"class":736},[351,3852,740],{"class":486},[351,3854,753],{"class":752},[351,3856,487],{"class":486},[351,3858,753],{"class":752},[351,3860,1071],{"class":450},[351,3862,3863,3865,3867,3869],{"class":353,"line":411},[351,3864,746],{"class":450},[351,3866,749],{"class":450},[351,3868,737],{"class":736},[351,3870,707],{"class":450},[351,3872,3873],{"class":353,"line":424},[351,3874,3875],{"class":357},"    # ... setup\n",[351,3877,3878],{"class":353,"line":437},[351,3879,383],{"emptyLinePlaceholder":382},[351,3881,3882,3885,3888,3890,3892],{"class":353,"line":457},[351,3883,3884],{"class":364},"    if",[351,3886,3887],{"class":375}," checkpointer ",[351,3889,2926],{"class":486},[351,3891,1064],{"class":375},[351,3893,707],{"class":450},[351,3895,3896,3899,3901,3903,3906,3908,3910,3912,3915,3917,3920],{"class":353,"line":470},[351,3897,3898],{"class":364},"        await",[351,3900,3659],{"class":375},[351,3902,764],{"class":450},[351,3904,3905],{"class":773},"start_session",[351,3907,720],{"class":450},[351,3909,1284],{"class":773},[351,3911,451],{"class":450},[351,3913,3914],{"class":773}," user_message",[351,3916,451],{"class":450},[351,3918,3919],{"class":773}," system",[351,3921,781],{"class":450},[351,3923,3924],{"class":353,"line":475},[351,3925,383],{"emptyLinePlaceholder":382},[351,3927,3928,3931,3934,3936,3939,3941,3944],{"class":353,"line":480},[351,3929,3930],{"class":364},"    for",[351,3932,3933],{"class":375}," iteration ",[351,3935,3744],{"class":364},[351,3937,3938],{"class":716}," range",[351,3940,720],{"class":450},[351,3942,3943],{"class":716},"MAX_ITERATIONS",[351,3945,966],{"class":450},[351,3947,3948],{"class":353,"line":494},[351,3949,3950],{"class":357},"        # ... turn execution\n",[351,3952,3953],{"class":353,"line":501},[351,3954,383],{"emptyLinePlaceholder":382},[351,3956,3957,3959,3961,3963,3965],{"class":353,"line":507},[351,3958,2920],{"class":364},[351,3960,3887],{"class":375},[351,3962,2926],{"class":486},[351,3964,1064],{"class":375},[351,3966,707],{"class":450},[351,3968,3969,3971,3973,3975,3977],{"class":353,"line":513},[351,3970,3124],{"class":364},[351,3972,3659],{"class":375},[351,3974,764],{"class":450},[351,3976,2646],{"class":773},[351,3978,1356],{"class":450},[351,3980,3981,3984,3986,3988],{"class":353,"line":519},[351,3982,3983],{"class":805},"                session_id",[351,3985,809],{"class":486},[351,3987,1284],{"class":773},[351,3989,1071],{"class":450},[351,3991,3992,3995,3997,4000,4002,4004],{"class":353,"line":525},[351,3993,3994],{"class":805},"                transcript",[351,3996,809],{"class":486},[351,3998,3999],{"class":773},"_serialize_transcript",[351,4001,720],{"class":450},[351,4003,1643],{"class":773},[351,4005,1646],{"class":450},[351,4007,4008,4011,4013,4016,4018,4021,4023,4025,4027,4029,4032,4034,4036],{"class":353,"line":531},[351,4009,4010],{"class":805},"                plan",[351,4012,809],{"class":486},[351,4014,4015],{"class":773},"_serialize_plan",[351,4017,720],{"class":450},[351,4019,4020],{"class":773},"plan_holder",[351,4022,764],{"class":450},[351,4024,1660],{"class":767},[351,4026,746],{"class":450},[351,4028,1665],{"class":364},[351,4030,4031],{"class":773}," plan_holder ",[351,4033,1671],{"class":364},[351,4035,753],{"class":752},[351,4037,1071],{"class":450},[351,4039,4040,4043,4045,4048,4050,4053,4055,4058,4060,4063],{"class":353,"line":536},[351,4041,4042],{"class":805},"                budget_spent_usd",[351,4044,809],{"class":486},[351,4046,4047],{"class":773},"budget_enforcer",[351,4049,764],{"class":450},[351,4051,4052],{"class":767},"spent_usd",[351,4054,1665],{"class":364},[351,4056,4057],{"class":773}," budget_enforcer ",[351,4059,1671],{"class":364},[351,4061,4062],{"class":1564}," 0.0",[351,4064,1071],{"class":450},[351,4066,4067],{"class":353,"line":542},[351,4068,3168],{"class":450},[112,4070,4071,4072,4075,4076,4079],{},"The ",[151,4073,4074],{},"_serialize_*"," helpers are one side of a round-trip; the resume path in §21.4 needs the inverse. Both helpers live in a small module so ",[151,4077,4078],{},"arun"," (write side) and the example script (read side) share them:",[342,4081,4083],{"className":344,"code":4082,"language":346,"meta":347,"style":347},"# src\u002Fharness\u002Fcheckpoint\u002Fserde.py\nfrom __future__ import annotations\n\nfrom dataclasses import asdict\nfrom datetime import datetime\nfrom typing import Any\n\nfrom ..messages import (\n    Message, Transcript,\n    TextBlock, ReasoningBlock, ToolCall, ToolResult,\n)\nfrom ..plans.tools import Plan  # §16.2's Plan dataclass\n\n\ndef _serialize_transcript(transcript: Transcript) -> list[dict]:\n    out: list[dict] = []\n    for msg in transcript.messages:\n        out.append({\n            \"id\": msg.id,\n            \"role\": msg.role,\n            \"created_at\": msg.created_at.isoformat(),\n            \"blocks\": [asdict(b) for b in msg.blocks],\n        })\n    return out\n\n\ndef _deserialize_transcript(data: list[dict]) -> Transcript:\n    messages: list[Message] = []\n    for m in data:\n        blocks = [_deserialize_block(b) for b in m[\"blocks\"]]\n        messages.append(Message(\n            id=m[\"id\"],\n            role=m[\"role\"],\n            created_at=datetime.fromisoformat(m[\"created_at\"]),\n            blocks=blocks,\n        ))\n    return Transcript(messages=messages)\n\n\ndef _deserialize_block(d: dict):\n    \"\"\"Dispatch on the `kind` discriminator from §3.2's block dataclasses.\"\"\"\n    kind = d[\"kind\"]\n    if kind == \"text\":\n        return TextBlock(text=d[\"text\"])\n    if kind == \"reasoning\":\n        return ReasoningBlock(text=d[\"text\"], metadata=d.get(\"metadata\", {}))\n    if kind == \"tool_call\":\n        return ToolCall(id=d[\"id\"], name=d[\"name\"], args=d[\"args\"])\n    if kind == \"tool_result\":\n        return ToolResult(\n            call_id=d[\"call_id\"],\n            content=d[\"content\"],\n            is_error=d.get(\"is_error\", False),\n        )\n    raise ValueError(f\"unknown block kind: {kind!r}\")\n\n\ndef _serialize_plan(plan: Plan | None) -> dict | None:\n    return asdict(plan) if plan is not None else None\n\n\ndef _deserialize_plan(data: dict | None) -> Plan | None:\n    return Plan(**data) if data is not None else None\n",[151,4084,4085,4090,4100,4104,4115,4126,4138,4142,4157,4169,4190,4194,4216,4220,4224,4252,4272,4290,4303,4324,4343,4367,4405,4410,4417,4421,4425,4453,4473,4487,4525,4540,4560,4579,4608,4619,4624,4640,4644,4648,4666,4675,4696,4715,4741,4758,4809,4826,4882,4899,4907,4926,4945,4974,4979,5007,5011,5015,5048,5075,5079,5083,5114],{"__ignoreMap":347},[351,4086,4087],{"class":353,"line":354},[351,4088,4089],{"class":357},"# src\u002Fharness\u002Fcheckpoint\u002Fserde.py\n",[351,4091,4092,4094,4096,4098],{"class":353,"line":361},[351,4093,365],{"class":364},[351,4095,369],{"class":368},[351,4097,372],{"class":364},[351,4099,376],{"class":375},[351,4101,4102],{"class":353,"line":379},[351,4103,383],{"emptyLinePlaceholder":382},[351,4105,4106,4108,4110,4112],{"class":353,"line":386},[351,4107,365],{"class":364},[351,4109,429],{"class":375},[351,4111,389],{"class":364},[351,4113,4114],{"class":375}," asdict\n",[351,4116,4117,4119,4121,4123],{"class":353,"line":395},[351,4118,365],{"class":364},[351,4120,442],{"class":375},[351,4122,389],{"class":364},[351,4124,4125],{"class":375}," datetime\n",[351,4127,4128,4130,4133,4135],{"class":353,"line":403},[351,4129,365],{"class":364},[351,4131,4132],{"class":375}," typing ",[351,4134,389],{"class":364},[351,4136,4137],{"class":375}," Any\n",[351,4139,4140],{"class":353,"line":411},[351,4141,383],{"emptyLinePlaceholder":382},[351,4143,4144,4146,4149,4152,4154],{"class":353,"line":424},[351,4145,365],{"class":364},[351,4147,4148],{"class":450}," ..",[351,4150,4151],{"class":375},"messages ",[351,4153,389],{"class":364},[351,4155,4156],{"class":450}," (\n",[351,4158,4159,4162,4164,4167],{"class":353,"line":437},[351,4160,4161],{"class":375},"    Message",[351,4163,451],{"class":450},[351,4165,4166],{"class":375}," Transcript",[351,4168,1071],{"class":450},[351,4170,4171,4174,4176,4179,4181,4184,4186,4188],{"class":353,"line":457},[351,4172,4173],{"class":375},"    TextBlock",[351,4175,451],{"class":450},[351,4177,4178],{"class":375}," ReasoningBlock",[351,4180,451],{"class":450},[351,4182,4183],{"class":375}," ToolCall",[351,4185,451],{"class":450},[351,4187,2826],{"class":375},[351,4189,1071],{"class":450},[351,4191,4192],{"class":353,"line":470},[351,4193,781],{"class":450},[351,4195,4196,4198,4200,4203,4205,4208,4210,4213],{"class":353,"line":475},[351,4197,365],{"class":364},[351,4199,4148],{"class":450},[351,4201,4202],{"class":375},"plans",[351,4204,764],{"class":450},[351,4206,4207],{"class":375},"tools ",[351,4209,389],{"class":364},[351,4211,4212],{"class":375}," Plan  ",[351,4214,4215],{"class":357},"# §16.2's Plan dataclass\n",[351,4217,4218],{"class":353,"line":480},[351,4219,383],{"emptyLinePlaceholder":382},[351,4221,4222],{"class":353,"line":494},[351,4223,383],{"emptyLinePlaceholder":382},[351,4225,4226,4229,4232,4234,4236,4238,4240,4242,4244,4246,4248,4250],{"class":353,"line":501},[351,4227,4228],{"class":699},"def",[351,4230,4231],{"class":950}," _serialize_transcript",[351,4233,720],{"class":450},[351,4235,1643],{"class":729},[351,4237,733],{"class":450},[351,4239,4166],{"class":375},[351,4241,746],{"class":450},[351,4243,749],{"class":450},[351,4245,1379],{"class":375},[351,4247,1382],{"class":450},[351,4249,1385],{"class":736},[351,4251,3640],{"class":450},[351,4253,4254,4257,4259,4261,4263,4265,4267,4269],{"class":353,"line":507},[351,4255,4256],{"class":375},"    out",[351,4258,733],{"class":450},[351,4260,1379],{"class":375},[351,4262,1382],{"class":450},[351,4264,1385],{"class":736},[351,4266,2573],{"class":450},[351,4268,487],{"class":486},[351,4270,4271],{"class":450}," []\n",[351,4273,4274,4276,4279,4281,4283,4285,4288],{"class":353,"line":513},[351,4275,3930],{"class":364},[351,4277,4278],{"class":375}," msg ",[351,4280,3744],{"class":364},[351,4282,1374],{"class":375},[351,4284,764],{"class":450},[351,4286,4287],{"class":767},"messages",[351,4289,707],{"class":450},[351,4291,4292,4295,4297,4300],{"class":353,"line":519},[351,4293,4294],{"class":375},"        out",[351,4296,764],{"class":450},[351,4298,4299],{"class":773},"append",[351,4301,4302],{"class":450},"({\n",[351,4304,4305,4308,4311,4313,4315,4318,4320,4322],{"class":353,"line":525},[351,4306,4307],{"class":490},"            \"",[351,4309,4310],{"class":497},"id",[351,4312,1528],{"class":490},[351,4314,733],{"class":450},[351,4316,4317],{"class":773}," msg",[351,4319,764],{"class":450},[351,4321,4310],{"class":767},[351,4323,1071],{"class":450},[351,4325,4326,4328,4331,4333,4335,4337,4339,4341],{"class":353,"line":531},[351,4327,4307],{"class":490},[351,4329,4330],{"class":497},"role",[351,4332,1528],{"class":490},[351,4334,733],{"class":450},[351,4336,4317],{"class":773},[351,4338,764],{"class":450},[351,4340,4330],{"class":767},[351,4342,1071],{"class":450},[351,4344,4345,4347,4350,4352,4354,4356,4358,4360,4362,4364],{"class":353,"line":536},[351,4346,4307],{"class":490},[351,4348,4349],{"class":497},"created_at",[351,4351,1528],{"class":490},[351,4353,733],{"class":450},[351,4355,4317],{"class":773},[351,4357,764],{"class":450},[351,4359,4349],{"class":767},[351,4361,764],{"class":450},[351,4363,1205],{"class":773},[351,4365,4366],{"class":450},"(),\n",[351,4368,4369,4371,4374,4376,4378,4380,4383,4385,4388,4390,4392,4395,4397,4399,4401,4403],{"class":353,"line":542},[351,4370,4307],{"class":490},[351,4372,4373],{"class":497},"blocks",[351,4375,1528],{"class":490},[351,4377,733],{"class":450},[351,4379,3726],{"class":450},[351,4381,4382],{"class":773},"asdict",[351,4384,720],{"class":450},[351,4386,4387],{"class":773},"b",[351,4389,746],{"class":450},[351,4391,3738],{"class":364},[351,4393,4394],{"class":773}," b ",[351,4396,3744],{"class":364},[351,4398,4317],{"class":773},[351,4400,764],{"class":450},[351,4402,4373],{"class":767},[351,4404,1388],{"class":450},[351,4406,4407],{"class":353,"line":548},[351,4408,4409],{"class":450},"        })\n",[351,4411,4412,4414],{"class":353,"line":554},[351,4413,3723],{"class":364},[351,4415,4416],{"class":375}," out\n",[351,4418,4419],{"class":353,"line":559},[351,4420,383],{"emptyLinePlaceholder":382},[351,4422,4423],{"class":353,"line":565},[351,4424,383],{"emptyLinePlaceholder":382},[351,4426,4427,4429,4432,4434,4437,4439,4441,4443,4445,4447,4449,4451],{"class":353,"line":571},[351,4428,4228],{"class":699},[351,4430,4431],{"class":950}," _deserialize_transcript",[351,4433,720],{"class":450},[351,4435,4436],{"class":729},"data",[351,4438,733],{"class":450},[351,4440,1379],{"class":375},[351,4442,1382],{"class":450},[351,4444,1385],{"class":736},[351,4446,2558],{"class":450},[351,4448,749],{"class":450},[351,4450,4166],{"class":375},[351,4452,707],{"class":450},[351,4454,4455,4458,4460,4462,4464,4467,4469,4471],{"class":353,"line":577},[351,4456,4457],{"class":375},"    messages",[351,4459,733],{"class":450},[351,4461,1379],{"class":375},[351,4463,1382],{"class":450},[351,4465,4466],{"class":375},"Message",[351,4468,2573],{"class":450},[351,4470,487],{"class":486},[351,4472,4271],{"class":450},[351,4474,4475,4477,4480,4482,4485],{"class":353,"line":583},[351,4476,3930],{"class":364},[351,4478,4479],{"class":375}," m ",[351,4481,3744],{"class":364},[351,4483,4484],{"class":375}," data",[351,4486,707],{"class":450},[351,4488,4489,4492,4494,4496,4499,4501,4503,4505,4507,4509,4511,4514,4516,4518,4520,4522],{"class":353,"line":588},[351,4490,4491],{"class":375},"        blocks ",[351,4493,809],{"class":486},[351,4495,3726],{"class":450},[351,4497,4498],{"class":773},"_deserialize_block",[351,4500,720],{"class":450},[351,4502,4387],{"class":773},[351,4504,746],{"class":450},[351,4506,3738],{"class":364},[351,4508,4394],{"class":375},[351,4510,3744],{"class":364},[351,4512,4513],{"class":375}," m",[351,4515,1382],{"class":450},[351,4517,1528],{"class":490},[351,4519,4373],{"class":497},[351,4521,1528],{"class":490},[351,4523,4524],{"class":450},"]]\n",[351,4526,4527,4530,4532,4534,4536,4538],{"class":353,"line":593},[351,4528,4529],{"class":375},"        messages",[351,4531,764],{"class":450},[351,4533,4299],{"class":773},[351,4535,720],{"class":450},[351,4537,4466],{"class":773},[351,4539,1356],{"class":450},[351,4541,4542,4545,4547,4550,4552,4554,4556,4558],{"class":353,"line":599},[351,4543,4544],{"class":805},"            id",[351,4546,809],{"class":486},[351,4548,4549],{"class":773},"m",[351,4551,1382],{"class":450},[351,4553,1528],{"class":490},[351,4555,4310],{"class":497},[351,4557,1528],{"class":490},[351,4559,1388],{"class":450},[351,4561,4562,4565,4567,4569,4571,4573,4575,4577],{"class":353,"line":604},[351,4563,4564],{"class":805},"            role",[351,4566,809],{"class":486},[351,4568,4549],{"class":773},[351,4570,1382],{"class":450},[351,4572,1528],{"class":490},[351,4574,4330],{"class":497},[351,4576,1528],{"class":490},[351,4578,1388],{"class":450},[351,4580,4581,4584,4586,4589,4591,4594,4596,4598,4600,4602,4604,4606],{"class":353,"line":610},[351,4582,4583],{"class":805},"            created_at",[351,4585,809],{"class":486},[351,4587,4588],{"class":773},"datetime",[351,4590,764],{"class":450},[351,4592,4593],{"class":773},"fromisoformat",[351,4595,720],{"class":450},[351,4597,4549],{"class":773},[351,4599,1382],{"class":450},[351,4601,1528],{"class":490},[351,4603,4349],{"class":497},[351,4605,1528],{"class":490},[351,4607,2525],{"class":450},[351,4609,4610,4613,4615,4617],{"class":353,"line":616},[351,4611,4612],{"class":805},"            blocks",[351,4614,809],{"class":486},[351,4616,4373],{"class":773},[351,4618,1071],{"class":450},[351,4620,4621],{"class":353,"line":622},[351,4622,4623],{"class":450},"        ))\n",[351,4625,4626,4628,4630,4632,4634,4636,4638],{"class":353,"line":628},[351,4627,3723],{"class":364},[351,4629,4166],{"class":773},[351,4631,720],{"class":450},[351,4633,4287],{"class":805},[351,4635,809],{"class":486},[351,4637,4287],{"class":773},[351,4639,781],{"class":450},[351,4641,4642],{"class":353,"line":634},[351,4643,383],{"emptyLinePlaceholder":382},[351,4645,4646],{"class":353,"line":640},[351,4647,383],{"emptyLinePlaceholder":382},[351,4649,4650,4652,4655,4657,4660,4662,4664],{"class":353,"line":646},[351,4651,4228],{"class":699},[351,4653,4654],{"class":950}," _deserialize_block",[351,4656,720],{"class":450},[351,4658,4659],{"class":729},"d",[351,4661,733],{"class":450},[351,4663,1399],{"class":736},[351,4665,966],{"class":450},[351,4667,4668,4670,4673],{"class":353,"line":652},[351,4669,3645],{"class":1117},[351,4671,4672],{"class":1121},"Dispatch on the `kind` discriminator from §3.2's block dataclasses.",[351,4674,683],{"class":1117},[351,4676,4677,4680,4682,4685,4687,4689,4692,4694],{"class":353,"line":658},[351,4678,4679],{"class":375},"    kind ",[351,4681,809],{"class":486},[351,4683,4684],{"class":375}," d",[351,4686,1382],{"class":450},[351,4688,1528],{"class":490},[351,4690,4691],{"class":497},"kind",[351,4693,1528],{"class":490},[351,4695,1568],{"class":450},[351,4697,4698,4700,4703,4706,4708,4711,4713],{"class":353,"line":663},[351,4699,3884],{"class":364},[351,4701,4702],{"class":375}," kind ",[351,4704,4705],{"class":486},"==",[351,4707,2750],{"class":490},[351,4709,4710],{"class":497},"text",[351,4712,1528],{"class":490},[351,4714,707],{"class":450},[351,4716,4717,4719,4722,4724,4726,4728,4730,4732,4734,4736,4738],{"class":353,"line":668},[351,4718,3419],{"class":364},[351,4720,4721],{"class":773}," TextBlock",[351,4723,720],{"class":450},[351,4725,4710],{"class":805},[351,4727,809],{"class":486},[351,4729,4659],{"class":773},[351,4731,1382],{"class":450},[351,4733,1528],{"class":490},[351,4735,4710],{"class":497},[351,4737,1528],{"class":490},[351,4739,4740],{"class":450},"])\n",[351,4742,4743,4745,4747,4749,4751,4754,4756],{"class":353,"line":674},[351,4744,3884],{"class":364},[351,4746,4702],{"class":375},[351,4748,4705],{"class":486},[351,4750,2750],{"class":490},[351,4752,4753],{"class":497},"reasoning",[351,4755,1528],{"class":490},[351,4757,707],{"class":450},[351,4759,4760,4762,4764,4766,4768,4770,4772,4774,4776,4778,4780,4783,4786,4788,4790,4792,4795,4797,4799,4802,4804,4806],{"class":353,"line":680},[351,4761,3419],{"class":364},[351,4763,4178],{"class":773},[351,4765,720],{"class":450},[351,4767,4710],{"class":805},[351,4769,809],{"class":486},[351,4771,4659],{"class":773},[351,4773,1382],{"class":450},[351,4775,1528],{"class":490},[351,4777,4710],{"class":497},[351,4779,1528],{"class":490},[351,4781,4782],{"class":450},"],",[351,4784,4785],{"class":805}," metadata",[351,4787,809],{"class":486},[351,4789,4659],{"class":773},[351,4791,764],{"class":450},[351,4793,4794],{"class":773},"get",[351,4796,720],{"class":450},[351,4798,1528],{"class":490},[351,4800,4801],{"class":497},"metadata",[351,4803,1528],{"class":490},[351,4805,451],{"class":450},[351,4807,4808],{"class":450}," {}))\n",[351,4810,4811,4813,4815,4817,4819,4822,4824],{"class":353,"line":686},[351,4812,3884],{"class":364},[351,4814,4702],{"class":375},[351,4816,4705],{"class":486},[351,4818,2750],{"class":490},[351,4820,4821],{"class":497},"tool_call",[351,4823,1528],{"class":490},[351,4825,707],{"class":450},[351,4827,4828,4830,4832,4834,4836,4838,4840,4842,4844,4846,4848,4850,4852,4854,4856,4858,4860,4862,4864,4866,4868,4870,4872,4874,4876,4878,4880],{"class":353,"line":691},[351,4829,3419],{"class":364},[351,4831,4183],{"class":773},[351,4833,720],{"class":450},[351,4835,4310],{"class":805},[351,4837,809],{"class":486},[351,4839,4659],{"class":773},[351,4841,1382],{"class":450},[351,4843,1528],{"class":490},[351,4845,4310],{"class":497},[351,4847,1528],{"class":490},[351,4849,4782],{"class":450},[351,4851,2798],{"class":805},[351,4853,809],{"class":486},[351,4855,4659],{"class":773},[351,4857,1382],{"class":450},[351,4859,1528],{"class":490},[351,4861,2856],{"class":497},[351,4863,1528],{"class":490},[351,4865,4782],{"class":450},[351,4867,2807],{"class":805},[351,4869,809],{"class":486},[351,4871,4659],{"class":773},[351,4873,1382],{"class":450},[351,4875,1528],{"class":490},[351,4877,1941],{"class":497},[351,4879,1528],{"class":490},[351,4881,4740],{"class":450},[351,4883,4884,4886,4888,4890,4892,4895,4897],{"class":353,"line":696},[351,4885,3884],{"class":364},[351,4887,4702],{"class":375},[351,4889,4705],{"class":486},[351,4891,2750],{"class":490},[351,4893,4894],{"class":497},"tool_result",[351,4896,1528],{"class":490},[351,4898,707],{"class":450},[351,4900,4901,4903,4905],{"class":353,"line":710},[351,4902,3419],{"class":364},[351,4904,2826],{"class":773},[351,4906,1356],{"class":450},[351,4908,4909,4912,4914,4916,4918,4920,4922,4924],{"class":353,"line":758},[351,4910,4911],{"class":805},"            call_id",[351,4913,809],{"class":486},[351,4915,4659],{"class":773},[351,4917,1382],{"class":450},[351,4919,1528],{"class":490},[351,4921,3038],{"class":497},[351,4923,1528],{"class":490},[351,4925,1388],{"class":450},[351,4927,4928,4931,4933,4935,4937,4939,4941,4943],{"class":353,"line":784},[351,4929,4930],{"class":805},"            content",[351,4932,809],{"class":486},[351,4934,4659],{"class":773},[351,4936,1382],{"class":450},[351,4938,1528],{"class":490},[351,4940,3228],{"class":497},[351,4942,1528],{"class":490},[351,4944,1388],{"class":450},[351,4946,4947,4950,4952,4954,4956,4958,4960,4962,4965,4967,4969,4972],{"class":353,"line":826},[351,4948,4949],{"class":805},"            is_error",[351,4951,809],{"class":486},[351,4953,4659],{"class":773},[351,4955,764],{"class":450},[351,4957,4794],{"class":773},[351,4959,720],{"class":450},[351,4961,1528],{"class":490},[351,4963,4964],{"class":497},"is_error",[351,4966,1528],{"class":490},[351,4968,451],{"class":450},[351,4970,4971],{"class":752}," False",[351,4973,1646],{"class":450},[351,4975,4976],{"class":353,"line":872},[351,4977,4978],{"class":450},"        )\n",[351,4980,4981,4984,4987,4989,4991,4994,4996,4998,5001,5003,5005],{"class":353,"line":895},[351,4982,4983],{"class":364},"    raise",[351,4985,4986],{"class":736}," ValueError",[351,4988,720],{"class":450},[351,4990,3052],{"class":699},[351,4992,4993],{"class":497},"\"unknown block kind: ",[351,4995,3058],{"class":1564},[351,4997,4691],{"class":773},[351,4999,5000],{"class":699},"!r",[351,5002,3074],{"class":1564},[351,5004,1528],{"class":497},[351,5006,781],{"class":450},[351,5008,5009],{"class":353,"line":915},[351,5010,383],{"emptyLinePlaceholder":382},[351,5012,5013],{"class":353,"line":938},[351,5014,383],{"emptyLinePlaceholder":382},[351,5016,5017,5019,5022,5024,5026,5028,5031,5034,5036,5038,5040,5042,5044,5046],{"class":353,"line":943},[351,5018,4228],{"class":699},[351,5020,5021],{"class":950}," _serialize_plan",[351,5023,720],{"class":450},[351,5025,1660],{"class":729},[351,5027,733],{"class":450},[351,5029,5030],{"class":375}," Plan ",[351,5032,5033],{"class":486},"|",[351,5035,753],{"class":752},[351,5037,746],{"class":450},[351,5039,749],{"class":450},[351,5041,1399],{"class":736},[351,5043,740],{"class":486},[351,5045,753],{"class":752},[351,5047,707],{"class":450},[351,5049,5050,5052,5055,5057,5059,5061,5063,5065,5067,5069,5071,5073],{"class":353,"line":954},[351,5051,3723],{"class":364},[351,5053,5054],{"class":773}," asdict",[351,5056,720],{"class":450},[351,5058,1660],{"class":773},[351,5060,746],{"class":450},[351,5062,1665],{"class":364},[351,5064,1668],{"class":375},[351,5066,2314],{"class":486},[351,5068,3016],{"class":486},[351,5070,753],{"class":752},[351,5072,2576],{"class":364},[351,5074,2326],{"class":752},[351,5076,5077],{"class":353,"line":969},[351,5078,383],{"emptyLinePlaceholder":382},[351,5080,5081],{"class":353,"line":977},[351,5082,383],{"emptyLinePlaceholder":382},[351,5084,5085,5087,5090,5092,5094,5096,5098,5100,5102,5104,5106,5108,5110,5112],{"class":353,"line":991},[351,5086,4228],{"class":699},[351,5088,5089],{"class":950}," _deserialize_plan",[351,5091,720],{"class":450},[351,5093,4436],{"class":729},[351,5095,733],{"class":450},[351,5097,1399],{"class":736},[351,5099,740],{"class":486},[351,5101,753],{"class":752},[351,5103,746],{"class":450},[351,5105,749],{"class":450},[351,5107,5030],{"class":375},[351,5109,5033],{"class":486},[351,5111,753],{"class":752},[351,5113,707],{"class":450},[351,5115,5116,5118,5121,5123,5125,5127,5129,5131,5134,5136,5138,5140,5142],{"class":353,"line":1008},[351,5117,3723],{"class":364},[351,5119,5120],{"class":773}," Plan",[351,5122,720],{"class":450},[351,5124,3197],{"class":486},[351,5126,4436],{"class":773},[351,5128,746],{"class":450},[351,5130,1665],{"class":364},[351,5132,5133],{"class":375}," data ",[351,5135,2314],{"class":486},[351,5137,3016],{"class":486},[351,5139,753],{"class":752},[351,5141,2576],{"class":364},[351,5143,2326],{"class":752},[112,5145,5146,5147,5149,5150,5153,5154,5157,5158,3304,5160,5162,5163,5166,5167,764],{},"Nothing here is clever — it's what the §3.2 block discriminator buys you. The ",[151,5148,4691],{}," field on every block makes deserialization a dispatch table, not a guessing game. ",[151,5151,5152],{},"dataclasses.asdict"," walks nested dataclasses for us on the way out; ",[151,5155,5156],{},"Plan(**data)"," reconstructs on the way in. The one subtlety is ",[151,5159,4349],{},[151,5161,4588],{}," doesn't round-trip through JSON by default, so we serialize with ",[151,5164,5165],{},".isoformat()"," and deserialize with ",[151,5168,5169],{},"datetime.fromisoformat(...)",[112,5171,5172,5173,5175,5176,5178],{},"After every turn, we have a checkpoint. If the process dies before the next turn, the last checkpoint is the recoverable state. Startup uses the deserializers to rehydrate the in-memory objects that ",[151,5174,4078],{}," (and §16's ",[151,5177,4020],{},") want:",[342,5180,5182],{"className":344,"code":5181,"language":346,"meta":347,"style":347},"# examples\u002Fch21_resume.py\nimport asyncio\n\nfrom harness.agent import arun\nfrom harness.checkpoint.store import Checkpointer\nfrom harness.checkpoint.resume import check_pending_tool_calls\nfrom harness.checkpoint.serde import (\n    _deserialize_transcript, _deserialize_plan,\n)\nfrom harness.messages import Transcript\nfrom harness.plans.tools import PlanHolder\nfrom harness.providers.anthropic import AnthropicProvider\nfrom harness.tools.selector import ToolCatalog\nfrom harness.tools.std import STANDARD_TOOLS\n\n\nasync def main():\n    checkpointer = Checkpointer(\".harness\u002Fsessions.db\")\n    session_id = \"session-alpha\"\n\n    # 1. Side-effect verification — surface anything interrupted mid-call.\n    pending = await check_pending_tool_calls(checkpointer, session_id)\n    if pending:\n        print(f\"WARNING: {len(pending)} tool calls were interrupted mid-execution.\")\n        for p in pending:\n            print(f\"  - {p['call_id']} {p['tool_name']} {p['args_json']}\")\n        # In production: run verification tools or ask the user before continuing.\n\n    # 2. Rehydrate in-memory state from the latest checkpoint, if any.\n    transcript: Transcript | None = None\n    plan_holder = PlanHolder()\n    latest = await checkpointer.load_latest(session_id)\n    if latest is not None:\n        print(f\"Resuming session-alpha from checkpoint v{latest['version']}\")\n        transcript = _deserialize_transcript(latest[\"transcript\"])\n        if latest[\"plan\"] is not None:\n            plan_holder.plan = _deserialize_plan(latest[\"plan\"])\n    else:\n        print(\"Starting new session-alpha\")\n\n    # 3. Pass the rehydrated transcript into arun via the `transcript=`\n    #    parameter from §5.5.1 — it resumes the conversation, it doesn't\n    #    replace the user's new message. Passing `transcript=None` is the\n    #    fresh-session path (arun builds a new Transcript internally).\n    provider = AnthropicProvider()\n    catalog = ToolCatalog(tools=STANDARD_TOOLS)\n    result = await arun(\n        provider=provider,\n        catalog=catalog,\n        user_message=\"continue from where we left off\",\n        transcript=transcript,            # ← rehydrated, or None\n        plan_holder=plan_holder,          # ← rehydrated plan (may be empty)\n        checkpointer=checkpointer,\n        session_id=session_id,\n    )\n    print(result.summary)\n\n\nasyncio.run(main())\n",[151,5183,5184,5189,5195,5199,5216,5237,5257,5276,5287,5291,5306,5325,5346,5366,5386,5390,5394,5406,5426,5440,5444,5449,5470,5479,5510,5524,5591,5596,5600,5605,5623,5635,5657,5672,5704,5727,5753,5780,5787,5802,5806,5811,5816,5821,5826,5838,5859,5872,5884,5896,5912,5926,5940,5951,5962,5967,5984,5988,5992],{"__ignoreMap":347},[351,5185,5186],{"class":353,"line":354},[351,5187,5188],{"class":357},"# examples\u002Fch21_resume.py\n",[351,5190,5191,5193],{"class":353,"line":361},[351,5192,389],{"class":364},[351,5194,392],{"class":375},[351,5196,5197],{"class":353,"line":379},[351,5198,383],{"emptyLinePlaceholder":382},[351,5200,5201,5203,5206,5208,5211,5213],{"class":353,"line":386},[351,5202,365],{"class":364},[351,5204,5205],{"class":375}," harness",[351,5207,764],{"class":450},[351,5209,5210],{"class":375},"agent ",[351,5212,389],{"class":364},[351,5214,5215],{"class":375}," arun\n",[351,5217,5218,5220,5222,5224,5227,5229,5232,5234],{"class":353,"line":395},[351,5219,365],{"class":364},[351,5221,5205],{"class":375},[351,5223,764],{"class":450},[351,5225,5226],{"class":375},"checkpoint",[351,5228,764],{"class":450},[351,5230,5231],{"class":375},"store ",[351,5233,389],{"class":364},[351,5235,5236],{"class":375}," Checkpointer\n",[351,5238,5239,5241,5243,5245,5247,5249,5252,5254],{"class":353,"line":403},[351,5240,365],{"class":364},[351,5242,5205],{"class":375},[351,5244,764],{"class":450},[351,5246,5226],{"class":375},[351,5248,764],{"class":450},[351,5250,5251],{"class":375},"resume ",[351,5253,389],{"class":364},[351,5255,5256],{"class":375}," check_pending_tool_calls\n",[351,5258,5259,5261,5263,5265,5267,5269,5272,5274],{"class":353,"line":411},[351,5260,365],{"class":364},[351,5262,5205],{"class":375},[351,5264,764],{"class":450},[351,5266,5226],{"class":375},[351,5268,764],{"class":450},[351,5270,5271],{"class":375},"serde ",[351,5273,389],{"class":364},[351,5275,4156],{"class":450},[351,5277,5278,5281,5283,5285],{"class":353,"line":424},[351,5279,5280],{"class":375},"    _deserialize_transcript",[351,5282,451],{"class":450},[351,5284,5089],{"class":375},[351,5286,1071],{"class":450},[351,5288,5289],{"class":353,"line":437},[351,5290,781],{"class":450},[351,5292,5293,5295,5297,5299,5301,5303],{"class":353,"line":457},[351,5294,365],{"class":364},[351,5296,5205],{"class":375},[351,5298,764],{"class":450},[351,5300,4151],{"class":375},[351,5302,389],{"class":364},[351,5304,5305],{"class":375}," Transcript\n",[351,5307,5308,5310,5312,5314,5316,5318,5320,5322],{"class":353,"line":470},[351,5309,365],{"class":364},[351,5311,5205],{"class":375},[351,5313,764],{"class":450},[351,5315,4202],{"class":375},[351,5317,764],{"class":450},[351,5319,4207],{"class":375},[351,5321,389],{"class":364},[351,5323,5324],{"class":375}," PlanHolder\n",[351,5326,5327,5329,5331,5333,5336,5338,5341,5343],{"class":353,"line":475},[351,5328,365],{"class":364},[351,5330,5205],{"class":375},[351,5332,764],{"class":450},[351,5334,5335],{"class":375},"providers",[351,5337,764],{"class":450},[351,5339,5340],{"class":375},"anthropic ",[351,5342,389],{"class":364},[351,5344,5345],{"class":375}," AnthropicProvider\n",[351,5347,5348,5350,5352,5354,5356,5358,5361,5363],{"class":353,"line":480},[351,5349,365],{"class":364},[351,5351,5205],{"class":375},[351,5353,764],{"class":450},[351,5355,2851],{"class":375},[351,5357,764],{"class":450},[351,5359,5360],{"class":375},"selector ",[351,5362,389],{"class":364},[351,5364,5365],{"class":375}," ToolCatalog\n",[351,5367,5368,5370,5372,5374,5376,5378,5381,5383],{"class":353,"line":494},[351,5369,365],{"class":364},[351,5371,5205],{"class":375},[351,5373,764],{"class":450},[351,5375,2851],{"class":375},[351,5377,764],{"class":450},[351,5379,5380],{"class":375},"std ",[351,5382,389],{"class":364},[351,5384,5385],{"class":368}," STANDARD_TOOLS\n",[351,5387,5388],{"class":353,"line":501},[351,5389,383],{"emptyLinePlaceholder":382},[351,5391,5392],{"class":353,"line":507},[351,5393,383],{"emptyLinePlaceholder":382},[351,5395,5396,5398,5400,5403],{"class":353,"line":513},[351,5397,3601],{"class":699},[351,5399,1052],{"class":699},[351,5401,5402],{"class":950}," main",[351,5404,5405],{"class":450},"():\n",[351,5407,5408,5411,5413,5415,5417,5419,5422,5424],{"class":353,"line":519},[351,5409,5410],{"class":375},"    checkpointer ",[351,5412,809],{"class":486},[351,5414,704],{"class":773},[351,5416,720],{"class":450},[351,5418,1528],{"class":490},[351,5420,5421],{"class":497},".harness\u002Fsessions.db",[351,5423,1528],{"class":490},[351,5425,781],{"class":450},[351,5427,5428,5431,5433,5435,5438],{"class":353,"line":525},[351,5429,5430],{"class":375},"    session_id ",[351,5432,809],{"class":486},[351,5434,2750],{"class":490},[351,5436,5437],{"class":497},"session-alpha",[351,5439,1517],{"class":490},[351,5441,5442],{"class":353,"line":531},[351,5443,383],{"emptyLinePlaceholder":382},[351,5445,5446],{"class":353,"line":536},[351,5447,5448],{"class":357},"    # 1. Side-effect verification — surface anything interrupted mid-call.\n",[351,5450,5451,5454,5456,5458,5460,5462,5464,5466,5468],{"class":353,"line":542},[351,5452,5453],{"class":375},"    pending ",[351,5455,809],{"class":486},[351,5457,2987],{"class":364},[351,5459,3606],{"class":773},[351,5461,720],{"class":450},[351,5463,2933],{"class":773},[351,5465,451],{"class":450},[351,5467,1064],{"class":773},[351,5469,781],{"class":450},[351,5471,5472,5474,5477],{"class":353,"line":548},[351,5473,3884],{"class":364},[351,5475,5476],{"class":375}," pending",[351,5478,707],{"class":450},[351,5480,5481,5484,5486,5488,5491,5493,5496,5498,5501,5503,5505,5508],{"class":353,"line":554},[351,5482,5483],{"class":716},"        print",[351,5485,720],{"class":450},[351,5487,3052],{"class":699},[351,5489,5490],{"class":497},"\"WARNING: ",[351,5492,3058],{"class":1564},[351,5494,5495],{"class":716},"len",[351,5497,720],{"class":450},[351,5499,5500],{"class":773},"pending",[351,5502,746],{"class":450},[351,5504,3074],{"class":1564},[351,5506,5507],{"class":497}," tool calls were interrupted mid-execution.\"",[351,5509,781],{"class":450},[351,5511,5512,5515,5518,5520,5522],{"class":353,"line":559},[351,5513,5514],{"class":364},"        for",[351,5516,5517],{"class":375}," p ",[351,5519,3744],{"class":364},[351,5521,5476],{"class":375},[351,5523,707],{"class":450},[351,5525,5526,5529,5531,5533,5536,5538,5540,5542,5544,5546,5548,5550,5552,5555,5557,5559,5561,5564,5566,5568,5570,5572,5574,5576,5578,5581,5583,5585,5587,5589],{"class":353,"line":565},[351,5527,5528],{"class":716},"            print",[351,5530,720],{"class":450},[351,5532,3052],{"class":699},[351,5534,5535],{"class":497},"\"  - ",[351,5537,3058],{"class":1564},[351,5539,112],{"class":773},[351,5541,1382],{"class":450},[351,5543,2132],{"class":490},[351,5545,3038],{"class":497},[351,5547,2132],{"class":490},[351,5549,2573],{"class":450},[351,5551,3074],{"class":1564},[351,5553,5554],{"class":1564}," {",[351,5556,112],{"class":773},[351,5558,1382],{"class":450},[351,5560,2132],{"class":490},[351,5562,5563],{"class":497},"tool_name",[351,5565,2132],{"class":490},[351,5567,2573],{"class":450},[351,5569,3074],{"class":1564},[351,5571,5554],{"class":1564},[351,5573,112],{"class":773},[351,5575,1382],{"class":450},[351,5577,2132],{"class":490},[351,5579,5580],{"class":497},"args_json",[351,5582,2132],{"class":490},[351,5584,2573],{"class":450},[351,5586,3074],{"class":1564},[351,5588,1528],{"class":497},[351,5590,781],{"class":450},[351,5592,5593],{"class":353,"line":571},[351,5594,5595],{"class":357},"        # In production: run verification tools or ask the user before continuing.\n",[351,5597,5598],{"class":353,"line":577},[351,5599,383],{"emptyLinePlaceholder":382},[351,5601,5602],{"class":353,"line":583},[351,5603,5604],{"class":357},"    # 2. Rehydrate in-memory state from the latest checkpoint, if any.\n",[351,5606,5607,5610,5612,5615,5617,5619,5621],{"class":353,"line":588},[351,5608,5609],{"class":375},"    transcript",[351,5611,733],{"class":450},[351,5613,5614],{"class":375}," Transcript ",[351,5616,5033],{"class":486},[351,5618,753],{"class":752},[351,5620,487],{"class":486},[351,5622,2326],{"class":752},[351,5624,5625,5628,5630,5633],{"class":353,"line":593},[351,5626,5627],{"class":375},"    plan_holder ",[351,5629,809],{"class":486},[351,5631,5632],{"class":773}," PlanHolder",[351,5634,935],{"class":450},[351,5636,5637,5640,5642,5644,5646,5648,5651,5653,5655],{"class":353,"line":599},[351,5638,5639],{"class":375},"    latest ",[351,5641,809],{"class":486},[351,5643,2987],{"class":364},[351,5645,3659],{"class":375},[351,5647,764],{"class":450},[351,5649,5650],{"class":773},"load_latest",[351,5652,720],{"class":450},[351,5654,1284],{"class":773},[351,5656,781],{"class":450},[351,5658,5659,5661,5664,5666,5668,5670],{"class":353,"line":604},[351,5660,3884],{"class":364},[351,5662,5663],{"class":375}," latest ",[351,5665,2314],{"class":486},[351,5667,3016],{"class":486},[351,5669,753],{"class":752},[351,5671,707],{"class":450},[351,5673,5674,5676,5678,5680,5683,5685,5688,5690,5692,5694,5696,5698,5700,5702],{"class":353,"line":610},[351,5675,5483],{"class":716},[351,5677,720],{"class":450},[351,5679,3052],{"class":699},[351,5681,5682],{"class":497},"\"Resuming session-alpha from checkpoint v",[351,5684,3058],{"class":1564},[351,5686,5687],{"class":773},"latest",[351,5689,1382],{"class":450},[351,5691,2132],{"class":490},[351,5693,2612],{"class":497},[351,5695,2132],{"class":490},[351,5697,2573],{"class":450},[351,5699,3074],{"class":1564},[351,5701,1528],{"class":497},[351,5703,781],{"class":450},[351,5705,5706,5709,5711,5713,5715,5717,5719,5721,5723,5725],{"class":353,"line":616},[351,5707,5708],{"class":375},"        transcript ",[351,5710,809],{"class":486},[351,5712,4431],{"class":773},[351,5714,720],{"class":450},[351,5716,5687],{"class":773},[351,5718,1382],{"class":450},[351,5720,1528],{"class":490},[351,5722,1643],{"class":497},[351,5724,1528],{"class":490},[351,5726,4740],{"class":450},[351,5728,5729,5731,5734,5736,5738,5740,5742,5744,5747,5749,5751],{"class":353,"line":622},[351,5730,2920],{"class":364},[351,5732,5733],{"class":375}," latest",[351,5735,1382],{"class":450},[351,5737,1528],{"class":490},[351,5739,1660],{"class":497},[351,5741,1528],{"class":490},[351,5743,2573],{"class":450},[351,5745,5746],{"class":486}," is",[351,5748,3016],{"class":486},[351,5750,753],{"class":752},[351,5752,707],{"class":450},[351,5754,5755,5758,5760,5762,5764,5766,5768,5770,5772,5774,5776,5778],{"class":353,"line":628},[351,5756,5757],{"class":375},"            plan_holder",[351,5759,764],{"class":450},[351,5761,1660],{"class":767},[351,5763,487],{"class":486},[351,5765,5089],{"class":773},[351,5767,720],{"class":450},[351,5769,5687],{"class":773},[351,5771,1382],{"class":450},[351,5773,1528],{"class":490},[351,5775,1660],{"class":497},[351,5777,1528],{"class":490},[351,5779,4740],{"class":450},[351,5781,5782,5785],{"class":353,"line":634},[351,5783,5784],{"class":364},"    else",[351,5786,707],{"class":450},[351,5788,5789,5791,5793,5795,5798,5800],{"class":353,"line":640},[351,5790,5483],{"class":716},[351,5792,720],{"class":450},[351,5794,1528],{"class":490},[351,5796,5797],{"class":497},"Starting new session-alpha",[351,5799,1528],{"class":490},[351,5801,781],{"class":450},[351,5803,5804],{"class":353,"line":646},[351,5805,383],{"emptyLinePlaceholder":382},[351,5807,5808],{"class":353,"line":652},[351,5809,5810],{"class":357},"    # 3. Pass the rehydrated transcript into arun via the `transcript=`\n",[351,5812,5813],{"class":353,"line":658},[351,5814,5815],{"class":357},"    #    parameter from §5.5.1 — it resumes the conversation, it doesn't\n",[351,5817,5818],{"class":353,"line":663},[351,5819,5820],{"class":357},"    #    replace the user's new message. Passing `transcript=None` is the\n",[351,5822,5823],{"class":353,"line":668},[351,5824,5825],{"class":357},"    #    fresh-session path (arun builds a new Transcript internally).\n",[351,5827,5828,5831,5833,5836],{"class":353,"line":674},[351,5829,5830],{"class":375},"    provider ",[351,5832,809],{"class":486},[351,5834,5835],{"class":773}," AnthropicProvider",[351,5837,935],{"class":450},[351,5839,5840,5843,5845,5848,5850,5852,5854,5857],{"class":353,"line":680},[351,5841,5842],{"class":375},"    catalog ",[351,5844,809],{"class":486},[351,5846,5847],{"class":773}," ToolCatalog",[351,5849,720],{"class":450},[351,5851,2851],{"class":805},[351,5853,809],{"class":486},[351,5855,5856],{"class":716},"STANDARD_TOOLS",[351,5858,781],{"class":450},[351,5860,5861,5864,5866,5868,5870],{"class":353,"line":686},[351,5862,5863],{"class":375},"    result ",[351,5865,809],{"class":486},[351,5867,2987],{"class":364},[351,5869,3817],{"class":773},[351,5871,1356],{"class":450},[351,5873,5874,5877,5879,5882],{"class":353,"line":691},[351,5875,5876],{"class":805},"        provider",[351,5878,809],{"class":486},[351,5880,5881],{"class":773},"provider",[351,5883,1071],{"class":450},[351,5885,5886,5889,5891,5894],{"class":353,"line":696},[351,5887,5888],{"class":805},"        catalog",[351,5890,809],{"class":486},[351,5892,5893],{"class":773},"catalog",[351,5895,1071],{"class":450},[351,5897,5898,5901,5903,5905,5908,5910],{"class":353,"line":710},[351,5899,5900],{"class":805},"        user_message",[351,5902,809],{"class":486},[351,5904,1528],{"class":490},[351,5906,5907],{"class":497},"continue from where we left off",[351,5909,1528],{"class":490},[351,5911,1071],{"class":450},[351,5913,5914,5917,5919,5921,5923],{"class":353,"line":758},[351,5915,5916],{"class":805},"        transcript",[351,5918,809],{"class":486},[351,5920,1643],{"class":773},[351,5922,451],{"class":450},[351,5924,5925],{"class":357},"            # ← rehydrated, or None\n",[351,5927,5928,5931,5933,5935,5937],{"class":353,"line":784},[351,5929,5930],{"class":805},"        plan_holder",[351,5932,809],{"class":486},[351,5934,4020],{"class":773},[351,5936,451],{"class":450},[351,5938,5939],{"class":357},"          # ← rehydrated plan (may be empty)\n",[351,5941,5942,5945,5947,5949],{"class":353,"line":826},[351,5943,5944],{"class":805},"        checkpointer",[351,5946,809],{"class":486},[351,5948,2933],{"class":773},[351,5950,1071],{"class":450},[351,5952,5953,5956,5958,5960],{"class":353,"line":872},[351,5954,5955],{"class":805},"        session_id",[351,5957,809],{"class":486},[351,5959,1284],{"class":773},[351,5961,1071],{"class":450},[351,5963,5964],{"class":353,"line":895},[351,5965,5966],{"class":450},"    )\n",[351,5968,5969,5972,5974,5977,5979,5982],{"class":353,"line":915},[351,5970,5971],{"class":716},"    print",[351,5973,720],{"class":450},[351,5975,5976],{"class":773},"result",[351,5978,764],{"class":450},[351,5980,5981],{"class":767},"summary",[351,5983,781],{"class":450},[351,5985,5986],{"class":353,"line":938},[351,5987,383],{"emptyLinePlaceholder":382},[351,5989,5990],{"class":353,"line":943},[351,5991,383],{"emptyLinePlaceholder":382},[351,5993,5994,5997,5999,6001,6003,6006],{"class":353,"line":954},[351,5995,5996],{"class":375},"asyncio",[351,5998,764],{"class":450},[351,6000,3192],{"class":773},[351,6002,720],{"class":450},[351,6004,6005],{"class":773},"main",[351,6007,6008],{"class":450},"())\n",[112,6010,6011,6012,6014,6015,6017,6018,6021,6022,6025],{},"Three steps, each with a distinct job: verify interrupted side effects (§21.3), rehydrate in-memory objects via the deserializers above, pass them into ",[151,6013,4078],{}," through its existing parameters. No new ",[151,6016,4078],{}," parameters are needed — ",[151,6019,6020],{},"transcript="," (from Ch 5) and ",[151,6023,6024],{},"plan_holder="," (from Ch 16) are already the resume seam, which is the whole point of making both of them optional injection points earlier in the book.",[112,6027,6028],{},"The harness now has memory across process deaths. Combined with the scratchpad from Chapter 9 (which was already durable on disk and needs no rehydration), you have full continuity: conversation state rebuilt from SQLite, plan state rebuilt from SQLite, durable findings already on disk, and idempotent replay for any side effects that were in flight.",[280,6030],{},[283,6032,6034],{"id":6033},"_215-choosing-your-checkpoint-cadence","21.5 Choosing Your Checkpoint Cadence",[112,6036,6037],{},"We save after every turn. That's defensible for most harnesses — turns are the natural unit of progress, and SQLite writes of a few KB each are microseconds. But on very fast\u002Fshort turns, checkpointing every turn starts to be a measurable fraction of latency.",[112,6039,6040],{},"Three cadences worth knowing:",[3758,6042,6043,6049,6055],{},[294,6044,6045,6048],{},[124,6046,6047],{},"Per turn"," (our default). Maximum durability. Suitable for human-facing agents where turn latency is 100ms–several seconds.",[294,6050,6051,6054],{},[124,6052,6053],{},"Per tool call."," Even finer. Useful when individual tool calls are long-running and you want to resume mid-turn. Costs more SQLite writes per session.",[294,6056,6057,6060],{},[124,6058,6059],{},"Periodic."," Every N turns or every N seconds. Used by LangGraph's checkpointer by default. Good for high-throughput batch scenarios where partial-loss is acceptable.",[112,6062,6063,6064,6066],{},"The interface (",[151,6065,2646],{},") is the same; the caller decides when.",[280,6068],{},[283,6070,6072],{"id":6071},"_216-postgres-when-youre-ready","21.6 Postgres When You're Ready",[112,6074,6075,6076,6078,6079,6081],{},"The checkpointer interface matches LangGraph's. When you outgrow SQLite (multiple machines, high write rates), swap ",[151,6077,153],{}," for a Postgres-backed implementation. The schema is essentially the same; the connection handling is different; the interface you pass to ",[151,6080,4078],{}," doesn't change.",[112,6083,6084],{},"What you actually get from Postgres: concurrent access across processes, network access from separate machines, point-in-time restore, replication. None of which you need on day one; all of which you might need on day 400.",[280,6086],{},[283,6088,6090],{"id":6089},"_217-commit","21.7 Commit",[342,6092,6096],{"className":6093,"code":6094,"language":6095,"meta":347,"style":347},"language-bash shiki shiki-themes material-theme-lighter github-light github-dark","git add -A && git commit -m \"ch21: SQLite checkpointer with idempotency-aware tool dispatch\"\ngit tag ch21-resume\n","bash",[151,6097,6098,6129],{"__ignoreMap":347},[351,6099,6100,6103,6106,6110,6113,6116,6119,6122,6124,6127],{"class":353,"line":354},[351,6101,6102],{"class":703},"git",[351,6104,6105],{"class":497}," add",[351,6107,6109],{"class":6108},"stzsN"," -A",[351,6111,6112],{"class":450}," &&",[351,6114,6115],{"class":703}," git",[351,6117,6118],{"class":497}," commit",[351,6120,6121],{"class":6108}," -m",[351,6123,2750],{"class":490},[351,6125,6126],{"class":497},"ch21: SQLite checkpointer with idempotency-aware tool dispatch",[351,6128,1517],{"class":490},[351,6130,6131,6133,6136],{"class":353,"line":361},[351,6132,6102],{"class":703},[351,6134,6135],{"class":497}," tag",[351,6137,6138],{"class":497}," ch21-resume\n",[283,6140,6142],{"id":6141},"_218-try-it-yourself","21.8 Try It Yourself",[291,6144,6145,6151,6157],{},[294,6146,6147,6150],{},[124,6148,6149],{},"Crash and resume."," Run a long session, kill the process after turn 4, restart and resume. Confirm the new session loads the previous transcript. Confirm any side-effecting tool calls that were pending were either verified, re-run safely, or surfaced for resolution.",[294,6152,6153,6156],{},[124,6154,6155],{},"Duplicate write test."," Write a tool that sends a mock \"email\" (prints a line to a file). Run the agent to send one email. Run it again in the same session with the same args. Confirm the second call returns the cached result, not a second \"email.\"",[294,6158,6159,6162,6163,6165],{},[124,6160,6161],{},"Corruption audit."," After a session completes, examine the ",[151,6164,2664],{}," table. How many versions are there? Can you reconstruct the session's evolution from them? Delete a middle version; does subsequent resume still work? (Answer: yes, because resume uses latest, but this tells you about recovery options.)",[280,6167],{},[6169,6170,6171,6174],"what-you-understand",{},[112,6172,6173],{},"The harness is durable. Every turn produces a versioned checkpoint; every side-effecting tool call is logged before execution and updated after; idempotency keys prevent double-execution on resume; pending calls on resume surface for verification. Combined with Chapter 9's scratchpad and Chapter 17's lease manager, the full session state — transcripts, plans, external discoveries, concurrent-resource ownership — survives crashes and restarts. SQLite is enough for one-machine deployments; the interface ports to Postgres when you need to scale.",[112,6175,6176,6177,6180],{},"What's still missing: nothing, in the harness. The pieces are in place. Chapter 22 — the last chapter — takes stock. We run the full harness against three providers and see that the adapter seam from Chapter 3 pays off. We list what the harness ",[115,6178,6179],{},"doesn't"," do and where each gap would be filled. We close with a scorecard you can use to evaluate the next framework that lands.",[6182,6183,6184],"style",{},"html pre.shiki code .sutJx, html code.shiki .sutJx{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}html pre.shiki code .sVHd0, html code.shiki .sVHd0{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}html pre.shiki code .s_hVV, html code.shiki .s_hVV{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .smGrS, html code.shiki .smGrS{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sjJ54, html code.shiki .sjJ54{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sbsja, html code.shiki .sbsja{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sptTA, html code.shiki .sptTA{--shiki-light:#6182B8;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .smCYv, html code.shiki .smCYv{--shiki-light:#E53935;--shiki-light-font-style:italic;--shiki-default:#24292E;--shiki-default-font-style:inherit;--shiki-dark:#E1E4E8;--shiki-dark-font-style:inherit}html pre.shiki code .sFwrP, html code.shiki .sFwrP{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#24292E;--shiki-default-font-style:inherit;--shiki-dark:#E1E4E8;--shiki-dark-font-style:inherit}html pre.shiki code .sZMiF, html code.shiki .sZMiF{--shiki-light:#E2931D;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s39Yj, html code.shiki .s39Yj{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .skxfh, html code.shiki .skxfh{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .slqww, html code.shiki .slqww{--shiki-light:#6182B8;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s99_P, html code.shiki .s99_P{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#E36209;--shiki-default-font-style:inherit;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit}html pre.shiki code .stp6e, html code.shiki .stp6e{--shiki-light:#39ADB5;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sGLFI, html code.shiki .sGLFI{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s2W-s, html code.shiki .s2W-s{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#032F62;--shiki-default-font-style:inherit;--shiki-dark:#9ECBFF;--shiki-dark-font-style:inherit}html pre.shiki code .sithA, html code.shiki .sithA{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#032F62;--shiki-default-font-style:inherit;--shiki-dark:#9ECBFF;--shiki-dark-font-style:inherit}html pre.shiki code .srdBf, html code.shiki .srdBf{--shiki-light:#F76D47;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .stzsN, html code.shiki .stzsN{--shiki-light:#91B859;--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":347,"searchDepth":361,"depth":361,"links":6186},[6187,6188,6189,6190,6191,6192,6193,6194],{"id":285,"depth":361,"text":286},{"id":339,"depth":361,"text":340},{"id":2690,"depth":361,"text":2691},{"id":3790,"depth":361,"text":3791},{"id":6033,"depth":361,"text":6034},{"id":6071,"depth":361,"text":6072},{"id":6089,"depth":361,"text":6090},{"id":6141,"depth":361,"text":6142},"md",{},null,{"title":94,"description":117},"e7ukLKV6mAllN1PQrdhXp6_XK0EQ5gQYwnKLDqfRj54",[6201,6203],{"title":90,"path":91,"stem":92,"description":6202,"children":-1},"Previously: evals measure correctness. Nothing in the harness caps spend. The $47K agent-loop incident (DEV Community, Nov 2025) was two agents ping-ponging requests for eleven days; alerts fired, no one stopped them. Alerts are not enforcement.",{"title":98,"path":99,"stem":100,"description":6204,"children":-1},"Previously: twenty-one chapters of cumulative engineering. The harness has a loop, a transcript, adapters for three providers, a tool registry with validation and loop detection, streaming, async, permissions, MCP integration, a scratchpad, retrieval, compaction, sub-agents, structured plans, parallel coordination, observability, evals, cost control, and durable checkpointing.",1776848986890]