condensate

Three ways in

Build your own product on the Condensate runtime

The HTTP API, resumable streams, AG-UI and the SDK.

On this page

Condensate's own apps are built on the runtime: Dex, the browser app for your threads; the review hub, which runs review rooms; and Dojo, a tutor app. Your own product can be too, through the HTTP API the runtime serves on 127.0.0.1:4741.

What you get

Streams that resume exactly

Every thread is an append-only log in SQLite, and each event is stored before any stream reads it. The durable streams, tail, follow and agui, read stored events from a position you pass. A client that disconnects reconnects with the last position it stored and gets exactly what it missed.

Live text as a preview

The stream route carries text and thinking deltas as they are generated. It has no positions and replays nothing, and a slow reader loses its oldest frames. When a streamed block settles, its stored event replaces it.

AG-UI from stored history

The agui route serves a thread as AG-UI events, the open Agent-User Interaction protocol that chat front ends read. It folds stored events and live deltas together, so a client that reconnects gets the thread's history back.

One feed for a whole workspace

A change stream tells you which threads in a workspace moved, and to which position. It sends positions, not events, and a thread you subscribe starts with its current position.

A typed SDK

@condensate/agents-sdk is a TypeScript client for the HTTP API, built on the Effect library. It decodes every response with a schema and fails with one of three typed errors. It ships with early access and is not published on npm.

Code mode

Code mode is an opt-in MCP tool. An agent runs one short JavaScript program against typed namespaces in a QuickJS sandbox, instead of making one model round trip per tool call. Every run has caps, and each cap returns a typed error that names it.

Runners

A runner is where an agent process runs. Today that is a process on your Mac. Every run passes one admission check before it starts, so a run is never placed on a runner that would refuse it.

Context windows that keep the prompt cache

The runtime keeps the full history and reduces only what the model sees next, cheapest step first. The provider's view is rendered from the log, so two renders of one log are byte-identical and the prompt cache holds.

Worktrees you can trust

Every task gets its own worktree. The worktree keeper frees a worktree only after its pull request has merged or closed and it holds no unpushed work, and it never wipes a new run's files.

Failures that never replay a side effect

A run that fails after a tool has started does not retry. A host restart marks a run as lost and offers Resume instead of re-running it. The failure table at the end of this page lists every case.

How a stream reads the log

A stream keeps no state of its own. There is no Redis and no stream buffer to keep in step.

  1. The runtime stores an event.
  2. It signals every follower waiting on that thread.
  3. Each follower reads the log from its own position and sends what it finds.
  4. A client that reconnects sends the last position it stored, as after or as the Last-Event-ID header, and gets everything after it.
RouteWhat it carries
GET /threads/:id/tailOne page of stored events. after reads forward, before reads older events newest first. A page holds up to 1000 events.
GET /threads/:id/followStored events as server-sent events, from after or Last-Event-ID.
GET /threads/:id/streamLive deltas as server-sent events. A slow reader loses its oldest frames.
GET /threads/:id/aguiThe thread as AG-UI events, from after.
GET /threads/:id/agui-pageOne reverse page as AG-UI frames, for chat clients that open at the end and page back.
GET /workspaces/:name/changesWhich threads in a workspace moved, and to which position.
  • Deltas can be lost. Order comes only from stored events.
  • AG-UI sends usage in two parts. When a client connects, it gets the newest 200 usage records one by one and any older ones as a single total.
  • A change stream holds up to 256 threads from one workspace. Between two sends it keeps only the newest position for each thread. Each thread you subscribe starts with its current position, so a reconnecting client needs no extra tail read.

The thread streams are one-way server-sent events. The HTTP reference lists every route.

The SDK

The three typed errors are AgentsTransportError, AgentsRejected and AgentsDecodeError. The SDK covers listing, spawning, sending, following, forking, approvals, memory, review rooms and workflows. Use the TypeScript SDK has runnable examples.

  • A follower keeps one cursor across reconnects. follow uses the push route when it exists. When it does not, it polls tail with the same cursor, so no event is delivered twice across the change.
  • A 404 is read by its body. A missing thread is reported as gone, and a missing route as unsupported.
  • A workflow stops at a human gate. Each step's gate is auto or human. A loop waits at a human gate and returns the settled outcome instead of failing.
// The SDK ships with early access.
import { Agents } from "@condensate/agents-sdk"
import { Effect } from "effect"

// Store the last event position and pass it back as `after` on reconnect.
const resume = (threadId: string, lastPosition: number) =>
    Effect.map(Agents, (agents) => agents.follow(threadId, { after: lastPosition }))

Code mode limits

