cd /news/ai-agents/i-gave-pi-one-tool-and-it-ran-for-ei… · home topics ai-agents article
[ARTICLE · art-69843] src=monotykamary.com ↗ pub= topic=ai-agents verified=true sentiment=· neutral

I gave Pi one tool and it ran for eight days

Infrastructure developer Earendil Works gave its Pi coding agent a single tool called fabric_exec, which accepts TypeScript programs that call Pi's capabilities as an API, and one such program ran continuously for eight days. The approach, inspired by Cloudflare's code mode and Armin Ronacher's single-code-tool MCP design, replaces multiple tool schemas with one flat TypeScript interface, reducing context waste and enabling real control flow between turns. The design emerged from a bug where models invented extra fields in nested tool schemas, leading to a deliberately flat schema that returns compiler diagnostics for precise error repair.

read14 min views1 publishedJul 23, 2026
I gave Pi one tool and it ran for eight days
Image: source

Still testing this

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.

The model writes something like this:

const [manifest, sources] =
  await Promise.all([
    pi.read('package.json'),
    pi.find('*.ts', 'src'),
  ])

return {
  package: JSON.parse(manifest).name,
  sourceCount: sources
    .split('\n')
    .filter(Boolean).length,
}

Only the returned object reaches the conversation.

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 inserted lines. I looked at it, decided it was probably too much, and immediately started using it to write the next version.

Twelve days later the public history contained 337 commits. The current tree has 49,569 source lines and 32,059 test lines across 98 test files. Most of it came from models working through Fabric while I chose the direction, reviewed the output, and sent bad ideas back.

If you read the commits in order, you can see the model and me running into a new problem every few hours.

Real extension tools exposed lifecycle gaps. Models invented harmless display keys and broke the outer schema. Long sessions forgot why they had opened a file. Separate sessions repeated investigations that had already happened. Agents stepped on each other's work. Handoffs flattened a day of exploration into a neat little paragraph that no longer carried the evidence behind it.

Most of those problems turned into features: schema filtering, deterministic compaction, cross-session recall, actors, mesh state, a participant directory, trajectory-preserving delegation, and eventually Prewalk.

I drew the clean architecture diagrams after the messy part had already happened.

The harder parts started with erasure #

On July 17, I dropped three things into one session: Schema Harness, its ARC-AGI-3 traces, and my slightly ugly compaction extension called pi-vcc.

Schema made me wonder if an agent could keep a labeled world state, transition it as facts changed, and spend test-time compute against that state. Pi-vcc had already survived more than twenty compactions without losing the task. It also had years of techniques plastered into one place, including regex tricks I did not want to carry forward.

I told the model this might be nonsense. I wanted programmatic compaction, a search engine over every Pi session, and a state layer with labels and transitions. I was not sure those ideas belonged together. That was the useful bit of confusion.

The pieces eventually became separate APIs. memory.*

searches session logs and can expand the exact source entries later. state.*

records labeled transitions and attached evidence. schema.*

adds a stricter hypothesize, verify, and commit path. compact.*

lets the model request compaction for itself or a Pi subagent.

I kept coming back to one rule: the transcript is the source of truth. The indexes and summaries are disposable views. Fabric reads typed roles, tool names, call IDs, paths, outcomes, and entry addresses. It does not rummage through prose with a bag of regexes and hope that something looks like a file or an error.

I was also unsure whether this state machinery belonged on the mesh. It ended up feeling a lot like the open task

tool in other coding harnesses. The model can call it whenever the work reaches a meaningful boundary. The records live on the mesh, so another session or actor can inspect the same transition later.

Around the same time, Taelin posted an argument about erasure. Models are trained to add: another answer, another file, another fix. They get much less practice throwing information away. He called out compression, garbage collection, and the ability to remove bad knowledge. For code, he suggested counting branches and rewarding reductions that preserve behavior. His definition of an abstraction was useful here: a compact thing that lets you throw details away because it can expand back into them.

