{"slug": "callscript-code-mode-without-the-sandbox", "title": "Callscript: Code Mode, Without the Sandbox", "summary": "CallScript, a new tool-calling language for LLMs, compiles a subset of JavaScript into an inert JSON plan instead of executing it, eliminating the need for a sandbox while preserving the benefits of code mode. The language, which parses statements into steps that reference each other by id, allows plans to be approved deterministically, stored, and resumed later, and supports concurrent execution via Promise.all. CallScript mounts tools via adapters like the AI SDK and MCP, and provides search and describe functions for tool discovery.", "body_md": "# CallScript\n\nCode Mode, without the sandbox.\n\nA tool-calling language for LLMs. The model writes a subset of JavaScript - CallScript parses it into a JSON plan instead of executing it. Plans can be approved deterministically, stored, and resumed later, and steps reference earlier results by id.\n\ncompiles to\n\n## why\n\nSay you have two GitHub tools mounted - `listIssues`\n\n, which returns the first 100 issues of a repo, and `closeIssue`\n\n, which closes one issue by number - and you prompt the agent: \"close stale issues\".\n\nWith plain tool calling, every `listIssues`\n\ncall lands all 100 issues in the agent's context. To pick the stale ones it has to read them; to close them it has to generate tokens for each `closeIssue`\n\ncall - and so on, one round-trip at a time.\n\nThat is slow, costs tokens, no way to see the full set of calls ahead of time, judgments like \"stale\" are made mid-run and so on..\n\n[Code Mode](https://developers.cloudflare.com/agents/tools/codemode/) - or, in Anthropic's writing, [code execution with MCP](https://www.anthropic.com/engineering/code-execution-with-mcp) - solves this by giving the model type definitions for the tools and letting it write a TypeScript program against them: models are better at writing programs than at emitting tool-call chains, and results flow between calls without going back through the model. But code mode introduces its own complexity - from Anthropic's:\n\nNote that code execution introduces its own complexity. Running agent-generated code requires a secure execution environment with appropriate sandboxing, resource limits, and monitoring. These infrastructure requirements add operational overhead and security considerations that direct tool calls avoid. The benefits of code execution—reduced token costs, lower latency, and improved tool composition—should be weighed against these implementation costs.\n\nBut calling tools and APIs shouldn't need a Turing-complete language. By the [rule of least power](https://en.wikipedia.org/wiki/Rule_of_least_power), the unused power is what forces the sandbox and keeps the code from being validated, bounded, or paused.\n\n## the script\n\nCallScript keeps the parts of JavaScript the job needs - calls, dataflow, branches, bounded fan-outs - and compiles them to inert data before anything executes. The benefits stay and the infrastructure goes: a plan and its state are plain data, so a run stores anywhere, resumes later, and takes new input when it does.\n\nThe agent answers the same prompt by writing one small JavaScript program:\n\ncallscript never executes it - each statement compiles into one step of an inert JSON plan:\n\nSteps reference each other by id, and those references are the schedule: independent steps run concurrently, dependent ones wait. Awaited calls keep statement order, and `Promise.all`\n\nruns calls in parallel.\n\n## usage\n\nMount your tools on callscript and hand the model the ready-made tools - `execute`\n\n, `search`\n\n, and `describe`\n\n:\n\n## tool definitions\n\nIn callscript, a tool is anything an executor can evaluate. Executors come from adapters - the AI SDK, MCP, and others - and the default executor evaluates a plain object: `{ name, execute }`\n\nplus an optional schema and description:\n\n### function signatures\n\ncallscript turns each tool definition into a function signature: one card with the signature line, the description, and any declared error codes.\n\n### search\n\nTools are meant to be discovered: `search`\n\nfinds mounted tools by keyword and returns names with one-line summaries, and `describe`\n\nreturns the full signature cards for the names a script will use. You pick the exposure. Append every card into the prompt when the toolset is small; or list only names and short descriptions and let the agent `describe`\n\nthe ones it needs - no searching to discover - or expose nothing inline and let it `search`\n\nfirst, so the prompt stays the same size however many tools you mount. `execute`\n\nis the third tool of the pair - the one that acts, running the script the model authored.\n\n## serializability\n\nAn execution of a callscript is data all the way down: the plan, every settled step, and the point where it stopped all serialize into one plain record. You can flag a risky call for approval, park a run on an external event, or leave a long job running and join it from a later script:\n\nwhich compiles to the plan step:\n\nThe paused run comes back as a plain `state`\n\nrecord that can be stored in memory or as a KV entry; when the answer arrives, execution continues from the serialized record - settled steps reused, not re-run.\n\n## typed authoring\n\n`cs.script({...})`\n\nand `cs.tool(...)`\n\nare typed against the mounted tools: `call`\n\nautocompletes to mounted tool names and `args`\n\nto that tool's input, so a typo'd name is a type error before it is a validation error. Every expression position takes the string form or a real JS arrow, transpiled - never executed - into the string at the door:\n\nThe arrow's parameter names everything the body reads; a free name - including a captured outer variable, the thing a native closure could smuggle in - is rejected at the door. What's stored, hashed, and re-executed is always the string form, so the script stays inert data.\n\n## reference\n\nEach step of a plan is one of three verbs:\n\n`call`\n\n-`const x = await tool.name({...})`\n\n- invokes a mounted tool; its`args`\n\nvalidate against the tool's schema before it fires. A second argument carries per-call options:`{ reason, suspend, onError }`\n\n.`let`\n\n-`const x = expr`\n\n- derives a value from earlier steps with a pure expression.`return`\n\n-`if (cond) return value`\n\n- is a guard clause: when it fires the run ends right there with that value; otherwise the run continues.\n\nAnd a step can carry modifiers:\n\n`if`\n\nskips the step unless a condition holds.`each`\n\nfans a call out over a list, one dispatch per element, bounded by a hard`max`\n\n.`after`\n\norders a step behind earlier ones when no data flows between them - close the issues, then post the summary.`suspend`\n\nflags a call for confirmation: the run pauses there until a human approves it.\n\nA few more things the language gives you:\n\n- Globals. Expressions read earlier steps by id,\n`input`\n\n(data passed to this execution), variables published by earlier runs in the session,`$errors.stepId`\n\nfor recorded failures, and safe built-ins like`Math`\n\n,`JSON`\n\n, and`Date`\n\n. - Promises. Every call is async;\n`await`\n\nonly decides whether the run blocks on it. A call*without*`await`\n\n(`const job = svc.export({...})`\n\n) detaches and keeps running in the background, and a later script joins it with`const r = await job`\n\n. - Expressions. A side-effect-free subset of JS: arrows, template literals, ternaries, optional chaining - no I/O, no imports, no reaching outside the script's scope.\n- Output.\n`output`\n\nprojects the run's final result from any settled step; by default it is the last step's value. - Validation. The whole plan is checked before anything runs - unknown tools, misshaped args, unbound references, all reported at once - and hard limits cap steps, total calls, and concurrency.", "url": "https://wpnews.pro/news/callscript-code-mode-without-the-sandbox", "canonical_source": "https://www.callscript.dev/", "published_at": "2026-08-31 23:09:22+00:00", "updated_at": "2026-08-31 23:22:18.508247+00:00", "lang": "en", "topics": ["artificial-intelligence", "large-language-models", "ai-tools", "developer-tools"], "entities": ["CallScript", "Cloudflare", "Anthropic", "AI SDK", "MCP"], "alternates": {"html": "https://wpnews.pro/news/callscript-code-mode-without-the-sandbox", "markdown": "https://wpnews.pro/news/callscript-code-mode-without-the-sandbox.md", "text": "https://wpnews.pro/news/callscript-code-mode-without-the-sandbox.txt", "jsonld": "https://wpnews.pro/news/callscript-code-mode-without-the-sandbox.jsonld"}}