Changelog

A new editor, now on Cloudflare

Jun 8, 2026·15 min read

This was a big infrastructure week. Soot moved its entire production stack onto Cloudflare, swapped the editor’s whole language layer, taught the agent factory to run in hosted cloud sessions, and ran its first real multi-user load tests.

Soot is now fully Cloudflare-native

Soot has completely migrated off its self-hosted box and onto Cloudflare. The production web app now serves from a Cloudflare Worker at contrast.dev, the data layer lives in Durable Objects, and user-app builds run on Cloudflare Containers.

The last piece this week was the user-app build runner — the thing that runs the full bundler on every deploy. It moved from the VPS to a Cloudflare Container bound to the apex worker via a service binding, and we verified it produces byte-identical build output to the old path. The Cloudflare deploy was then folded into the main deploy pipeline, running in parallel with end-to-end validation and flipping live only on green.

We deleted roughly 1,850 lines of deploy, health-check, and SSH machinery. One Cloudflare-native deploy path remains. The box can be powered off.

migratedAfter Cloudflare edgeservice bindingservice bindingApp Workercontrast.devData WorkerDurable ObjectsBuild RunnerCF ContainerBefore single boxVPSweb app + data + builds

A two-worker data tier ends the bootstrap OOM and slashes auth latency

The core blocker for the Cloudflare backend was Durable Object isolate out-of-memory crashes that made every bootstrap time out. A single shim was trying to bundle both the React app and the data tier into one 128 MiB isolate.

We split it: a lean data worker bundling only the Zero SQL/cache Durable Objects (~4.5 MB), and an app worker that calls it over a service binding. That gave the data tier its own clean 128 MiB of headroom. A 1-minute cron keeps the auth Durable Object resident instead of cold-starting on real requests, and the /api/auth/me reads were batched into a single data-tier hop.

Auth path — before vs afterBootstrap (before)36s — OOMBootstrap (after)40msSign-in (before)~10sSign-in (after)<1s/api/auth/me (before)1.5–3.6s/api/auth/me (after)<1slower is better; before in red, after in green

Runaway Durable Object loop, and the circuit breaker that now guards it

A synthetic 5-second replication heartbeat in the Zero cache Durable Object was continuously re-streaming the retained change set even when nothing was being written. Standby feedback never converged, so the same 2.3 GB of rows were rewritten in an infinite loop — burning compute on billions of redundant writes before anyone caught it.

The fix makes replication entirely write-driven, matching the upstream event-driven shape, so it only fires after real app writes. We then layered in defense-in-depth so a regression like this trips loudly instead of looping silently:

noyesapp writecircuit breakerover 2M rows/min sustainedor 10M instantaneous?replicate (write-driven only)refuse writes, alert loudlyhourly write-rate watch +daemonelevated rate alertsustained runawayauto-shutdown

Post-fix steady-state write rate is about 70k rows/day — down from billions.

Monaco is gone — CodeMirror 6 with an in-browser TypeScript language server

We deleted the entire Monaco editor surface and replaced it with a CodeMirror 6 pane backed by a purpose-built, portable TypeScript language-service kernel. The kernel wraps the TypeScript language service over an in-memory virtual filesystem and speaks LSP over JSON-RPC, so it runs identically in-process under bun for tests and in a Web Worker for the editor.

Every Monaco parity feature was ported: agent-watch and AI-highlight line decorations, tab-aware cross-file jump-to-definition, auto-fold imports, Cmd+S save-and-format, a per-file editor-state cache that preserves undo history and selection across tab switches, and the batsignal theme with proper syntax highlighting. The heavy monaco-editor packages were dropped entirely.

In-editor diagnostics now land in ~70ms, even on busy waves

Two things were holding back inline TypeScript diagnostics. First, three boot-ordering races silently killed diagnostics when a file opened before the project ID arrived — so a freshly opened editor often showed no errors at all. Second, the warm typecheck program cache had a fixed 1.5 GB budget, so parallel branch lanes evicted each other and fell back to cold 8–10 second builds while gigabytes of RAM sat idle. The budget is now sized to one-eighth of physical RAM.

Type-check latencyCold seed (before)95–130sCold (warm container)4.8sWarm program440msSecond lane105mswarm container, same native compiler engine as the deploy gate (ms)

Cloud factory: run the agent build loop in a hosted Cloudflare session

The agent factory can now run entirely in hosted cloud infrastructure instead of requiring a local Chromium process. A per-project Cloudflare Container acts as the supervisor and runs the existing agent work manager inside a real headless Soot session: locally it launches Chromium, and in the cloud it connects over CDP into a Cloudflare Browser Rendering session.

localcloudagent work managerruntime?launch ChromiumCDP to CF BrowserRenderingshared supervisor10s heartbeat · 90sreadiness gatecrash recovery: up to 20relaunchesheadless Soot session

We validated that the Soot client boots correctly in CF Browser Rendering with cross-origin isolation, SharedArrayBuffer, IndexedDB, and nested workers all available. The project menu gained a Cloud Factories manager showing named instances and their active branches.

Orchestrator prefix-cache thrash fixed — cache-hit jumps from 68% to ~96%

