Making Agent-Written Code Durable
How Nine uses WebAssembly and replay to let JavaScript outlive its workers.
An agent's work rarely finishes in a single burst. A task might run for a few seconds, wait an hour for an external system or a human reply, and then run again. During that hour, the task is still in flight, but it needs almost no compute.
Meanwhile, the cluster around it is moving. Traffic spikes spin up new workers; quiet periods scale them back down. Deployments roll through, and occasionally a process dies.
If a task only lives in the memory of the worker that started it, every one of those events becomes a problem. You either pin workers open while they wait, or you lose in-flight work whenever capacity shifts.
To scale cleanly, Nine needed a simple rule: the work must outlive the worker.
What should survive the worker?
For a replacement worker to pick up an interrupted task, something has to carry the progress across. There are four common ways to do it:
- Keep the process alive: Local variables and the call stack stay in memory. This is the simplest model for short jobs, but long waits tie up capacity, and a crash still loses everything.
- Save application checkpoints: Persist state at explicit steps so a new worker can resume from the last marker. The saved state is easy to inspect, but someone has to define the steps and what to save—awkward when an agent is writing fresh code on the fly with its own variables and control flow.
- Snapshot the runtime: Capture the memory of the engine or VM and restore it elsewhere. That avoids custom checkpoint code, at the cost of moving larger snapshots and keeping runtime environments compatible.
- Replay from recorded inputs: Save only the external inputs the program observed, then run the same code again in a fresh runtime to rebuild its state.
Each approach fits a different problem. For agent-written JavaScript, Nine uses replay. Source code is small, and running it again reconstructs local variables and control flow automatically—provided the runtime can feed the code the exact same inputs it saw the first time.
A three-line script shows why that boundary matters.
Recovering the same ID and timestamp after a restart
Suppose an agent writes and runs this JavaScript in Nine:
const id = crypto.randomUUID();
const time = new Date().toISOString();
console.log(id, time);
Line one generates a UUID. Line two reads the clock at noon on November 6, 2026. Before line three can print, the worker stops.
With the worker gone, id and time vanish. If a replacement worker simply
reruns the script thirty seconds later, crypto.randomUUID() produces a
different ID and new Date() returns a later time. In a real task, that drift
breaks things: an ID might track work already started, and a timestamp might
decide which records belong in a report.
To recover the original run, the second attempt has to observe the same world as the first.
Nine makes this work by running QuickJS compiled to WebAssembly. Because the host controls the WebAssembly boundary, Nine supplies the engine's clock and random source—and records every value that crosses that boundary in a durable log before JavaScript sees it:
-
Random bytes requested by
crypto.randomUUID(). -
The clock reading requested by
new Date().
The agent still writes ordinary JavaScript. It does not need special
checkpointing APIs or durableDate() wrappers.
When the worker stops, the log survives. A replacement worker creates a fresh QuickJS runtime and starts the same script from line one.
id and time.
This time, when crypto.randomUUID() asks for random bytes, the host hands back
the bytes from the log. When new Date() checks the clock, the host returns the
recorded noon timestamp, even though real time has moved on.
By line three, the new runtime has rebuilt id and time purely by executing
the code. No heap snapshot or saved call stack was needed. The script reaches
console.log and prints:
7c92f068-9a14-4f20-a802-63e8d51ae401 2026-11-06T12:00:00.000Z
It is the exact output the first worker was about to produce.
Order is strict during replay: each host call must match the next entry in the log, or the runtime halts with a replay mismatch rather than feeding a saved timestamp into a random-byte read. Once execution passes the end of the log, it catches up to the present—a new clock read later in the script observes the current time and appends a fresh entry.
In this example, the worker stopped before console.log. Crashes after an
external action raise a separate question—whether that action already happened—so
replaying inputs is only one piece of recovery, not a claim of exactly-once
output on its own.
Ordinary code on replaceable workers
A three-line script is a small example, but it shows the property Nine needs for long-running work: execution state can survive a change of worker.
When a worker is replaced—by a deployment, a scale-down, or a failure—another worker can rebuild the script's state from the log. Because recovery happens in the runtime, Nine does not need to spend an extra model turn asking the agent to recover its old ID or guess when the task began.
That keeps the division of labor clean. The agent writes ordinary code, the runtime makes its observations reproducible, and the work keeps moving while the capacity underneath it changes.