Condensatecondensate

From copy and paste to a runtime

Two years ago a coding agent was a chat window and a clipboard. Today it is a loop that edits your repo for hours without you. This is the story of how it got there, what broke at each step, and what Condensate is: a headless, model-agnostic, harness-agnostic runtime for running those loops in one place you can read.

2023Copy and paste

The first coding agent was you. You opened a chat tab, described the bug, pasted the function in, read the answer, and pasted it back into the editor. The model could not see your files. It could not run anything. Every fact about the codebase reached it through your clipboard, and every change it proposed reached the repo through your hands.

It worked because you were the harness. You chose what context to give, you checked the answer, you ran the tests. The model's memory of the project was whatever fit in the window, and it forgot everything when you closed the tab.

2024The editor learns to read

Then the model moved into the editor. Copilot chat, Cursor and the rest could read the open file, then the whole workspace, and apply a diff you accepted with a click. The clipboard was gone. The model saw real code and its edits landed in real files.

But the shape was still a conversation. One model, one session, one person watching. The model could propose a change but not run the tests to see if it was right. When you closed the editor the session was over, and if you wanted to know a week later why a function looked the way it did, the answer was in a chat panel that no longer existed.

2025The terminal, and the loop

The third step changed what an agent is. Claude Code, Codex and the open-source agents like pi put the model in a terminal with tools: read a file, edit it, run a command, search. And they gave it a loop. The model reads the prompt, calls a tool, reads the result, and calls again, on its own, until it decides it is done.

That loop is the whole difference. An agent with a loop can run the tests, read the failure, fix it, and run them again. It can work for an hour on a task you described in one sentence. Permission prompts and hooks appeared so you could gate what it ran. Subagents appeared so it could fan work out.

The loop also created a new kind of artifact: a long, structured record of what an agent did. Every tool call, every result, every reply. That record is the thing you would want when something goes wrong. And every tool kept it in its own format, in its own place, in a scrollback that goes away.

2026Many at once

Once one agent could work alone for an hour, the obvious move was to run several. A builder, a reviewer that reads its diff, a fixer that answers the reviewer, a second reviewer on the fix. Five agents on one machine, across two or three model providers, on a couple of subscriptions.

This is where the terminal-era design breaks, and it breaks in the same way every time. Each agent is its own process with its own history and its own login. Five histories in five formats means there is no single place to ask what happened. Five processes refreshing the same OAuth token means one refresh wins and the others fail, and the failure looks like a dead credential when it is a race. A model that hits its quota looks the same as one whose login expired, and the wrong guess takes a healthy account out of service. A restart changes session ids, and anything that copied them is now pointing at nothing.

The common cause is that more than one process holds a copy of a fact, and the copies disagree. Every fix that stays inside the agent tools is a patch on that. The real fix is to move the loop out of the tools.

A runtime

Condensate is the loop, taken out of the tool and put in one process. It is headless: there is no window, only an HTTP surface and a socket. It is model-agnostic: the same loop runs Claude through the Claude Agent SDK and any provider pi-ai serves, Codex and OpenCode Go among them. It is harness-agnostic: Claude Code, a review workspace, a chat app, or a program you wrote can all drive it, and a Claude Code session on the same machine sees runtime threads as peers.

The rest of this page walks through its layers in the order the problems above appeared. Four of them have names, because they are the ones you will talk about. The rest are plain words.

Halo, the runtime

HaloHost-Anchored Loop Orchestrator. The one process that owns every thread, engine session, workspace, account and policy on the machine.

Halo is the answer to "which process holds the fact". It holds all of them. A thread is one agent conversation with one id. An engine session is one live connection to one model for one thread. A workspace is a named group of repos, accounts and policy that a thread is spawned into. Halo owns the registry of each, and everything else on the machine reads Halo over HTTP and keeps only its own product state.

That one-owner rule is what the rest depends on. A review tool that keeps its own copy of session ids will be wrong after a restart. A review tool that asks Halo cannot be.

