Nine stages, nine amnesiacs: context isolation as an architecture
Dex never lets a session get long enough to rot. Every stage is a brand new agent with an empty context window, state travels through the filesystem, and the unit of everything — execution, resume, storage, the UI — is the stage. Including what that costs.
Every long agent session dies the same way, and it never looks like dying. It looks like agreement.
Somewhere past the hundred-thousandth token the model stops pushing back. It re-reads a file it read forty minutes ago. It forgets the constraint you gave it in turn four — not dramatically, just quietly, the way you forget a name at a party. It marks a task complete with total confidence and a test suite it never ran. The prose is still fluent, the tone is still crisp, the bullet points still line up. That is exactly the problem. Fluency is the last thing to go.
The industry’s answer is a bigger context window, which is the right answer if you believe the failure mode is capacity. I don’t. A long session is not a bucket filling up; it is a room getting messier, and past a certain point the model spends more of itself navigating the mess than doing the work.
Dex takes the opposite bet: never let a session get long enough to rot. It runs a project as a sequence of discrete stages, and every single stage is a brand new agent with an empty context window. Nine stages, nine amnesiacs. None of them has ever met the others.
State does not travel through the context window. It travels through disk. That is Ralph Wiggum’s idea, not mine, and it is the whole trick: each stage reads what it needs, works inside the sharp part of a fresh window, and writes its results back.
The nine, and what each one leaves behind
An architecture built on amnesia only works if every stage’s output is a file someone else can read cold. So the useful way to describe the loop is not by what each stage does but by what it leaves on disk:
| Stage | Runs | Leaves behind |
|---|---|---|
| clarify | once, interactively | full_plan.md — the clarified goal, produced by Q&A with you |
| constitution | once | constitution.md — project-wide principles every later spec must obey |
| gap analysis | every cycle | one decision: what this cycle is for |
| specify | per feature | spec.md — user stories and acceptance criteria |
| plan | per feature | plan.md — technical approach, architecture decisions |
| tasks | per feature | tasks.md — dependency-ordered, individually actionable |
| implement | per phase of tasks.md |
code, and the commits that carry it |
| verify | per feature | a structured verdict — build, tests, criteria, blocking vs minor |
| learnings | per feature | appended insights that later cycles read |
Around those sit the mechanical steps that are not really agent work — prerequisites, branch creation, a one-off manifest extraction, and a completion step at the end.
Four of the nine are spec-kit’s, and Dex does not reimplement them; the whole
specify stage prompt is one line of /speckit-specify. The three that are not
spec-kit’s are the three that make it a loop rather than a workflow: gap
analysis decides what a cycle is for, verify provides the backpressure that
lets a cycle fail, and learnings is the only channel through which a run
influences its own future.
The ordering is not arbitrary either. Every stage’s input is the previous stage’s committed output, which is what makes the amnesia survivable — nothing in the chain requires knowing how the previous artifact came to exist, only what it says.
The stage is the unit of everything
This is the part that took a dedicated spec to get right, and the mistake is common enough to be worth naming precisely.
Cycles were originally the unit of the loop. Stages were an implementation detail of a cycle. That was fine right up until stages appeared in the UI as a row of things with their own names and their own progress — at which point the user’s mental model became “stages” and the engine’s model was still “cycles”, and every bug living in that gap was invisible to me and infuriating to anyone using it.
The clearest symptom was Stop. Click it at a cycle boundary and everything
behaved. Click it anywhere else and: specify had finished, plan had not
started, and on resume the loop counted the aborted cycle as complete, began a
fresh one, and left the spec directory specify had just written orphaned on
disk. Never planned against, never implemented — a folder you paid four dollars
for and could never use.
The fix is to make the stage the unit of resume as well as execution: persist
lastCompletedStage, and let a resume re-enter the middle of a cycle against the
same spec directory rather than restarting from the top. That is the
RESUME_AT_STEP path, and it is deterministic — lastCompletedStage is a
recorded fact, not something a model is asked to infer.
Once the stage is genuinely the unit, the rest of the architecture falls out of
it. A stage is one query() call. A stage is one commit. A stage is one row in
the timeline, one entry in the trace, one resume point. Those are not four
coincidences; they are one decision, and the places Dex was buggy were exactly
the places where one of the four had not caught up with the others.
What context isolation costs
Every stage re-pays its read cost. This is the direct and unavoidable price.
An agent that remembers can answer a follow-up question for free; an amnesiac has
to re-derive the situation from disk each time. Across a nine-stage cycle the
same plan.md may be read four times by four different agents. Context isolation
trades tokens spent remembering for tokens spent re-reading, and that trade
is excellent precisely when a session would otherwise degrade — and simply
wasteful when it would not.
Anything not written down is lost, including things worth keeping. Artifacts
survive. Reasoning does not. When the plan agent considered three approaches
and rejected two for good reasons, plan.md records the winner; the two
rejections evaporate with that agent’s context. The implement agent may then
cheerfully rediscover a dead end that was explored and dismissed twenty minutes
earlier, because nothing in the pipeline captures why not. Human handoffs have
the same failure, and humans compensate with a corridor conversation. There is no
corridor here.
Nine stages is a lot of fixed overhead. A feature that is genuinely twenty lines still pays for gap analysis, specify, plan, tasks, implement, verify and learnings. The ceremony can cost more than the work. Dex earns its keep on multi-feature projects running unattended for hours; for “add a field to this form”, opening Claude Code and typing is strictly better.
And the stage boundary is where meaning gets lost. Every handoff is a serialisation, and every serialisation drops something. That problem turned out to be sharp enough — and expensive enough — to deserve its own post, which is the next one.
The takeaways, minus the product
Strip Dex away and three things survive that I would apply to anything letting an agent run unattended:
- Context isolation beats context size. Design for a fresh brain, not a bigger one. The fresh brain is cheaper, more predictable, and gets better for free every time the model does.
- State that outlives a process belongs on disk — and if your users already have git, you already have versioned, branchable, shareable state with a twenty-year track record. Don’t invent a second one.
- Pick one unit and make everything agree with it. Execution, storage, resume and display should all be granular at the same level. Every place they disagree is a bug you have not found yet.
Dex is a desktop app — Electron, React, the Claude Agent SDK, no database, all state in files and refs — and it is on GitHub. It is early in the way that matters: it works, it has built real features unattended, and I still find something wrong with it most weeks.
Next: structured handoffs — between two agents, prose is a wire protocol, and a catastrophically bad one.