The orchestrator agent’s context compactor recomputed its drop boundary from the full transcript on every request and never persisted it. So once the context ceiling was crossed, the position-0 summary message changed every turn, resetting the provider’s prefix cache on every single orchestrator call. One measured wave saw 114 cache resets on the orchestrator versus near-zero for every other role.

We rewrote the compactor as a stateful object that memoizes the drop boundary per session and uses ceiling/floor hysteresis to hold a stable cached prefix.

Prompt cache-hit rate by agent roleOrchestrator (before)68.5%Orchestrator (after)96%Worker97%Reviewer98%orchestrator was the lone outlier eating $1.22 of a $1.99 wave (%)

Factory testing now auto-graded by two independent LLM judges

A model-efficacy evaluation system now scores every factory wave on quality-per-dollar. Deterministic gates produce a machine verdict, and a new subjective grader sends each wave’s captured app screenshots to two independent strong models for screen-by-screen design and quality grading against a shared rubric. Results assemble into a durable scorecard store, so any wave is re-gradeable forever from its stored screenshots without re-running the factory.

Multi-user load testing: green at 50 and 100 concurrent users on Cloudflare

A new Playwright-based load and longevity harness validates the class of failure that production smoke tests can’t see: projects that have lived through many writes, accessed by several real users at once. It gates five scenarios — collaborative invite-and-accept, churn writes with degradation measurement, real-time fan-out to two live browser clients, post-churn cold-mount stability, and concurrent-user Zero sync. Every gate held green — first at 3–5 users, then under sustained pushes to 50 and then 109 concurrent users on both Cloudflare and the dedicated target.

Editor

  • The TypeScript compiler is now lazily loaded so the Cloudflare app worker stays under the 10 MiB limit (10.45 → 9.07 MiB gz), keeping previews alive on Cloudflare.
  • Eliminated 19 false GPU-type errors in the game template by reading the tsconfig types array and acquiring ambient packages like @webgpu/types and @types/node.
  • Single-file type checks no longer scan the whole project on every keystroke, and the assembled editor-type declaration bundle is memoized per package closure.

AI Factory

  • Designer and QA roles split so they stop sharing blind spots: the designer gets an explicit right-by-construction mandate (one primary creation affordance per screen, every CTA resolving to a real destination, an auth entry always present), while QA is now adversarially runtime-proof only, with four named gates that each require a measured tap or layout result.
  • Chat-driven factory: drive the whole build through Soot’s chat UI, with a simulated user that answers clarifying questions off a persona card. Wave closeout now triggers a real deploy and requires the served HTML to return 200 with the spec’s brand present — finally proving dev-to-prod parity.
  • Hot module swap for query/mutation edits (was ~100 restarts + dozens of desyncs per wave, now zero of each), record-presence runner spawning, and a 15s per-agent crash cooldown (was ~40 restarts/sec on a push-endpoint blip).
  • Corrected wave cost reporting (DeepSeek rates were ~1.9× overstated; a real wave now prices at $1.71 instead of $3.30), and established a clean Flash-model baseline: three consecutive waves passed at ~26 minutes for ~$0.40 each.
  • Added Kimi K2.7-Code (1T-parameter coding MoE, 256K context) as a selectable factory model; removed Nemotron.

Collaboration

  • The agent’s on-screen cursor was redesigned as a clean arrow and generalized into a per-owner registry — the agent in brand amber, deterministic per-user colors — ready for the upcoming live-presence layer.
  • When the agent presents a pane, it can glide its cursor to a specific element and pulse a short label, directing your eye to exactly the thing it’s describing, and posts a chat message explaining why the stage moved.
  • Stage targets expanded from one to four (factory chat, factory floor, kanban, and spec), with the spec target deep-linking straight to the product spec document.
  • Added a standalone full-page app preview route — a clean, unobstructed view to open a project’s running app in a new tab.

SootSim

  • sootsim.com and contrast.dev are now separate sites, each with its own route root selected by a build variable. This retires the old build-time route-stripping dance and the recurring class of login-404 and wrong-wordmark bugs.
  • SootSim login no longer flashes your name then signs you out. The first authenticated check could 401 on a cold auth Durable Object before a freshly minted session replicated; the client now uses bounded backoff retry and only clears the session if every attempt fails — never on 5xx or network errors.
  • Hashed SootSim engine bundles, fonts, and WASM assets are now served immutable on Cloudflare, dropping the 304 revalidation round-trip on every visit.

Reliability

  • Fixed stale entry HTML being served for up to 10 minutes after a deploy by removing stale-while-revalidate, bounding edge staleness to 60 seconds.
  • Added a live backup/restore drill that exports prod Durable Object data and restores it into a throwaway namespace, asserting exact row parity (64,240 rows round-tripped clean) so a backup regression goes red in CI.

Fixes & polish

Beyond the headline work, 394 fixes and 65 performance improvements landed this week. Most are small and self-explanatory; the ones worth calling out: the merged source tree is now parse-gated before flipping main (a bad conflict stitch can no longer ship a silent SyntaxError that takes the web mount down), the contamination gate stopped false-positiving on schema-build churn, and the dev-stack teardown now reaps only its own process tree instead of a blanket sweep.

Ready?

Create a web, iOS, and Android app in minutes with agents working alongside you.