Back to work

Case study · Developer tool · AI agents · 2026

Pico
as state machines.

A browser workspace for durable coding-agent sessions. The agent keeps its work when the stream drops, the tab sleeps, or the server restarts.

The problem

A coding agent can work for many minutes. During that time, the phone that shows it can lock, the Wi-Fi can drop, and the tab can reload. The server can crash, or restart after each rebuild.

After each of these events, the screen must show the true state of the session. And an agent that already sent a notification, started a job, or started a child session must not do it again.

Ali's role

Ali made Pico: the product concept, the interface design, the server, the web client, and the local-first architecture.

Pico runs its agents on the open-source pi-durable runtime (@earendil-works/pi-durable). The runtime stores each step of a run in SQLite and continues unfinished runs when it opens again. Pico adds the live event stream, the reconnect logic in the browser, and its own agent tools, which follow the replay rules of the runtime.

The state machines

Each diagram comes from the same data as the tables under it. The tables give the same facts as text.

Machine 01 · 6 states · 12 transitions

The live event stream

The browser gets all live updates through one Server-Sent Events stream. Each event has an ID. The first message on a new stream is always a full baseline of the session. After that, the server sends only patches against what the browser already has.

The live event stream: state diagram. The tables below list the same states and transitions.

States

Connecting
The browser opens the stream. It sends the last event ID that it saw, from memory or from session storage.
Replaying
The server sends the events that the browser missed, from a window of the last 512 events. It skips old state events, because the baseline replaces them.
Baseline
The server sends the full state of the selected session and the session list. Events that arrive during this step wait in a queue.
Live
The server sends each new state as a patch: the start, the delete count, and the new items. Streamed text arrives as deltas.
Checking
The stream failed. The browser asks the health endpoint whether the server is up.
Unreachable
The server does not answer. The page shows one “server unreachable” view, not one error for each request.

Transitions

Transitions of the live event stream: the state before, the event, and the state after.
FromEventTo
Startpage opensConnecting
Connectingstream opens with a last event IDReplaying
Connectingstream opens without a last event IDBaseline
Replayingmissed events sentBaseline
ReplayingID too old, resync noticeBaseline
Baselinequeued events deliveredLive
Livepatch or deltaLive
Livestream errorChecking
Livetab back after 30 s, stream closedConnecting
Checkingserver answersConnecting
Checkingno answerUnreachable
Unreachableretry timer (1, 2, 3, 5 s)Checking

Machine 02 · 6 states · 9 transitions

One tool call across a crash

Each tool call is a durable task. The runtime stores the intent of the call before the tool runs. If the process dies during the call, the next process decides what to do from the replay policy of the tool. Pico marks its tools with external effects as replay-safe, and each of them checks a stored memo before it acts.

One tool call across a crash: state diagram. The tables below list the same states and transitions.

States

Intent stored
The runtime stores the call before the tool runs.
Running
The tool does its work.
Result stored
The result goes into the transcript.
Cut off
The process crashed or restarted during the call.
Interrupted
The tool is not replay-safe. The model gets an “interrupted” error with the output so far, and decides what to do.
Rerun
The tool is replay-safe. It runs again from the top and first reads its memo.

Transitions

Transitions of one tool call across a crash: the state before, the event, and the state after.
FromEventTo
Startmodel calls a toolIntent stored
Intent storedexecuteRunning
Runningtool returnsResult stored
Runningcrash or restartCut off
Cut offreopen, tool is not replay-safeInterrupted
Cut offreopen, tool is replay-safeRerun
Rerunmemo found, return the earlier resultResult stored
Rerunno memo, do the workRunning
Interruptederror result storedResult stored

What can go wrong

  1. Failure 01

    The live stream drops

    A phone locks, the network changes, or a proxy returns a 502. Events that the server sends during the gap do not arrive.

    How the design handles it

    • Every event has an ID, and the browser keeps the last one in session storage. A new stream sends it back, and the server replays the missed events from a window of 512.
    • Event IDs start from the clock when the server starts, so they keep growing across restarts. An ID from before a restart gets no replay; the baseline covers it.
    • When the window no longer covers the ID, the server says so in a resync notice and sends the full baseline. The browser never applies a patch to the wrong base.
    • After the baseline, state changes go out as small splices: the server finds the common start and end of the item list and sends only the middle.
    • The replay window keeps only the newest state event of each session, so old full states do not push out the small events.
  2. Failure 02

    The server is down

    Every request fails at the same time. Without care, the page shows one error toast for each failed request.

    How the design handles it

    • One small store holds the connection state: unknown, online, or unreachable. A failed request or stream asks the health endpoint once.
    • While the server is down, the store checks again after 1 s, 2 s, 3 s, and then every 5 s. The page shows one “server unreachable” view.
    • When the server answers again, the page opens a new stream with the saved event ID and loads its data again.
    • When a tab comes back after 30 s or more in the background and its stream is not open, it reconnects at once.
  3. Failure 03

    A run is cut off by a crash or restart

    The agent is in the middle of a run when the process dies. The work must not be lost, and it must not start from zero.

    How the design handles it

    • Sessions live in one SQLite database. The pi-durable runtime stores a checkpoint at every step of a run and continues unfinished runs when Pico opens again.
    • Pico keeps tool state in documents, not in server memory, so a restart does not lose a todo list, a timer, a question, or a child-session link.
    • A timer that was due while the server was down fires one time at startup. Its follow-up message has a fixed request ID per run, so a second delivery is refused.
    • Terminals and background jobs run in a separate pico-pty-host process. After a server restart, the server takes over the jobs that still run and records the ones that ended. A job with no process is marked “lost” and reported, not shown as finished.
  4. Failure 04

    A tool with side effects runs twice

    A replay-safe tool reruns after a crash. Without a guard, it sends a second notification, starts a second job, or makes a second child session.

    How the design handles it

    • Each tool with an external effect checks a memo first. If the memo has a result, the tool returns it and does nothing else.
    • A rerun of job_start reports the job that it already started. It never starts the command again.
    • A rerun of spawn_session finds the child that the same tool call made. The first message to the child has the request ID spawn:<call ID>, and the runtime returns the existing submission for a request ID that it already has.
    • One gap stays open: the effect and its memo are two steps. A crash between them can repeat the effect. Where the effect is a message to a session, the fixed request ID closes the gap.

Numbers

Only numbers from the code or from a count. Each one says where it comes from.

events in the replay window
512
MAX_REPLAY_EVENTS in apps/pico/server/src/events.ts.
between health checks while the server is down
1, 2, 3, 5 s
RETRY_DELAYS_MS in apps/pico/src/lib/pico/server-connection.ts.
in the background before a tab reconnects
30 s
RESUME_RECONNECT_AFTER_MS in apps/pico/src/features/pico/use-app-shell-session-sync.ts.
server test cases in 26 files
121
Counted test( declarations in apps/pico/server/test with grep, at commit abae556, on 27 October 2026. Counted, not run.

Next case study

Whimcall