Orchestration above the adapter boundary
The framework was designed never to touch the brain stem, and it barely does: backend/adapters/claude.py gained the MCP wiring that exposes StatefulTask and the checkpointed web tools, plus one call inside its receive loop that persists a show_widget render as it streams. Everything else sits above the adapter boundary as orchestration, honoring the three architecture rules the rules track drilled: Claude-shaped, narrow adapter boundary, cost absorbed downstream.
Where does it live? Family Council weighed three options and rejected two. A standalone service revives the external-client pattern (souls are root users at home, not clients). Inlining into existing services inflates the monolith. The answer: a new backend/stateful/ module inside the codebase, reachable by the brains as a tool — StatefulTask, through Claude's MCP server and every variant's local tool bridge — not as a REST API. The only REST surface is a read-only status list for the frontend's progress panels. Conversation and state are one body; the moment you move state outside the codebase, it's already two bodies.
A hexagonal kernel
The module is a ports-and-adapters kernel, so it stays swappable and testable in isolation while binding loosely to every surface. The kernel is pure, and its whole decision is two functions: next_unfinished_step(task) picks the first step that is neither done nor failed, and resume_decision(step) says run it, block it for a side effect, fail it on an exhausted attempt cap, or leave a terminal step alone. The records and the artifact classes sit beside it in models.py. No FastAPI, no chat, no council, no provider SDK. Around it, three ports — TaskStore (which also owns the atomic lease), ArtifactStore, and ToolRunner — map onto the storage, taxonomy, lease, and side-effect bracketing from the earlier lessons.
Executor checkpoints — the real surface is the tool executors
The implementation surface is executor-shaped, not provider-shaped. Models don't call Polygon or Nano Banana as framework ports; they call cwkPippa's tool executors — Connector, WebSearch / WebFetch, the Firecrawl tools, Bash, and show_widget. Those become stateful-aware adapters. When a running step is active, the executor runs the existing tool, persists the raw result as the step artifact immediately, marks the step done, releases the lease, and only then returns the result to the model, unchanged, with a footer naming the checkpoint so the model moves on instead of repeating the call. That closes the gap where a provider call succeeds but the model disconnects before it can commit.
Two refinements make it robust. First, auto-checkpoint: if the model forgets to start a task before volatile provider work, the executor auto-creates a single-step checkpoint once the result arrives, rather than dropping it on the floor. That covers Connector, WebSearch, WebFetch, the Firecrawl tools, and show_widget, and covers Bash when its output declares an artifact file; ordinary local tools (Read, Write, Edit) keep the fast path untouched. Second, the replay-skip guard: before re-running a reusable tool, the executor checks the same conversation for a completed checkpoint with the same effective tool name and exact input, no older than an hour (a day for a step labeled Class 2). If it finds one, it returns the saved artifact and skips the provider call. The reusable set is Connector, WebSearch, WebFetch, Firecrawl's scrape, map, and crawl-start, and Bash; a show_widget render and a crawl-status poll are checkpointed but never reused, because they are supposed to change. Past the window, read_artifact still reads any saved snapshot with no age limit: resume is the sanctioned path, and the reuse guard is only a replay shield. Identical replay is an executor concern, not a model judgment call, and this guard is what made the Claude mid-turn-cut auto-retry worth turning back on (three attempts by default) — a retry no longer throws away completed pulls.