Scripting

Two verbs that run many actions in one call. They exist because an agent working item-by-item spends its whole context describing the work rather than doing it.

7 min

Which one

batchcode-run
ShapeDeclarative — "do these things"JavaScript
Reach for it whenThe steps are known, or come from a queryYou must compute or branch between steps
CostCheaperHeavier
Limits25 steps · 400 actions totalStep budget per pass, wall-clock per run

Start with batch. Reach for code-run when it cannot express the job.

batch

Steps, aliases, references and a loop.

{
  "steps": [
    { "method": "workspace-sql",
      "params": { "query": "SELECT id FROM notes WHERE project_id = 'x'" },
      "as": "q" },
    { "forEach": "${q.rows}", "as": "row",
      "do": { "method": "document-append",
              "params": { "id": "${row.id}", "markdown": "\n\nReviewed." } } }
  ]
}

A step can name its result with as, and later steps reference it with ${alias.path}. A lone reference keeps its type — so {"limit": "${a.n}"} sends the number, which matters because every validated action refuses a string there. Embedded in prose it stringifies.

Why forEach is the load-bearing part

Feeding one step’s rows into the next already happened server-side, so the rows never entered the model’s context. What cost O(N) was the model writing out N step specifications. forEach is what removes that — and its result is O(1) too: it reports counts and the first few errors rather than one envelope per item, which would hand back everything the loop just saved.

No nested forEach — that is N×M actions from two numbers nobody multiplied. And the whole batch is bounded at 400 actions, because a loop over a caller-chosen query result is otherwise unbounded. Past 25 steps you get batch capped at 25 steps rather than a truncated run.
Do not confuse this with agent-batch, which is a different verb with a different shape: it takes ops of { action, input } and returns ran/failed counts. It has no as aliases, no ${ref} substitution and no forEach — so it cannot feed one step’s output into the next, which is the whole reason to reach for batch.

code-run

JavaScript, in a sandbox that holds nothing.

var docs = clearly.documentList({ limit: 200 });
var stale = docs.documents.filter(function (d) { return !d.body; });
return { count: stale.length, keys: stale.map(function (d) { return d.key; }) };
Never write await. Every call is synchronous — var d = clearly.documentList({}). Top-level await is a syntax error whose message names a semicolon and never mentions await, so the error cannot teach you. Measured: an agent’s first use of this tool spent two of five calls on exactly that.

Call any action as clearly.someAction({…}) (camelCase maps to the kebab-case name) or clearly.call("action-name", {…}). Only your return value reaches you, so filter and aggregate inside the script — that is the entire point.

It must be deterministic

The script is re-run from the top after each workspace call — that replay is how a synchronous sandbox reaches an asynchronous host, and it is why you never write await. So the script must take the same path every time. The clock is frozen and randomness is seeded for exactly this reason; a script that branched on the real time would take a different path on replay and appear to hang.

The sandbox holds nothing: no network, no storage, no environment, no imports. Everything arrives through the gated workspace bridge. It gains composition, never authority.

They do not widen what you can do

A composite re-gates every action it runs.

Naming an action inside a batch step or a script does not bypass permission — each one is checked as if you had called it directly. Otherwise permission to run the multiplier would become permission to run anything, which is the shape of a real escalation.

A denial comes back as a value rather than an exception, so a script can branch on it — the same contract as every other failure.

If you have a shell, you may not need either

A client with a shell — Claude Code, for instance — can already loop over beehaven call in bash, which is code execution plus a tool API, fully attributed. These verbs exist for the clients that have no machine to run on: Claude Desktop, ChatGPT, and any hosted MCP connection, where the only code that can ever run against the workspace is code the workspace runs.