The script language
One of the model's tools, RunScript, is a small language of its own — glob, read, chunk, ask, each — that puts several agents on one job at once and hands back only the number or line you asked to see.
It is easy to miss, because nothing in a normal turn shows it: the model reaches for it on its own, and what you see in the log is one tool call and one short result, not the work behind it. This page is the part of the picture the tool call itself does not show.
A worked example
- files = glob core/src/*.rs
- some = first 5 files
- answers = each some
> Read {it} and answer with exactly one word: YES if it opens or connects a unix socket, NO if not.
- yes = keep answers YES
- total = count yes
- show total
Run against this project, that script answers 2. Behind it: five agents ran at once, one per file, each reading its own file and answering one word; two of them — attach.rs and agents.rs, as it happens — said YES. The five file contents never reached the conversation. Only the number did.
That is the whole design in one run. each fans a list out to one agent per element and collects the answers as a list; keep, count and show then work on that list without anything in it ever becoming a message you have to read. A script that reviews forty files and shows the count of failures costs the conversation one number, the same as a script that reviews four.
Why it matters at scale
At five files this is a toy — you could have read them yourself faster than writing the script. At two hundred files it stops being a toy: two hundred files do not fit in one conversation's context, however patiently you read them one at a time, and a fan-out is the difference between the question being answerable at all and not.
chunk plus model is the pattern worth knowing on top of that: chunk splits a long text into pieces of a fixed number of lines, and model switches which model the ask and each calls after it run on. Put a cheap model on the chunks and an expensive one only on their answers, and the ratio inverts — a model that would have been too costly to point at two hundred pieces reads their two hundred answers instead, which is a much smaller job.
What stays bound
Every name a script binds is still bound the next time a script runs in the same conversation — the interpreter's environment lives in the process, not in the script. A later script can reuse files or answers from an earlier one instead of globbing or reading the same material again, and the tool result after every run lists what is currently held: the name, its type, its size, never its content, so choosing a name to reuse costs nothing.
The surprise most people hit first: this only holds for the running process. A resumed session starts with an empty environment — the conversation history is still there, but nothing it bound is. A script that depends on a name from three turns ago works only as long as the session that created it keeps running.
The commands
A script is a markdown document. Its list items are commands, generally of the shape name = command arguments; a command run without an assignment (show, save, budget, model) just has its effect. A block quote right below ask or each is the prompt that command sends. {name} inside a prompt is a bound value, {it} is the current element while each is fanning out. A wrong script — an unknown name, an argument of the wrong shape — is refused before the first agent runs, so a typo in a two-hundred-file script costs nothing.
| Command | Shape | Effect |
|---|---|---|
glob | glob <pattern> | Find files by pattern: files = glob src/**/*.rs, through the same sandboxed file layer the Glob tool uses |
list | list <text> <text> … | Write a list of literal strings down: questions = list "What is 2+2?" "Capital of France?", for a handful of independent questions with no file to glob them from |
read | read <path> | Read a file as text: content = read README.md |
lines | lines <text> | Split a text into a list, one element per line: rows = lines content |
chunk | chunk <text> <n> | Split a text into pieces of n lines each: pieces = chunk content 50 — what a long file is broken into before each fans it out |
ask | ask, with a block quote below | Put one agent on the prompt in the block quote and return its answer: reply = ask followed by > What is 2+2?. Takes no arguments of its own — the prompt is the quote |
each | each <list>, with a block quote below | Put one agent on every element of the list at once and collect the answers as a list, in order: answers = each files followed by > Read {it} and answer YES or NO. |
keep | keep <list> <text> | Keep the elements equal to that text: yes = keep answers YES |
drop | drop <list> <text> | The complement of keep: no = drop answers YES |
first | first <count> <list> | The first count elements: top = first 5 files. The count comes before the list — the reverse order is the single most common way a script gets refused |
count | count <list> | How many elements: total = count yes |
show | show <name> | Put that value into the tool result. It is the only thing that leaves the interpreter — everything else stays bound and unseen |
save | save <path> <name> | Write a file: save "notes.md" content, through the same sandboxed file layer the Write tool uses |
model | model <name> | Switch which model the ask and each calls after this line run on, for the rest of the script |
budget | budget <count> | Cap how many agent calls the rest of the script may make. A call past the cap answers with an error instead of running, rather than the script being refused outright |
What it can reach
glob, read and save go through the same sandboxed file layer as the ordinary tools, and ask/each go through the same child-agent mechanism as SpawnAgent — a script reaches exactly what the model may reach directly, and nothing more. Children spawned by each run in parallel, so a fan-out over fourteen files takes about as long as the slowest one rather than fourteen times as long.