Programs
A program is the agent's plan as an artifact. Instead of one tool call at a time, the agent emits a list of ops with explicit data flow — and because the plan is a value, you can read it, save it under a name, and run it again with different inputs.
The agent emits the whole plan at once
One run_program call carries the full sequence. Ops run in order, each result can be named, and the runner reports back what completed — so the model never repeats a write that already happened.
run_program({
program: [
{ op: "github_list_prs", id: "prs", params: { repo: "$repo" } },
{ op: "core.let", id: "open", params: { be: "prs.filter(p, p.state == 'open')" } }
],
inputs: { repo: "acme/api" }
})
Every op has the same shape: op (the tool), an optional id (bind the result to a name), params, and optional onError and retry policies.
Data flows through $-refs
An op's id binds its output; later ops reference it in any string value — "$prs", "${prs[0].title}" — and the runner substitutes before dispatch. For reshaping, two core ops do the work: core.let binds a CEL expression, core.transform runs a jq filter over any binding — group, reduce, slice, build new objects.
Control flow is part of the language
core.if— a CEL condition withthen/elseregions.core.map— run a body over every item, withmaxConcurrencyand per-item error policy.flow.parallel— independent branches at once,failFastoptional.- Per-op
onError(halt,continue, or a default value) andretrywith backoff.
The plan carries its own branching, so a conditional isn't a second round-trip to the model — it's a line in the program.
Functions have inputs and sealed scopes
A program with inputs runs sealed: the body sees only its declared inputs, the job's history, and names bound by its own ops. That's what makes it a function — same body, different inputs, no accidental dependence on conversation state. A bare program (no inputs) sees cross-turn bindings normally, which is the convenient form for one-shots.
Save it by name, call it like a tool
save_program stores the function form under a name; list_programs, read_program, and archive_program manage the library. Any program can then invoke it with core.call — the inner body runs sealed, and the call's id binds to the inner program's bindings. A saved program is a tool you taught the system yesterday.
Every run leaves a trace
Each tool op dispatches through the normal call path, so a program run lands in the job's trace like any other work: every op a span, every cost counted, every write gated by approvals.
What's next
- Traces — the record a run leaves.
- Jobs — where programs execute.
- Assets — what programs act on and produce.
Updated 2026-07-18