Halo also bounds the fleet. There is a cap on live engine sessions, 32 by default. At the cap the oldest idle session is evicted; if none is idle, the spawn is refused with a 429 instead of starving the machine.

Ledger, the durable log

LedgerLog of Every Durable, Gated Event and Reply. The append-only record of a thread, written before the reply is returned.

The Ledger answers "what did the agent do". Every thread is an append-only list of events in SQLite: messages, every tool call and result, thinking, steering, approvals, forks, merges, seats joining and leaving. Nothing is edited in place. To know what a thread looks like now, you fold its events from the start, and that fold is the only way state exists. Participants, the current model, gate verdicts and every metric are computed from events when asked for, never kept as counters beside the log, because a counter can drift and a fold cannot.

What makes it durable is the turn journal. When the engine emits an event, it goes onto a queue without waiting. One task drains the queue in order, writing each event and waiting for the acknowledgement before the next. The reply goes through the same queue, so it lands after every event that produced it, and the client only gets the reply once the journal reports everything durable. A reply can never overtake the thinking that produced it, and a failed write can never become a log line while the turn reports success.

If a write is refused mid-turn, the thread is marked degraded and every later turn on it answers 503, including after a restart. A log with a hole in it would claim a turn it did not fully record, so Halo stops that thread instead. When Halo itself dies mid-turn, the next boot writes an error into every thread whose log ends mid-turn.

Loom, the agent loop

LoomLog-Owned Orchestration of Models. The loop itself, run by the engine over the Claude Agent SDK and pi-ai, with its own record for the model.

Loom is the terminal-era loop with its ownership fixed. Build the message array, call the model, run every tool call it made, append the results, call again, stop when the model stops. Two adapters sit behind one interface. The Claude adapter holds one persistent query per session on the Claude Agent SDK and resumes it across restarts. The pi-ai adapter takes only what pi-ai is for, the providers, the credential store contract, streaming, the model registry and the retry classifier, plus the seven builtin tool definitions, and runs the loop itself. Condensate does not embed anyone else's agent.

Loom keeps a second record beside the Ledger. The Ledger is for people. Loom's transcript is for the model: an append-only file per session holding the exact message array the provider receives, serialized once when each message happens and never re-rendered. Provider prompt caching is keyed on the exact byte prefix of a request, so a history rebuilt from the Ledger on every send would miss the cache every turn. With its own transcript a session keeps a warm cache across turns and across restarts. Compaction is the one deliberate rewrite, at a turn boundary, and it is logged as the cache miss it is.

Every session runs one state machine with six states, and nothing leaves closed. A session you shut down is never quietly rebuilt by a retry. A session's thinking budget is one number, and Loom maps it to whatever the model actually accepts. Text can be steered into the loop mid-turn and lands as a user message before the next model call.

The gate

The terminal agents had permission prompts. Condensate has one gate, and every tool surface passes through it: MCP tools, custom tools, builtins. The gate answers allow, deny, or pending. Pending parks the call, emits an approval event, and holds it until a person resolves it. Disposing the session refuses everything parked so nothing hangs.

Above the gate sits steering. Both engines post hook events to a steering backend at fixed points in a turn: prompt submitted, before and after each tool call, model stopped. The backend answers with a verdict. deny refuses a tool call ahead of the gate. inject queues text into the session. interrupt ends the turn. Anything else allows, and if the backend cannot be reached, the action is allowed and one log line says so. Every verdict that changes a turn is written to the Ledger by Halo, not by the client, so it cannot be forged.

The broker

The broker answers the token race. An account is one login for one engine, kept as its own config directory, so two sessions on two accounts run at the same time. Every access token on the machine comes through the broker. It holds one refresh in flight per account inside a process and a lock file per account across processes, refreshes a minute before expiry, and logs token ids, never values. Six sessions asking for the same token get one refresh.

Failure, classified