That point stuck. Fabric's state layer now counts decision points in TypeScript and JavaScript. A claimed reduction needs attached evidence, and it remains pending until verification passes. Representation transitions can archive an old view of the state. Compaction rebuilds its summary from raw typed history each time, so it can erase the live context without treating an earlier summary as truth. Its reconstruction QA removes projected files, errors, turns, and recall pointers to make sure the loss is detected.

This does not teach a model how to forget. It gives the runtime a few places where forgetting is deliberate, inspectable, and reversible when the evidence fails.

Compaction needed one more rule. A model should not be able to rewrite the context underneath its own running turn. compact.request()

only records an advisory intent. The host commits it at agent_settled

, when the turn is actually done. Subagent compaction follows the same pattern at the child's safe boundary.

Fabric's deterministic compactor became the default and takes precedence over pi-vcc for automatic compaction. An explicit /pi-vcc

command still reaches pi-vcc. I wanted the clean implementation to win by default without deleting the escape hatch that had already proven itself.

The first implementation happened across parallel agent worktrees. Then I asked Fabric to recall the session and check whether the model and I had actually resolved my confusion around Schema, compaction, and memory. The answer was no. I sent the agents through another round to remove shortcuts, fix the two-extension precedence rules, and chase the GitHub CI failures until the result held together.

That founding session was also one of the first to run on Fabric's new compaction while the model and I were still changing it. This seems like a bad way to test context continuity. It ended up being a pretty good one.

11,155 programs later #

Eventually I wanted to know what I had actually been doing. I aggregated 146 local pi-fabric session files covering July 13 through July 23. The script counted metadata, usage, tool names, and trace structure. Prompts, command payloads, file contents, and private project names stayed out of it.

Operation traces arrived partway through the build, so the deeper sample covers 6,641 programs. They contain 14,114 nested operations. The median program has one. The 90th percentile has four. One particularly determined program has sixty.

I figured code mode would produce giant autonomous scripts. It mostly produced small ones. This was a relief. They only grew when the work called for it, and 41.5% of the traced set contain multiple operations.

The calls themselves are pretty boring: 4,441 shell commands, 3,919 reads, 2,977 edits, 1,509 searches, and 305 writes. Another 158 launch child agents. This is regular coding work with a programming language wedged between model turns.

It also failed plenty. Of the traced programs, 5,959 succeeded, 667 failed, 9 timed out, and 6 were aborted. Shell commands and exact-string edits caused most of the damage. At least the failures came back with a trace and a line number.

Reading is still the bill #

Those sessions record 93.4 million fresh input tokens, 1.76 billion cache-read tokens, and 10.4 million output tokens. Branches, retries, and long sessions all contribute to those numbers.

Reading dwarfs everything else.

The recorded metered cost for the pi-fabric sessions is $1,309.73. One subscription-backed GLM provider reported usage without a dollar amount. Pricing that usage at Z.ai's official GLM-5.2 API rates adds $186.76: $1.40 per million fresh input tokens, $0.26 per million cached input tokens, and $4.40 per million output tokens.

The result is roughly $1,496.49 in equivalent API usage. My cash invoice was lower because subscription pricing works differently.

I spent about fifteen hundred dollars of model work asking whether one programmable tool could carry a real codebase. It could. By then it had also invented several reasons to keep building the runtime.

The browser became programmable #

The first real test outside Fabric itself happened in a browser.

The browser sessions started running longer without needing more from me. Median active time went from 7 minutes to 23. Follow-ups per 100 model turns fell from 7.69 to 3.77, and the typical session needed one fewer message from me.

I had used browser tools before. They were good at isolated verbs: click this, read that, take a screenshot. Then I tried a workflow built around a React Flow canvas. It needed retained tab state, DOM inspection, coordinate input, version changes, publishing, and a final check that the visible graph had actually changed.

The seams showed immediately.

Fabric started writing programs against browser-harness-js

