On 29 October 2026 Factory shipped Droid 1.0 and made programmatic tool calling the default. The model no longer emits one tool call, waits for the result, and repeats. It writes a short JavaScript program that runs inside a sandbox, can fire hundreds of tool calls in a single model request, and only returns the printed summary.
That change matters once you leave toy demos. In a normal agent loop every tool result is appended to the context window. After a few dozen file reads, search results or Linear updates the prompt is mostly raw data the model already saw. Rate limits get hit, latency climbs, and the next decision is made on a noisy, truncated history.
The Script tool
Droid implements the pattern as a single Script tool. The model receives a tiny set of tool definitions up front (Factory cut the base prompt from ~21 k to ~13 k tokens). When it decides a multi-step workflow is needed it emits something like:
const issues = await linear.search({ query: "status:Todo", limit: 200 });
const toMove = issues.filter(i => i.labels.includes("stale"));
const results = await Promise.all(
toMove.map(i => linear.update(i.id, { status: "Done" }))
);
console.log(JSON.stringify({
moved: results.length,
failed: results.filter(r => !r.ok).length
}));
The sandbox has no filesystem, network or system access of its own. It can only call the tools the harness exposes. Independent calls run concurrently. The only thing that returns to the model is whatever the script prints (capped in size). Full raw results stay in a log the model can search later if it needs them.
Factory’s own numbers illustrate the effect. A Linear cleanup that touched 151 issues (312 tool calls) dropped from an estimated 36 model requests and 3.1 M input tokens to 2 requests and ~61 k tokens. A CI sweep of 5 384 jobs finished in two requests with under 0.1 % of the tool results ever reaching the model. On Claude Opus 5.5 the same mode cut cost ~15 % versus direct tool calling.
Why the context window is the real bottleneck
The model is not slow at arithmetic or filtering. It is slow (and expensive) at re-reading the same data every turn. Once the context is polluted the attention is diluted, the model starts dropping edge cases, and you pay for tokens you already paid for.
Moving the loop into code has three practical consequences:
- Instructions and history are sent once.
- Checks, filters and retries become deterministic.
- The model sees only the decision-relevant summary.
The idea is not new—CodeAct (Wang et al., 2024) showed code-based tool use beating JSON tool calling on multiple models—but making it the default path in a production harness is the part that changes day-to-day agent work.
Trade-offs on a live path
A sandbox that can only call pre-approved tools is safer than giving the model a raw shell, but it also means the model cannot invent new side-effects on the fly. If your tools are incomplete the script simply cannot finish. You still need a fallback to direct tool calls for open-ended exploration.
Concurrency helps latency when calls are independent, but a single slow tool still gates the whole batch. In a real-time setting (speech-to-text segments arriving every few hundred milliseconds, overlay updates, local model queries) you want the Script to be short-lived. Long-running scripts that block the audio thread are worse than the old sequential loop.
On Windows the same pattern maps cleanly onto a desktop agent. Capture audio with WASAPI, push PCM frames into a ring buffer, run a short Script that decides which local or remote model to query and what to push to the overlay, then print only the final text and confidence. The model never sees the raw float32 buffer.
// audio thread (simplified)
while (running) {
WaitForSingleObject(captureEvent, INFINITE);
// drain WASAPI packet into lock-free ring
ring.push(frames, count);
}
// agent thread
ScriptResult r = sandbox.run(scriptSource, {
{"getLatestAudioMs", [&]() { return ring.latestMs(); }},
{"queryLocalSTT", [&](auto ms) { return localModel.transcribe(ring.slice(ms)); }},
{"updateOverlay", [&](auto text) { overlay.setText(text); }}
});
// only r.printed reaches the next model turn
What still breaks
- Scripts that print too much still blow the context on the next turn.
- Non-deterministic tools (network, user input) make the printed summary incomplete; you need an explicit “need more data” signal.
- Debugging a failed script is harder than debugging a single tool call. Keep the full log and a way to replay the exact Script source.
Factory ships the harness as a desktop app (macOS/Windows), a terminal UI and a Node SDK. Script mode is on by default for a list of frontier models including GPT-6.1 Sol and Claude Opus 5.5; you can also point it at your own keys or a local endpoint.
The practical takeaway is simple: stop letting every intermediate result re-enter the context. Write the loop in code, keep the model on the decision boundary, and the same token budget suddenly covers a lot more real work.
I'm building Talkaside, a Windows app for job interviews and calls in another language.
Drafted with AI assistance from this project's code and logs.

Top comments (0)