Skip to content

How it is built

A request enters this system as one message and leaves as one message. Between those two moments it descends through layers, and each layer exists to make the one under it affordable or safe to reach. This section follows that descent. Every chapter is written from the code it describes and names the file that owns a behaviour, so a claim here has a file behind it.

flowchart TB
  host["your host<br/><small>declares tools, decides bounds</small>"]
  adapter["adapter<br/><small>@supolka/cloudflare-codemode-executor</small>"]
  executor["IsolateExecutor<br/><small>the native contract, in process</small>"]
  pool["worker pool<br/><small>supervises children, queues, retires</small>"]
  child["child process<br/><small>one execution at a time by default</small>"]
  sandbox["sandbox<br/><small>builds the isolate, holds the bounds</small>"]
  runtime["runtime<br/><small>the standard library, from the specifications</small>"]
  isolate["V8 isolate<br/><small>empty global, fresh per execution</small>"]

  host --> adapter --> executor
  host --> executor
  executor --> pool --> child --> sandbox --> isolate
  runtime -. installed into .-> isolate

Both sides of every boundary implement the contract, a package with no dependencies. It carries the wire types, the codec, and every failure the executor declares as a class that composes its own text. But nothing in it names a runtime, and a host on any JavaScript engine can implement against it. So it is published as the executor protocol and not described as an internal.

One request becomes one isolate in the sandbox. It restores a standard library from a startup snapshot, installs the capabilities the request granted, compiles an entry module around the program, races that module against the run clock, and disposes what it built when the run settles. The sandbox chapter walks a single execution through all of it. That standard library is large enough to be a chapter of its own.

Turning that into a service is the pool’s work. It owns the child processes, decides which one serves a request, and replaces a child that crashes or wears out. Overload becomes a typed fault, not a growing queue. The pool chapter covers all of it.

A socket in front of the pool is the server. It carries a caller’s own functions across two process boundaries, so a program running on another machine still calls tools that run in the caller’s process. The server chapter covers it and the daemon around it.

Only the adapter speaks a foreign dialect. It serves the @cloudflare/codemode shape over the native contract, and the same package holds the client that reaches a daemon over a socket. Its chapter is where every convention of that dialect is written down.

flowchart LR
  subgraph parent["host process"]
    caller[caller code]
  end
  subgraph worker["child process"]
    direction TB
    supervisorside[sandbox]
    subgraph iso["V8 isolate"]
      guest[the program]
    end
  end
  caller -- "request, one message" --> supervisorside
  supervisorside -- "result, logs" --> caller
  guest -- "tool call: three strings" --> supervisorside
  supervisorside -- "envelope: one string" --> guest
  supervisorside -- "tool call" --> caller
  caller -- "answer" --> supervisorside

Two bounds live at the isolate boundary: the memory limit and the interrupt. Every allocation the program makes lands in the heap of one V8 isolate, created under the request’s limit by isolated-vm. A run’s deadline is measured from outside that isolate, so a program that cannot be interrupted still settles.

Behind the isolate, the process boundary bounds the cost of a native crash: one worker, one caller, a typed fault the caller may retry, and a replacement child. That boundary is also a ceiling on the size of one message, so every message a program sends outward is measured where it is built, not where it is sent. Where a deployment has a network boundary, it carries the same contract, and the caller’s own functions travel across it with the request. What a boundary is claimed to hold, and what it is not, is stated in the threat model. Every run of the gate measures that the claims still hold, under the method of the benchmark.

Where to start depends on the question. Questions about one program go to the sandbox, questions about load to the pool, and questions about what a program can call to the runtime. But the server and the adapter begin to matter once the executor sits behind a socket, or behind somebody else’s dialect.