Code mode is off by default. CONDENSATE_CODE_MODE switches it on, and only the principals listed in CONDENSATE_CODE_MODE_PRINCIPALS can see it.

A program can reach only the threads it opened, the thread it runs in, and threads below that one in the thread tree. To check this, the runtime walks up at most 32 parent links.

Cap, per runDefault
Pure JavaScript time10 s. Time spent waiting on a host call does not count, up to a hard ceiling. The ceiling is 300 s for agent calls that wait on a model.
Tool calls20
Namespace calls100
Returned result64 KiB, counted in UTF-16 units

Nothing is cut short silently.

Runner states

The refusal from the admission check gives the same reason wherever the check fails.

RunnerStateWhat it does
ProcessAvailableRuns the agent as a process on your Mac.
Apple container VMIn developmentRuns a Claude agent in a VM. It refuses local plugins, session forks, middleware keys and MCP servers.
Container and KubernetesIn developmentThe Kubernetes runner takes Claude agents with no local plugins, no MCP servers and no fork, in the pod's empty /work.

In the Apple container VM, the guest sees /work/.git masked, and CAP_SYS_ADMIN is dropped so it cannot remove the mask. The runtime checks at boot that the VM network is off the internet. Egress is limited to the credential proxy by an operator pf rule that lives outside the code.

Context window steps

Each run has a finite input budget. The runtime moves to the next step when one frees less than 10%.

  1. Microcompact clears older large results that can be read again, and keeps the last 5 tool results.
  2. Collapse also clears older action results, and leaves a receipt of up to 200 characters for each.
  3. Summary keeps the newest 20,000 estimated tokens as written, and summarizes what comes before them.
  • Estimates are never scaled down. Token estimates are calibrated from measured usage on each run. An overcount only picks a deeper step, while an undercount would overflow.
  • Some results are never cleared by a cheap step. These are memory and control results, and any result under 1,500 characters. A cut cannot cross a child thread whose spawn has not yet joined.
  • Every timestamp comes from an event. The provider's view is rendered in event order, and no timestamp comes from the clock.

On Claude Code, the runtime switches off Claude Code's own auto-compaction and builds the context window itself. On Codex, it raises the native auto-compact limit so its own steps run first, and Codex can still compact if it overflows. Condensate's own agent runs the steps inside its loop. Context windows and memory has the detail.

Worktree rules

The worktree keeper is the part of the runtime that frees worktrees and watches free disk. It ships in observe mode. It reports what it finds, and acts only on the rules you set to act.

A worktree is held while its task runs, while its pull request is open, or while it holds unpushed work. Age does not matter. When the keeper's disk rule is set to act, new spawns pause while free disk is below 10 GB and resume once it is back above 30 GB.

  1. The keeper checks the pull request's state and any unpushed work.
  2. It checks again just before the worktree is returned.
  3. The return is conditional on the lease, so a new run's files are never wiped.
  4. A clone is archived and fingerprinted before it is removed.

What the runtime does when something fails

FaultWhat the runtime does
A socket drops mid-streamThe client reconnects from its stored position. The chat client gives up on a read that stays silent for 60 seconds, and retries from its last complete event.
The push route is missingThe SDK polls with the same cursor.
The database is reset under a feedThe Needs you feed reports that the store changed instead of replaying old events.
The host restarts during a runAt boot the runtime marks the run as lost, once, and offers Resume. Nothing re-runs by itself.
A run fails before any tool has startedThe run may retry once. After a tool has started it does not retry, so a side effect is never replayed.
An account refuses for quotaThe task moves to the next account in its workspace without re-running tools that already ran. A stream that has already sent text is not moved.
Every account in the workspace is outThe refusal names each account, why it is out, and the earliest reset. No account is tried twice.
A login is rejectedThe account is marked as needing sign-in, and the task moves to the next account. In a model failover chain, every remaining model on that engine is skipped, because they sit behind the same login.
A crash interrupts a token refreshThe rotated refresh token is saved to a recovery slot before the publish is retried, so the crash does not force a new login.
Two processes refresh at onceThe commit is a compare-and-swap. A rotation made elsewhere is adopted, not overwritten.
A new run claims a worktree during cleanupThe return is conditional on the lease, so it fails instead of wiping the new run's files.
The event loop stallsThe process exits after 120 seconds stalled while idle, or after 600 seconds otherwise.
A restart is requested while runs are activeThe drain answers new requests with HostDraining and lists the active threads. The restart script polls the drain before it restarts.
A GitHub write returns no readable resultThe write is not sent again. The runtime reads the result back from GitHub to settle the outcome.