Two Failures That Kept Coming Back
Before Coop had an engine, the ledger ran on a habit. A worker session was supposed to run todo take before it started and todo done when it finished. Workers occasionally started assigned work without taking its record. More often they finished long work after the reminder to close it had scrolled out of reach. Dad was the one who noticed, and he repaired the ledger by hand, through Almanac or by asking another Pippa to fix it.
The tempting fix is a better reminder. Coop's architecture rules that out in one sentence: moving the reminder to another Pippa prompt would leave the same failure. A reminder is still something a model has to remember, and remembering is exactly what failed.
Two Facts Nobody Has to Remember
So the engine keeps two facts, independently of any model's memory:
- Which actual session accepted which exact work items. Not which actor name, not which folder. The session.
- Which accepted items still need an outcome or another recorded decision. An obligation that stays owed until someone records something.
A model still judges whether the work meets its acceptance criteria. The engine only guarantees that the judgment stays owed until it lands. This track is about the first fact, and about the gap between "a session exists" and "a session owns this work", which is where every false claim used to slip in.
The Row Comes First
When Dad assigns a todo, from the workspace, from Telegram or through Pippa, the first thing that happens is a database row. Assignments.create writes an engine_assignments row in state prepared, inside an idempotent engine command. It carries the item and its current revision, the vendor, the project, the mode, a fresh random marker, the persisted setup prompt, the work scope and the reasoning effort. Nothing on any Mac has moved yet.
Every native effect comes after. The coordinator moves the row to awaiting_session before it opens an app or starts a process, and the code says why in a comment: "Persist BEFORE the external effect. On restart, discover; never open a second native chat automatically." If a background launch times out, Coop does not launch again. It keeps looking for the one session that launch may have started. One request, one chat, even across a restart. A Claude new chat is the one launch Coop does not wait on: if the app cannot be opened on office, a timeout included, the assignment fails at once and Dad is told to open the app there and try again.
Four Ways to Get a Session
| Mode | What happens |
|---|---|
new | A fresh chat. For Claude Code, the app's composer opens on office with the setup prompt prefilled, and a person presses Send. For Codex, a private, persistent App Server creates the chat, runs only its setup turn, exits, and then the Codex app opens that exact thread. Nobody presses Send. |
manual | "Copy setup brief." Nothing opens. Coop shows the persisted brief and Dad sends it as the first message of a new chat himself, which is the path for when he is away from office and nobody can press Send there. |
existing | A chat that is already open, chosen by title, folder and status. It skips awaiting_session, because there is nothing to wait for. |
remote | The default for Telegram and Pippa's run. Claude Code starts in the background with claude --bg --remote-control, and Coop picks up the Remote Control link when the session shows one. Codex takes the same private App Server bootstrap as new. |
Whatever the mode, the row walks the same states: prepared → awaiting_session → enrolling → dispatched → resolved or declined, with failed, expired and cancelled as explicit terminal states. The four active states are one tuple in code, and the store enforces that an item has at most one assignment in any of them: a unique partial index, plus a check that no live dispatch already holds the item. Double-click the button and the second click conflicts.
The Effort It Starts At
One small field with a story. Until 2026-10-10 Coop recorded a worker's reasoning effort but never passed one at launch, so every Coop-started Claude Code worker ran at the CLI's native default, medium, while Dad's baseline is high (commit adcc5ab). Now launch_effort is the single resolver for both vendors: high unless the request names another level, and the family's max becomes xhigh for Codex, whose scale tops out there. The effort reaches only a process Coop starts itself: a Claude background session (remote) gets --effort, and the Codex bootstrap (new and remote) creates its thread at that level. A Claude new chat does not: its composer link carries a folder and a prompt and nothing else, so it runs at whatever the app is set to, like an existing chat or one Dad opens with a manual brief.
open until one specific session accepts one specific invitation (the other door, a chat typing todo take itself, is track 8's), and the rest of this track is how that acceptance is built.