Five independent language models worked one continuous task. None of them saw another one's conversation.
An AI model forgets everything the moment its session ends. So when a long job has to pass between several of them, the usual answer is to replay the entire conversation to the next one, which gets slower, more expensive and less useful at every step. The idea here is to stop passing the conversation at all, and keep the work itself in a record that no model owns.
Further down, five models take one job and finish it that way, live, while you watch. You can read what each of them was told, and check for yourself that none of them ever saw another's conversation.
Every section here opens in plain words followed immediately by the engineering mechanics, formal invariants, and types behind it.
Imagine five people take turns building one big Lego castle, but each person completely forgets everything the moment they hand over. How does the fifth person know not to rebuild the front gate that the second person already finished?
One answer is to tell every new person the entire story of the whole day. That gets very long very fast, and the few important bits end up buried in the middle of it.
The idea is to stop retelling the story. Instead, keep a notebook that belongs to the table rather than to any of the builders. The notebook holds what is finished, what was decided and why, what was tried and did not work, and what to do next.
Before each new person starts, a helper reads the notebook and writes them one short briefing that fits on a single sheet. That sheet is all they get. No story, no chat. Just the sheet. And crucially, nobody may quietly cross something out of the notebook: if you claim you fixed a problem and nobody checked, the problem stays written down.
It works. Five different AI models, from four different companies, each with its own separate password, finished one job together, and not one of them ever read another one's conversation.
In engineering terms: a model's working context dies with its session, and both usual ways of continuing a task in a new session fail. Replaying the transcript grows without bound. Summarising it loses exactly the things that matter: why a decision was made, what already failed, and what the precise next action is.
So the state of work is held outside every model. Each worker returns a result and a report for its successor; those are treated as claims and merged into the record by something that stamps the attribution itself and refuses to close anything unverified; then the record plus the next assignment is compiled into one bounded briefing. The next worker gets that briefing and nothing else.
Continuity across heterogeneous LLM sessions is modelled as an externalised, typed, append-biased execution state rather than as message history. ExecutionState is a Pydantic aggregate: objective, completed and pending tasks, decisions with reasons, versioned artifacts, issues, append-only failed attempts, assumptions, progress, last and next action, worker and handoff history. It is owned by the orchestrator; no worker holds a reference that outlives its turn.
Each turn is stateless in the way that matters: compile(state, assignment, budget) is deterministic, and the message array handed to the provider is rebuilt from scratch every turn and always has length two. There is no accumulator into which a prior transcript could leak, which is why raw_conversation_transfers == 0 is an assertable structural invariant rather than a convention someone has to remember.
Transcript replay costs O(n²) tokens across n handoffs. A compiled package costs O(n·B) for a fixed budget B, and the interesting engineering is entirely in how the compiler spends B: relevance scoring against the pending assignment, a recency blend, per-category caps, and an anchoring rule that pins founding decisions so recent detail cannot outrank them.
The second axis is trust. Model output is treated as a claim from an untrusted origin, and StateReconciler is the only component permitted to write canonical state. Its four invariants, the compile algorithm and the failure semantics are all in Internals.
That is the whole experiment. Everything in this project exists to answer it honestly.
A language model's working context dies with its session. When one worker stops and another takes over, the obvious fix is to replay the entire conversation into the next model. That fails predictably: context windows fill, token cost climbs with every handoff, irrelevant history accumulates, and the details that actually matter get buried in transcript.
Summarising is the usual second attempt, and it is lossy in exactly the places that hurt. A summary drops the reasons behind decisions, the approaches that already failed, the dependencies between pieces of work, the precise execution state, and the exact next action.
So this project takes a different position. The state of work is treated as infrastructure that exists independently of whichever model happens to be doing the work. Models are interchangeable execution engines. The record of the work is not.
An AI assistant only remembers things while you are talking to it. Close the window and its memory is gone. Not hidden somewhere, actually gone.
So if one AI does half a job and a different one has to finish it, how does the second one find out what happened? There are two obvious ideas, and both have a problem.
This project stops trying to pass the memory along at all. The important facts live in a notebook the system owns, and each new worker gets a fresh, short briefing written from that notebook. The workers come and go. The notebook stays.
Replaying history into worker n sends roughly the sum of all prior turns, so cumulative tokens across a run grow as O(n²) while per-call latency grows linearly. Worse, the prompt's signal-to-noise ratio falls monotonically: the fraction of the window occupied by material relevant to the current assignment shrinks with every turn, which is the mechanism behind the familiar "lost in the middle" degradation. It is also model-bound: a transcript carries one provider's message roles, tool-call encodings and formatting conventions, so switching families mid-task means translating a growing artifact you did not design.
A summariser optimises for narrative compression, and the material it discards first is precisely what a successor needs: the reason attached to a decision (which is what lets a later worker judge whether the decision still applies), negative results, and exact cursor position. Negative results are the sharpest case (low narrative salience, high operational value), so they are systematically the first casualties, and re-deriving one costs a full wasted turn.
Model context is treated as a cache; the system of record is a typed aggregate with explicit categories. That distinction is what lets the compiler make a category-aware budgeting decision rather than a token-level one. It is the reason failed_attempts exists as a first-class field with its own priority slot and its own append-only rule, instead of being a sentence inside a summary that the next compression pass can quietly delete.
You will see every one of these named in the playground, so it is worth knowing what they are. Each is one file, and each has exactly one responsibility.
Picture a group project where only one person may work at a time, and each person forgets everything when their turn ends. Here is who does what.
Dependencies point one way: orchestrator → {worker, reconciler, compiler, store}, and only worker → gateway. Nothing above the gateway imports a provider SDK or names a provider, and the gateway itself delegates to LiteLLM rather than branching per vendor. That is not a style preference. It is what makes "swap the model family mid-run" a configuration change instead of a code change.
Two contract families sit side by side, and the split is deliberate. WorkerResult and HandoffReport are claim types: they are whatever a model produced, validated for shape only. Decision, Artifact, Issue and friends inside ExecutionState are record types, and they carry fields a worker structurally cannot populate: recorded_by, recorded_at, verified. Because provenance lives only on the record side, a model cannot assert its own authorship or its own verification status.
A per-model worker subclass is the obvious design and it is a trap: capability drift creeps into the class hierarchy, and within a few months "which worker can do X" is decided by inheritance instead of configuration. Here a worker is WorkerConfig + Gateway, and everything that varies (model string, credential, temperature, token ceiling, role label) is configuration read at runtime.
The result and the handoff report are separate calls with separate prompts. Merging them into one schema is cheaper and measurably worse: asked in the same breath to report success and to warn its successor, a model biases both toward the same tone. Asking separately, after telling the model its turn is over, is what surfaces "do not assume the refresh-token issue is solved". The cost is one extra call per turn, and on a rate-limited free tier that cost is the binding constraint, which is why max_tokens defaults to 2,200 rather than something larger.
A worker finishes, writes a report for its successor, and disappears. The engine reconciles what it claimed, updates canonical state, and compiles a fresh package for whoever runs next.
Think of a relay race. The runners never merge into one person; they just pass a baton. What matters is what is on the baton.
Here the baton carries: what has been finished, what was decided and the reason for it, what was tried and failed, what is still broken, and what to do next. Short, useful, checked.
What the baton never carries: the previous runner's conversation, their thinking, who they were, or their password. Not because someone remembered to strip it out, but because the engine never wrote it down in the first place. There is nothing there to leak.
That is the dotted red line in the picture below. Everything above it simply does not exist as far as the next worker is concerned.
Every model call is constructed as exactly [{role: "system", ...}, {role: "user", package.rendered_text}]. The list is built fresh inside the worker on each turn; there is no instance-level message buffer, no session id, no thread handle, and no provider-side conversation object. Isolation here is structural rather than procedural: there is no code path that could append a prior turn, so it cannot be forgotten or regressed by a later change.
Each handoff also emits a TransferAudit record carrying raw_conversation_transferred, canonical_state_transferred, handoff_report_included, the included and omitted section lists, and the package's token cost against its budget. It is persisted, not merely logged, so the claim "no conversation crossed" is auditable after the fact from the database rather than trusted from a run's stdout. The run summary aggregates it, and across a full multi-worker run that count is zero.
The credential boundary is separate and narrower still. A key resolves inside the gateway from api_key_env or an in-memory WorkerConfig field; it is never a parameter of state, never serialised into a package, and never written to any of the six tables. Keys pasted into the playground live for the duration of one run.
Below is a single worker's turn taken apart. The numbers, the package text, the JSON and the reconciler verdict are all from a real run of the demo task in the playground: worker three, picking up a design it has never seen, from two models it has never met.
The package above cost 631 tokens because the budget allowed 1600. Squeeze it and the compiler starts giving things up, but never in an arbitrary order: it fills strictly by priority, trims a section line by line before abandoning it, and refuses to drop the two sections a worker cannot function without.
Packing a suitcase with a weight limit. You cannot take everything, so you have to decide what matters most, and you decide before you start packing, not by grabbing whatever is nearest.
The order here is fixed: your job first, then the goal, then what has been done, then the decisions and why, and so on down to notes from the last person. Two things are never left behind at any weight: your job and the goal. If they will not fit, they get shortened, never dropped, because a worker who does not know what it is doing is worse than useless.
Now drag the slider down to about 300 and watch failed attempts get struck out. That is the interesting one. The next worker now has no idea that the sliding door already jammed, so it will happily try a sliding door. Preventing exactly that is why this whole project exists.
Sections are built in a fixed priority order (1 assigned task, 2 objective, 3 current progress, 4 current task state, 5 decisions, 6 completed work, 7 artifacts, 8 unresolved issues, 9 failed attempts, 10 assumptions, 11 last action, 12 next action, 13 previous worker's notes), then filled greedily in that order. Three details make it behave:
Item selection within a section blends relevance against the assigned task with a recency bias, under per-category caps. Pure relevance-plus-recency has a failure mode that showed up in a real five-model run: by worker five, the founding decisions ("use FastAPI", "use UUID primary keys") had been outranked by a flood of recent detail, and the review worker was auditing an architecture whose foundations it could not see. Anchoring pins the earliest decisions and failed attempts regardless of score. Flood the record with thirty later decisions and the founding ones still reach the final briefing.
Uses tiktoken when installed, otherwise the heuristic max(words, chars // 4). TokenEstimator is a protocol, so a per-provider tokenizer drops in without touching the compiler. The slider above runs the heuristic path, which is why its numbers match a tiktoken-less run exactly.
Cost is O(s²) in section count from the re-render-and-measure loop, with s = 13 fixed. Line-level trimming adds a factor of the lines in one section. At these sizes the whole compile is microseconds and is not on any hot path: a single model call is five to six orders of magnitude slower.
A worker's report is a claim, not a fact. Generated text can exaggerate, invent an artifact, quietly drop an open issue, or declare a problem solved that nobody verified.
"I did my homework" is not the same as homework. A teacher who writes done in the register every time a student says so ends up with a register full of nonsense.
So this engine believes almost nothing on the word of the worker. Four house rules:
And the engine writes the name and time on every entry itself. A worker cannot sign someone else's name, because it never holds the pen.
StateReconciler.reconcile(state, result, report, worker_id) → ReconcileOutcome is the only write path into canonical state. It is a pure merge over the aggregate, returning accepted counts, suppressed duplicates, warnings, unverified artifacts and rejected resolutions, all of which are persisted next to the execution record, so the verdict on any turn is queryable long after the run.
Completed tasks and decisions dedupe on normalised text, so a restated decision does not inflate the record. Artifacts are versioned rather than overwritten: architecture.md v3 keeps that worker-1 created it and workers 2 and 5 modified it. Discrepancies between result and report (a last_action that disagrees, a decision with no reason, missing handoff notes, a completion claim for an unplanned task) are recorded as warnings rather than swallowed or thrown.
verified is currently only ever false. Real verification (file existence, content hashing, a tool result) is the next layer, and verified is precisely the seam it plugs into: nothing above the reconciler reads the flag as anything other than a boolean, so a validator can begin setting it true without a change anywhere else. Claiming more than that today would be dishonest, so the page and the README both say so.
Worker five reported that the refresh token rotation issue was resolved. Nothing had verified it. The engine recorded the claim, kept the issue open, and passed it forward as still unresolved. You can watch this exact behaviour happen in the playground below.
Give it a job and a key, or borrow one of ours. Every worker runs on a separate credential and answers to a different model, and none of them ever sees another one’s conversation. Watch what crosses instead.
Any vendor. The key tells us who issued it, we ask them what it can run, and you pick from what comes back.
Split across the workers, with later ones given more because they inherit more. Each share is a ceiling, not a cost.
One worker per step, in order. Change any model you like: the engine never learns which company is behind which worker, so nothing below is load-bearing.
Two things are worth watching for. A green line between two workers is the handover, and it says in writing that no conversation crossed. A red box is a worker claiming something the record would not accept.
Each worker will appear here with the exact briefing it was handed, what it claimed afterwards, which of those claims were refused, and the document that went on to whoever runs next.
Everything below came out of the run you just watched. Each item names the worker that produced it, and the second tab has the exact briefing that worker was handed, so any line here can be followed back to where it came from. The third tab is the whole record in the form the engine wrote it, to copy or keep. Artifacts are named by workers and nothing here opens one to check what is inside, which is why every one of them is labelled unverified.
Continuous tasks, heterogeneous teams, crash recovery, and auditable pipelines without conversation bloat.
A long refactor, a migration, or a research task runs past what any single session can hold. Instead of compacting a transcript and losing the reasoning, the work continues in a fresh session with the decisions, the dead ends, and the exact next action intact.
The failed attempts list is the part summarisation always loses.Put a small fast model on bulk drafting and a strong one on review, inside one continuous task. Because no worker inherits a transcript, the expensive model pays only for the compiled state it actually needs, not for everything said before it arrived.
Verified across four model families in a single run.A provider throttles or fails mid task. The engine records the failure, keeps canonical state intact, and the next worker picks the work up from disk. Nothing in flight is lost, and the switch requires no code change.
Rate limit backoff honours the delay the provider itself reports.Every decision carries who made it and why. Every artifact carries a version and whether anything verified it. Every claim the engine declined to accept is written down next to the execution that made it. You can answer what was decided, by which model, on what basis, and what was never actually checked.
Useful anywhere a human has to sign off on model output.Long unattended jobs crash. When the process dies between worker three and worker four, a new process compiles worker four's package from what is on disk and finishes the task. State is written after every turn, not at the end.
Tested by killing a run mid execution and resuming it in a fresh process.Because context is compiled rather than inherited, two models can be given byte identical inputs at the same point in a task. Differences in output are attributable to the model instead of to accumulated conversational drift.
Every model call carries one system message and one package.Everything below is true of the working code behind the run you just watched, including the parts that are deliberately unfinished.
# state is loaded from the record, never held only in memory for seq, (worker, task) in enumerate(assignments): package = compiler.compile(state, task, worker.id, budget) # deterministic result = worker.execute(package) # call 1 -> WorkerResult report = worker.handoff(package, result) # call 2 -> HandoffReport outcome = reconciler.reconcile(state, result, report, worker.id) store.write_turn(state, package, result, report, outcome) # before the next turn audit = handoff.audit(worker, next_worker, package) # persisted, not logged
Two model calls per turn, one write per turn, and no shared mutable object between iterations except state, which only the reconciler writes.
| Type | Trust | What it holds |
|---|---|---|
| ExecutionState | canonical | Objective, completed and pending tasks, decisions, versioned artifacts, issues, failed attempts, assumptions, progress, last and next action, worker and handoff history. |
| WorkerResult | claim | What the worker says it did this turn. Shape-validated by Pydantic, believed by nothing. |
| HandoffReport | claim | A second, separate output addressed to the successor: state at handoff, what is unfinished, what must not be assumed. |
| HandoffPackage | derived | The compiled context a worker receives, plus included_sections, omitted_sections, dropped_items and estimated_tokens. |
| ReconcileOutcome | verdict | Accepted counts, suppressed duplicates, warnings, unverified artifacts, rejected resolutions. Persisted beside the execution. |
| TransferAudit | evidence | Per-handoff record of what crossed and what did not, with token cost against budget. |
| WorkerConfig | config | id, model, credential source, api_base, temperature, max_tokens, max_retries, role, enabled, provider_config. Read from JSON at runtime. |
An event-sourced design would give free history and replay. It was rejected because the compiler's job is category-aware budgeting, and folding an event stream into categories on every compile is work the aggregate does once. History is kept anyway as per-turn snapshots plus an append-only event table, so replay is available without paying for it on the read path.
Only one worker runs at a time. Parallel branches would need conflict resolution in the reconciler (two workers versioning the same artifact from the same base), and that is a different, larger problem. SwitchPolicy is the interface a scheduler would implement; the loop itself would not change shape.
Summing per-section estimates drifts from the real prompt by headers and joining whitespace, and that drift is exactly what produces a context overflow that unit tests never see. Re-rendering costs O(s²) with s = 13 fixed: microseconds against a model call.
Persisting per-turn is what makes resume real rather than aspirational: kill the process between worker three and worker four and a new process compiles worker four's package from disk.
The gateway parses the delay the provider itself suggests ("try again in 8.4s") and honours it, falling back to exponential backoff. The same worker simply waits; state is untouched, so nothing needs unwinding.
| What fails | What happens | What is lost |
|---|---|---|
| A single worker call | The failure is recorded on the execution row; the run continues from canonical state with the next worker. | that turn only |
| Provider rate limit | Retried inside the gateway, honouring the provider's own suggested delay. | nothing |
| The process is killed mid-run | State up to the last completed turn is on disk. A fresh process recompiles the next briefing from it and finishes the job. | the in-flight turn |
| A model returns unparseable output | Pydantic validation rejects it; the turn is recorded as failed rather than merged half-formed. | that turn only |
| A worker lies about what it did | The reconciler's four invariants contain it: the issue stays open, the artifact is stamped unverified, the failed attempt cannot be deleted. | nothing structural |
| The budget is set too low | Sections are trimmed then dropped in priority order, and every omission is reported in the package. | context, visibly |
Not a parallel agent framework: turns are strictly sequential. Not a tool-use or code-execution runtime: workers produce structured descriptions of work, and artifact content is never validated, which is why every artifact carries unverified. Not a scheduler, a queue, or a service. And not a claim that this beats a single long-context model on a task that comfortably fits in one window: it earns its place when the work outlives a context window, crosses model families, or has to be auditable afterwards.
Once the way you would explain it to someone who has never written code, and once the way you would write it in a design document. Both columns describe the same thing.
| Term | In plain words | Precisely |
|---|---|---|
| Tokenthe unit of cost | Roughly a word-and-a-bit. AI models charge by how many you send them, so "how many tokens" really means "how much does this cost and will it even fit". | The unit a model's tokenizer splits text into. Here estimated with tiktoken when available, otherwise max(words, chars // 4). |
| Context windowthe size limit | How much a model can hold in its head at once. Go over it and the oldest parts fall out the back. | The maximum token span a model attends over in one call. Exceeding it truncates or errors depending on the provider. |
| Worker | One AI doing one turn of the job. It walks in knowing nothing, reads its briefing, does its bit, and forgets everything. | WorkerConfig + Gateway. One class for every model; no memory between turns and no reference to state that outlives the turn. |
| Canonical state | The notebook. The one true record of the job, owned by the system and not by any of the AIs. | ExecutionState, a typed Pydantic aggregate. The single source of truth; only the reconciler may write to it. |
| Context packagethe briefing | The one-page sheet a worker is handed. Written fresh for that worker, for that step, with a note saying what had to be left off. | HandoffPackage: rendered text plus included_sections, omitted_sections, dropped_items and estimated_tokens. |
| Handoffthe pass | The moment one worker stops and the next starts. What crosses is the notebook, never the conversation. | A turn boundary producing a HandoffReport, a reconcile, a persisted write and a TransferAudit record. |
| Handoff reportthe note | A note every worker must write to whoever comes next: where I stopped, what to look out for, what not to assume is done. | A second, separate model call with its own schema, so warnings are not biased by the same breath that reports success. |
| Reconciler | The teacher who checks the homework before writing anything in the notebook. Believes almost nothing on the worker's word alone. | StateReconciler, the sole write path into canonical state. Dedupes, stamps provenance, and enforces four invariants. |
| Claim vs. factthe trust split | "I did my homework" is not homework. Everything a worker says is a claim until the engine has a reason to accept it. | Claim types (WorkerResult, HandoffReport) are shape-validated only. Record types carry engine-written recorded_by, recorded_at and verified. |
| Provenancethe signature | Who wrote this down, and when. The engine signs every entry itself, so no worker can sign someone else's name. | Engine-stamped attribution fields on every canonical record. Structurally unpopulatable by a model. |
| Unverifiedthe stamp | "Someone said this exists, but nobody checked." The stamp travels forward so the next worker knows not to rely on it. | verified=False on an artifact. Currently always false: the seam where real validation plugs in. |
| Token budgetthe weight limit | The size limit on a briefing sheet. It forces the engine to decide what matters most instead of dumping everything. | A per-package ceiling enforced against the rendered package, filled greedily in priority order. Default 1,600. |
| Anchoringcompiler rule | Always keep the very first big decisions, even when newer stuff looks more urgent. Otherwise the last worker cannot see the foundations it is judging. | Pinning earliest decisions and failed attempts regardless of relevance score. Added after a real run where founding choices were outranked by recent detail. |
| Failed attemptthe dead end | Something already tried that did not work. The most valuable thing in the notebook, and the first thing a summary would throw away. | An append-only category. No later worker can remove one, because a removable negative result will be rediscovered at full cost. |
| Gateway | The only part allowed to phone the AI companies. Everything above it has no idea which company it is talking to. | The single provider-aware module, delegating to LiteLLM. Ships live and deterministic mock implementations of one interface. |
| Mock gatewayoffline mode | A stand-in that answers instantly without calling anyone, so you can run the whole thing with no key and no cost. | Deterministic offline implementation. Compilation, reconciliation, persistence and audit all execute for real; only the model call is simulated. |
| Orchestrator | The organiser. Decides who goes next, hands out briefings, and keeps the notebook safe. | Owns the turn loop, the worker registry and the SwitchPolicy. Owns the plan cursor; a worker's suggested next action is advisory. |
| Resumecrash recovery | If the power goes out halfway, you can pick up where it stopped, because the notebook was saved after every turn. | Rebuilding the next package from persisted state in a fresh process. |
Direct answers on models, security, RAG vs. state, rate limits, and framework comparisons.
No. Leave step 1 of the playground alone and the run uses a deterministic offline gateway. Compilation, reconciliation, the writes to disk and the transfer audit all execute for real. Only the model calls are simulated. It costs nothing and finishes in milliseconds, which is why playback is paced by default so you can watch it.
No, and the difference is the point. Retrieval fetches documents similar to a query. This compiles execution state relevant to an assignment: a typed record with explicit categories, of which "decisions and their reasons", "issues still open" and "approaches that already failed" are the ones that matter.
An embedding index has no way to express "this decision is foundational, keep it regardless of similarity score", or "this dead end must never be deleted". Those are category rules, not similarity rules. Nothing stops you putting retrieval behind an artifact, but it solves a different problem from the one here.
If your whole task fits comfortably in one window and one session, do that: it is simpler and this project would not earn its complexity.
It earns it when at least one of these is true: the work outlives any single window; you want different model families on different steps (cheap for drafting, strong for review) and refuse to pay the expensive one to re-read everything said before it arrived; the process may crash and must resume; or somebody has to answer afterwards what was decided, by which model, on what basis, and what was never actually checked.
There is also a quality argument independent of size. A large window still degrades as the relevant fraction of it shrinks. A compiled 631-token package is not merely cheaper than a 27,000-token transcript; it is more useful to the model, because everything in it was selected for the task at hand.
Nothing stops it lying; the design assumes it will. What is enforced is what a lie can accomplish. An issue is never closed on an unverified claim: the assertion is recorded as resolution_claimed_by and the issue stays open. An artifact mentioned only in prose is stored stamped unverified and travels forward with that label. Failed attempts are append-only, so no worker can delete an inconvenient dead end. And provenance is written by the engine from the worker id and the server clock, never self-reported.
You can watch this happen: in the demo run, worker five claims the refresh-token rotation issue is resolved, and the engine forwards it as still open.
Those are mostly about control flow: graphs, roles, tool routing, who calls whom. This is about what survives a handoff, and it is deliberately narrow: one sequential loop, one worker class, no graph, no tools.
The substantive differences are that state here is a typed aggregate the engine owns rather than a blob passed between nodes; model output is treated as an untrusted claim with a reconciliation boundary in front of canonical state; and every handoff is bounded by an explicit token budget with omissions reported rather than silent. If you want branching or parallelism, SwitchPolicy is the interface to implement, but conflict resolution for concurrent writes is a real problem this does not solve today.
A rate limit is retried inside the gateway, honouring the delay the provider itself suggests. A hard failure is recorded on that execution row and the run continues from canonical state with the next worker: the failed turn is lost, nothing else is. If the whole process dies, the record up to the last completed turn is already on disk, so a fresh process recompiles the next briefing from it and finishes the job.
The roster is configuration, read at runtime, and adding one requires no code change. The identical path runs with 1, 3, 5, 20 and 100 workers. More plan steps than workers cycles the roster; more workers than steps gives the surplus continuation turns, so every configured worker gets one.
A key resolves inside the gateway, either from the environment or from an in-memory config held for one run if you pasted it into the playground. It is never written to the database, never logged, and never returned to the page. No table has a column that could hold one.
One caveat that trips people up: the model prefix picks the provider, not the key. A valid key with the wrong prefix fails with invalid x-api-key, because it is being offered to a provider that never issued it.
Artifact content is never validated: workers name artifacts and the engine grades every one unverified. Turns are strictly sequential, with no parallel branches and therefore no write-conflict resolution. There is no tool use or code execution: workers produce structured descriptions of work, not the work itself. Each of those has a named seam (verified, SwitchPolicy, the gateway interface), and none of them is claimed as done.
Yes. The compiler needs no model call and no orchestrator: engine.compile_context(state, task, worker_id) turns any ExecutionState into a bounded, prioritized prompt context and tells you what did not fit. The reconciler, store, registry and gateways are exported and swappable individually: the Engine facade is delegation, not a wall.
Current test coverage, verified behaviors, and architectural invariants.