Inspiration
We were given a working agent platform and told to pick one middleware layer and make it demonstrably better. We picked the Glass Box track (trace and audit), but we kept getting stuck on the same complaint about our own daily use of coding agents: a trace tells you what went wrong, and then leaves you holding a broken workspace.
That gap is the whole problem. When you hand an agent a long task, it edits twelve files across six subtasks, and something breaks at subtask five, your options today are bad. You can read a wall of logs. You can ask the agent to fix its own mess and watch it dig deeper. Or you can ask it to run git for you, which means version control depends on the agent remembering to use a tool correctly at exactly the moment it is already confused. Codex has no first-class revert, so undoing edits that have already landed is awkward by design.
So we asked a different question. Instead of "why did this fail", ask "where was this last known to be good, and can I go back there and branch". That reframing turned an observability feature into a recovery feature, and it is what the whole project is built around. We took the checkpoint, thread, time-travel and fork concepts from LangGraph as inspiration, but implemented them small and local instead of dragging a whole orchestration framework into a three-day build.
What it does
Rewind is middleware that sits between the control plane and the Codex runtime and gives the platform a version-control model for agent execution.
When you send a task from the Playground:
It snapshots first. Every run gets a stable trace ID and a pre-run checkpoint before a single token is spent, so there is always somewhere to go back to. It plans the task into stages. The middleware asks the runtime for a bounded JSON plan (1 to 8 stages, each with a title, a goal, and a reason to checkpoint there). Planning is read-only: no file edits, no commands. It runs one stage at a time. Each stage is a focused turn on the same Codex thread, with a prompt that says complete only this stage and stop at the boundary. It records evidence as it goes. Codex's raw JSON stream gets normalized into a small vocabulary of eight event types (orchestration, model activity, command execution, file change, validation, checkpoint, recovery, redaction), each with a sequence number the service allocates itself, plus status, timing, errors, and token usage where the runtime exposes it. It closes each stage with a checkpoint. A checkpoint is not just a folder copy. It captures the workspace files, the exact interval of execution events since the parent checkpoint, a verification status, redacted model-visible decision state (goal, completed work, evidence, assumptions, unresolved issues, next action), the parent checkpoint, a branch ID, and how to continue from here.
How we built it
We kept the starter kit's React and Fastify skeleton and spent all three days on the middleware layer underneath it.
The ledger. SnapshotManager owns an append-only execution ledger inside a single atomic JSON store. Sequence numbers are allocated by the service rather than derived from timestamps, because two events in the same millisecond are common and the UI needs a total order it can trust to reconstruct causality.
The runner seam. Rather than replacing the Codex runner, we widened its contract. AgentRunner picked up an optional plan() method and an onEvent callback, and both runtimes (the in-container CodexRunner and the disposable-container ContainerCodexRunner) share the same parsing code. parseCodexRuntimeEvent maps each raw JSON line into the safe event vocabulary, so the checkpoint graph is never coupled to Codex's exact stream format.
The checkpoint. Snapshots are recursive file copies with an exclusion list (.git, .codex, .harness, node_modules, dist, .env, *.log) and a 512 KB per-file ceiling, hashed with SHA-256 so diffs are content-based. Metadata lives in the JSON store alongside the ledger. Git stays available for file diffs, but it is optional and it is not the source of truth for execution state.
Redaction everywhere. A shared redaction pass runs over every label, reason, prompt, error, metadata object, decision state, and stage plan before anything is persisted or sent to the browser. It substitutes known secrets, strips Bearer tokens and KEY=value patterns, blanks any field whose key looks like a credential, and bounds strings to 2000 characters, arrays to 20 items, objects to 30 keys, and nesting to 4 levels. Bounded evidence is a security control, not just a performance one.
Safe persistence. The JSON store is versioned with real migrations (v1 to v2 to v3), backs up the old file before touching it, and serializes all writes through a queue. There is a test asserting that migrating a legacy database does not invent evidence that was never recorded.
The UI. The Playground timeline is stage-first. Stages are the primary nodes with status, verification badge, changed-file counts and duration, and raw runtime events expand inside a stage as supporting evidence. We only fall back to a flat causal-checkpoint list when no plan is available.
Process. We ran the whole build spec-first with OpenSpec: three changes, each with a proposal, a design doc, a capability spec, and a task list, written before the code. npm run check runs a secret scanner, typecheck, the Vitest suite, and a full build in one command.
Stack underneath: Node.js 22, TypeScript, Fastify with Zod validation, React 19 and Vite, Codex CLI against a Volcengine Ark Responses endpoint, and one disposable Docker, Colima, or Podman container per turn with dropped capabilities, no-new-privileges, and CPU, memory and PID limits.
Challenges we ran into
The model does not want to return clean JSON. Our stage planner asks for one exact JSON shape and gets back fenced code blocks, prose preambles, nine stages when we asked for eight, or stages missing a field. We wrote a strict extractor and validator, and more importantly we decided what happens when it fails: the run marks its plan status unavailable, falls back to the original single-turn flow, and records why. Never fabricate a plan the model did not produce. That rule shows up all over the codebase.
You cannot rewind a conversation. Restoring files is easy. The Codex thread is the hard part, because by the time you roll back the thread has already seen everything that went wrong and you cannot un-tell it. We ended up modeling continuation explicitly with two modes: aligned_thread when the checkpoint's thread still matches the agent's current thread, and fresh_seeded_thread when it does not, which starts a new thread from a redacted resume bundle built out of the checkpoint's goal and next action. It is honest about the seam instead of hiding it.
Deciding what "verified" is allowed to mean. Our first instinct was to let the agent label its own checkpoints as tested. That is exactly the kind of thing a judge should not trust, and neither should a user. Rewriting verification as a pure function over the events in the checkpoint's interval was the single best decision we made, and it made the failing-stage demo work honestly.
Ordering and correlation. Getting stage boundaries, checkpoint intervals and event sequences to line up took several passes. A stage records the sequence it started after and the sequence its checkpoint closed on, and every query (evidence, verification, usage, recovery recommendation) is a filter over that interval. Once the interval model was right, most of the remaining features became small.
Cost of snapshots. Copying a workspace at every stage boundary gets expensive fast. Exclusion rules, the file-size ceiling, a cap of 12 checkpoints per run and 8 stages per run keep it bounded, and rejected checkpoint requests are logged rather than silently dropped.
Resisting scope creep. The brief is explicit that a narrow feature that works end to end beats three half-built ideas, and that finishing one track matters more than touching all three. We wrote down non-goals early (no LangGraph dependency, no multi-user merging, no chain-of-thought persistence) and kept pointing back at them.
What we learned
The most useful thing we learned is that observability and recovery are the same feature seen from two angles. We started building a trace viewer. We finished building a version-control system, and the trace fell out of it for free, because once you know which interval of events produced a given workspace state, you have both the explanation and the restore point in one object.
We also learned how much design work goes into a data model that refuses to lie. Every field that could be fabricated needed a rule for what happens when the evidence is not there, and answering that consistently is most of what makes the output trustworthy.
On the practical side: normalizing a vendor's event stream into your own small vocabulary at the boundary pays for itself immediately, sequence numbers beat timestamps for causality, and writing the spec before the code (thanks OpenSpec) kept a three-day build from turning into a three-day argument.
Built With
- colima
- docker
- fastify
- git
- mermaid
- node.js
- openspec
- podman
- react
- responses-api
- terraform
- tsx
- typescript
- vite
- vitest
- volcengine-ark
- volcengine-ecs
- zod
Log in or sign up for Devpost to join the conversation.