Codemode in Pi: JavaScript sandbox for orchestrating tool calls
Overview – Codemode lets an LLM run short JavaScript scripts inside the harness to orchestrate tool calls
Codemode is a sandboxed JavaScript environment (QuickJS in a WASM runtime) that lives on the harness side of Pi, not in the execution sandbox where bash tools run. By writing tiny scripts, an LLM can compose multiple tool calls, store intermediate state, and invoke MCP APIs without sending all intermediate output into the model’s token window. This reduces turn count, avoids context overflow, and makes complex workflows (e.g., image generation, classification, sub‑agent loops) feasible.
How Codemode Works – a language‑level bridge between brain and hands
- Brain vs. Hands – The brain (the trusted harness) and the hands (the Unix sandbox where bash tools execute) are isolated file‑systems with different trust levels. Codemode runs in the brain, giving the LLM a programmable interface to the harness.
- Tool calls as JavaScript functions – Inside the sandbox, the LLM can call
tools.bash,models.generateImages,models.classify, etc., as ordinary async functions. The sandbox returns structured objects (JSON) rather than raw text, so the model receives clean data. - State persistence – Calls to
store(key, value)write data into the session transcript; later invocations can retrieve it withload(key). This enables multi‑step reasoning without re‑sending large payloads. - Concurrency – Because the environment is JavaScript, the LLM can launch
Promise.allto run up to four concurrent tool executions (Pi’s built‑in limit) and then process the results.
Benefits – why Codemode matters for LLM‑driven agents
- Reduced context usage – Only the final script output is injected into the model, not the full intermediate tool logs.
- Composable workflows – The LLM can write loops, conditionals, and parallel calls, turning ad‑hoc tool invocations into proper programs.
- Direct MCP access – MCP APIs that are not exposed as regular tools (e.g., image generation, classifiers) become callable from the script, eliminating the need for bespoke extensions.
- Deterministic data exchange – Structured JSON from
models.*and MCP calls avoids fragile text parsing. - Improved sub‑agent orchestration – Scripts can spawn and coordinate sub‑agents without relying on external CLI wrappers.
Example: Image Generation via Codemode
const [painter] = await models.getAvailableOfType("image");
const result = await models.generateImages(painter, {
input: [{ type: "text", text: "A cute little puppy sitting on a grassy lawn, soft natural light, photorealistic" }]
});
if (result.stopReason !== "stop") return result.errorMessage;
for (const block of result.output) {
if (block.type === "image") image(block); // sends image back to LLM
else text(block.text);
}
The script calls the internal image model, receives an object containing the image bytes, and forwards it to the LLM with a single image() call.
Example: Bulk Sentiment Classification of GitHub Issues
const jev = await models.getModelOfType("classifier", "typesafe", "jev-latest");
const r = await tools.bash({
command: "gh issue list --state open --limit 100 --json number,title,body,comments"
});
const issues = JSON.parse(r.output);
const results = await Promise.all(issues.map(async issue => {
const res = await models.classify(jev, {
state: {
title: issue.title,
body: (issue.body || "").slice(0,4000),
comments: issue.comments.slice(-5).map(c => c.body.slice(0,800))
},
questions: { /* sentiment, frustration, kind */ }
});
return { n: issue.number, title: issue.title, ...res.answers };
}));
store("sentiment_results", results);
return results.filter(r=>!r.error).sort((a,b)=>b.frustration.score-a.frustration.score).slice(0,12)
.map(r=>`#${r.n} ${r.frustration.score.toFixed(2)} [${r.kind.choice}] ${r.title}`);
The script fetches 100 issues, classifies them in parallel, stores the full result, and returns a concise ranking.
Interaction with MCP – progressive discovery and limitations
- Progressive tool discovery – Inside Codemode the LLM can call
tools.mcp__sentry__find_organizations({})and then iterate over returned organizations to query projects. The harness supplies a structuredContent field, making further calls deterministic. - Current pain points – Many MCP servers still return free‑form text rather than JSON, breaking Codemode’s expectation for structured data. Inconsistent batch sizes and lack of large‑binary support also cause failures.
- Double‑escaping issue – When an MCP server itself runs Codemode (e.g., Cloudflare’s MCP), the LLM ends up nesting JavaScript inside JavaScript, leading to double JSON escaping and harder debugging.
Community Feedback – what users think
"Between this and the Cloudflare post, this is a lot of words and no simple system level picture. … Code mode is basically rediscovering homoiconicity and actor semantics for LLMs." – @ssivark
"If you're like me and you're still wondering what 'codemode' is after reading the article and comments: It's giving the harness a small js sandbox to compose tool calls and manipulate the data they return before reading it into context." – @ohgodhelpplease
"I'm not sure why almost all codemode implementations choose Javascript. I prototyped an agent to use bash as the language for codemode, which worked equally well and requires no teaching." – @ylxdzsw
"Codemode reduces the amount of turns by about an order of magnitude." – @lionkor
"The promise of Pi was that you basically just need bash and no other tools. The minimally‑invasive approach would have been to just inject tool calls as virtual bash commands. No code mode required." – @dnlgrmm
These comments highlight both enthusiasm for the turn‑reduction and concerns about language choice, complexity, and the original vision of a pure‑bash workflow.
Limitations and Open Questions
- Durability – Long‑running scripts may need checkpointing; current implementations lack built‑in snapshotting.
- Language choice – JavaScript is convenient but not the only option; some users report success with Bash or Starlark for deterministic execution.
- Binary handling – Large images or files still require work‑arounds (pre‑signed URLs) because MCP does not yet stream binary blobs.
- Scalability of MCP discovery – No standardized way to fan‑out a tool search across multiple MCP servers; emergent behavior can become brittle.
Future Directions – making Codemode a first‑class programming layer for LLMs
- Deterministic composition languages – Starlark or a Lisp‑style language could replace JavaScript to guarantee referential transparency.
- Workflow engines – Integrating durable workflow engines (e.g., Temporal) could provide reliable snapshots and retries.
- Better MCP contracts – Enforcing
outputSchemaand consistent JSON responses will smooth the bridge between Codemode and external services. - Unified harness design – Treating the LLM as a language runtime participant (rather than a token generator) may lead to a new class of “code‑mode‑aware” harnesses, similar to the vision of homoiconic operating systems.
Codemode is not a silver bullet, but it offers a pragmatic path to richer, lower‑overhead LLM‑driven automation by giving the model a lightweight, sandboxed programming surface inside the harness.