I gave Pi one tool — Tom<br>Most of pi-fabric was written by a model. I directed it, stole a few good ideas, rejected plenty of bad ones, and kept asking the runtime to work on itself. The original thought was smaller: what if code mode made Pi programmable?
The first version took a weekend. Then I used it.
That part took longer.
The first thought
A coding agent usually gets a box of verbs. Read a file. Search text. Run a command. Edit something. Each verb has a schema, so the model chooses one, fills out the arguments, waits, reads the result, and starts over.
It works. It also means a language model performs control flow by talking through every step.
Cloudflare's code mode article gave me the initial shape. Turn the connected tools into a TypeScript API. Let the model write a small program against it. Run that program somewhere isolated. A loop becomes an actual loop. Parallel work becomes Promise.all. Twenty megabytes of tool output can become ten useful lines before the conversation sees any of it.
The TypeScript world has been heading this way for a while. REST exposed resources. GraphQL added a typed query language. tRPC and oRPC made remote calls look like ordinary functions, complete with compiler errors when you get them wrong. Fabric felt like the same move inside an agent harness. The tools became an API, and the model wrote the client.
Armin Ronacher reached a similar place from MCP. His single code tool keeps the things MCP already handles well: connectivity, authorization, discovery, and state. The command language becomes Python or JavaScript, which models have seen millions of times.
I wanted that inside Pi. So I gave the model one tool called fabric_exec.
fabric_exec accepts TypeScript. Pi's ordinary capabilities appear inside it as pi.read(), pi.edit(), pi.bash(), and the rest. MCP servers, extension tools, memory, state, child agents, persistent actors, and mesh operations all cross the same host bridge. The default runtime is a fresh QuickJS sandbox with no filesystem, network, process, or module globals. Approvals, timeouts, traces, and cancellation stay at the capability boundary.
MODELfabric_exec<br>TYPE-SCRIPTED INTENT01 read + search in parallel02 branch on evidence03 edit + verify04 return the proof<br>TYPE CHECK→QUICKJS→HOST BRIDGE<br>PIMCPBROWSERAGENTS<br>COMPACT EVIDENCE RETURNS<br>Generated control flow stays inside the sandbox. Every effect crosses the typed host bridge, and only selected evidence returns to the model.
The model writes something like this:
const [manifest, sources] =<br>await Promise.all([<br>pi.read('package.json'),<br>pi.find('*.ts', 'src'),<br>])
return {<br>package: JSON.parse(manifest).name,<br>sourceCount: sources<br>.split('\n')<br>.filter(Boolean).length,<br>Only the returned object reaches the conversation.
READload manifest01<br>READinspect source02<br>GREPnarrow evidence03<br>BASHcheck state04<br>EDITapply patch05<br>READreread change06<br>AGENTindependent review07<br>TESTverify behavior08<br>RETURNcompact evidence09
grounded context accumulatescompact result enters the conversation<br>A representative public coding trajectory, simplified from the aggregate operation mix. It is not a private transcript.
I figured this would mostly save context. It did. Then the programs started doing more than I had planned for. They branched on results, retried one bad step, reduced large outputs, and returned exactly what the next turn needed. The model had somewhere to do real control flow between turns.
Tool schemas have entropy
One of the bugs that pushed Fabric forward was almost funny. A model would produce the correct edit payload, then invent one extra field at the end of a nested object. The edit itself was fine. The harness rejected the envelope.
Armin's later investigation, Better Models: Worse Tools, put a name to the pattern. Tool calls are sampled text. A model can understand the task and still drift while reproducing an unfamiliar schema.
That bug shaped Fabric's outer interface. The model sees one deliberately flat schema: a large code string and a few scalar options. Inside the code it writes ordinary TypeScript objects. A wrong property gets a line number and a compiler diagnostic. The next attempt has somewhere precise to begin.
Generated TypeScript still fails. Commands return bad exits. Files move. Exact-string edits miss. The compiler and trace give those failures stable addresses, which makes repair much less mysterious.
Browserbase independently arrived at the same architecture in Code mode is all you need. Their useful phrase is that the tool surface moves down a layer. Types become the vocabulary. Policy sits below the generated code. Dangerous actions can keep narrow interfaces while routine work remains programmable.
That was close to what I had built. I had no idea how much more runtime it would demand.
Then it built itself
The first commit landed on July 12, 2026. It already contained a sandbox, an action registry, MCP, subagents, and a TypeScript checker. The diff was 7,694...