Moving a turn to another model spends a second quota, puts a different runtime in front of the same work, and writes a switch into a log people read. So it is never decided from the shape of an error message. The engine computes one of nine classes, from the error's tag first and its text second, and every decision branches on the class.

ClassMeaningFails overRetries once
quotathe account has no allowance leftyesno
auththe credential is deadyesno
model-not-foundthe model id is in no catalognono
sessiona protocol fault between runtime and enginenoyes
closeda caller closed the session on purposenono
toolan MCP server or tool call failednoyes
permissionthe gate refused a tool callnono
interrupteda caller aborted the turnnono
unknownanything elsenono

Only quota and auth move the work. Halo writes a model-switch event before the replacement sends anything. An auth failure drops every remaining model on the same engine, because the credential belongs to the engine. An MCP server that answers "invalid api key" is reporting its own credential, and the classifier checks for that first, so the account stays healthy. A send that fails, heals and succeeds is one turn to its caller, and the Ledger reads failure, retry, outcome.

Which model, how much thinking, and which chain to fail over along all live in one schema-validated routing document. A caller may override a field, a profile may set it, the document supplies the default.

Seats and children

A seat is a named participant on a thread with its own engine session. Every thread carries a role in its first event: build, review, chat or audit. Seats join and leave through events Halo authors, so the participant list cannot be forged. On its turn a seat receives the messages since its last turn as one window.

A thread can delegate: the engine defines the tools and Halo creates and joins the child threads. A thread can subscribe to another and be woken when that thread needs an answer, which is how a reviewer wakes a fixer and a fixer wakes its reviewer. This is the fleet from the previous era, with one owner.

Brain, the memory

BrainBank of Recalled, Retained And Indexed Notes. Memory per workspace, written after every turn without the turn waiting on it.

The chat-tab agent forgot everything when you closed the tab. The Brain is what a thread remembers across sessions. After a turn's reply is committed, a background task folds the turn into a short note, the ask, the first sentence of the reply, any sentence naming a file or command, the refs that were touched, under 400 tokens, and retains it in the workspace's bank, tagged with product, role, thread, seat, model and engine. A memory event in the Ledger says what was retained. Tool results and thinking are never read into it. At spawn, Halo recalls into the prompt and applies the workspace's standing directives.

Reflection, which turns notes into durable observations, needs a model call of its own. It gets one through Halo's completions door, an OpenAI-shaped endpoint that picks the workspace's account with the same guards a seat would get, makes one call with no session, and records one event so it is metered beside the turns. Anything on the machine that needs a single model answer uses that door instead of holding its own key.

The ship door

An agent that can edit code can also push it. In the terminal era it pushed from its own shell with whatever credential was ambient. Condensate has one route from a seat's work to GitHub. Every push, submit and merge runs through it, from a detached worktree the door creates, with the account pinned to the repo, and one audit line per command. Seats are denied those commands in their own shell, so there is no second path. A change reaches the main branch because someone chose to ship it, the log says who, and there was only one way through.

In code

You do not curl any of this. @condensate/agents-sdk is the typed client: spawn, send, tail, follow, stream, fork, merge, approve, seats, a round loop, and complete. A STEPS layer declares a whole workflow as data and runs it. The same type definitions are handed to an agent through the steering backend's codemode upstream, so an agent that needs ten things from Halo writes a loop instead of making ten tool calls.

Built on it

None of these are the runtime. They are what it makes possible, and each keeps only its own state.

Run it

git clone https://github.com/dextracker/condensate
cd condensate
bun install
bun run typecheck
bun test

cd packages/agents-host
bun src/main.ts                        # Halo, port 4741

curl -s localhost:4741/live            # live engine sessions
curl -s localhost:4741/workspaces      # the workspace registry
LayerPackage
Halo@condensate/agents-host
Loom, the gate, failure classes, routing@condensate/engine
the broker@condensate/broker
steering backendapps/middleware
the SDK@condensate/agents-sdk

The full design with file and line references is in the whitepaper.

Named for the Bose-Einstein condensate, where independent particles brought low enough act as one wave.