and Chrome DevTools Protocol. It created isolated targets, attached sessions, armed lifecycle waits before navigation, queried the DOM, and used screenshots when the structure ran out. It could inspect a version, change the graph, publish it, reopen the result, and check its own work without losing the browser halfway through.

You would figure that a REPL like browser-harness-js

inside another REPL would cause more trouble, but it ended up just fine.

The anonymized subset contains 21 relevant sessions and 180 successful traced executions. Somewhere inside those traces is a model learning that React Flow nodes have both a DOM representation and a place under the mouse.

The commercial target is private. The part I can talk about is how uneventful it became. I had never kept this workflow alive through ordinary one-action tools. Fabric finished it, then did it again.

The browser was already programmable. The agent finally got to use it that way.

Then it touched a printer #

Then one session refused to end.

It stayed alive for roughly eight days: 2,586 model inferences, 2,186 Fabric programs, 3,489 traced nested operations, and 22 compactions. It started with image generation. A few days later I was cleaning a printer bed and sending photos of failed plastic back to the model.

The project was a character cable holder. The model produced meshes, printable candidates, and several confident assumptions that did not survive contact with plastic.

A tree-support coupon failed. The revision failed differently. A normal support wall then collapsed twice at the same place. I cleaned the plate. I checked extrusion. The brim remained stuck to the bed, which ruled out the easy explanation.

The support body above it had narrowed to 4.191 millimeters for more than twelve millimeters of height.

This was the moment the session became interesting. It kept the failed artifacts, took the physical evidence seriously, and changed the validator. The new audit rejects sustained narrow rooted walls. The full nine-hour print stayed blocked. The next thing sent to the printer was a 47-minute coupon containing the disputed feature.

Context filled up twenty-two times during that session. I kept waiting for the agent to forget what it was doing. It never really did.

I checked the trace afterward. The median context before compaction was 340,253 tokens, with another 128 model inferences before the next one. Eighteen compacted segments later made another Fabric call. Every first call worked.

There was no reorientation ritual. The agent went back to the current experiment and carried on. It felt a lot like OpenAI Codex compaction, where you barely notice the boundary while you work.

The physical workload represents another $506.76 in equivalent API usage, including the same upstream estimate for subscription-backed GLM calls.

What stayed with me was the caution. The agent had spent enough time with one failure to stop gambling on the full print.

The strict part lives below the code #

Giving a model arbitrary code execution is obviously a little alarming. Fabric's default QuickJS guest has no direct filesystem, network, process, or module access. Every effect has to cross the host registry, where arguments, approvals, traces, timeouts, and cancellation still apply. Fabric also has a Node process executor for jobs that exceed the WASM memory ceiling. The UI calls it unsafe because it is.

I trust Fabric on my machine because I can inspect that boundary and the work lives in version control. On somebody else's production system, I would make the approvals annoying on purpose. Expressive code needs stingy capabilities.

What I think now #

Cloudflare showed that models can call tools through code. Armin showed why one code-speaking MCP can beat a catalog of tiny verbs, then traced failures back to schema sampling. Browserbase reached the same layer through production browser agents. Stencil showed how much grounded work disappears inside a plan.

Pi-fabric is what happened when I put those ideas inside a small coding harness and kept fixing whatever broke next.

Pi still sees one tool. Underneath it is now a runtime for typed capabilities, isolated execution, agents, memory, state, traces, and handoff. Most of the runtime was written through itself. So far it has survived more than eleven thousand generated programs, one production browser workflow, and a printer that kept making spaghetti.

I started with a small question: what if code mode made Pi programmable?

The answer cost about two thousand dollars of equivalent model work and twelve fairly intense days. I now want an agent harness to give the model a language inside the boundary, keep failed work around, and let useful context survive the next model.

For now, I am going to keep using it until it gives me another reason to change it.

Have a lookmonotykamary/pi-fabric / main

── more in #ai-agents 4 stories · sorted by recency
── more on @earendil works 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/i-gave-pi-one-tool-a…] indexed:0 read:14min 2026-07-23 ·