Tool-call pipeline — batch multi-tool sequences in one model turn (T319)
Implementer-class loop workers (loop--build) automatically get a runpipeline tool that lets a worker express a predictable multi-step tool sequence as a single tool call, collapsing N sequential...
Implementer-class loop workers (loop-*-build) automatically get a run_pipeline tool that lets
a worker express a predictable multi-step tool sequence as a single tool call, collapsing N
sequential round-trips into 1. Each round-trip costs an LLM API call plus the growing prompt
overhead of the accumulated conversation history; a pipeline eliminates that overhead for the
entire sequence.
How it works:
The pipeline accepts a JSON steps array. Each step names a registered tool and its args. String
arg values may embed ${step_N.output} references (N is 1-based) to thread the output of a prior
step into the next step's args. Steps execute in order inside a single tool-execute call with no
additional model turns. The full structured result (per-step output + ok flag) is returned in one
tool result.
Security model: only tools already registered with the adapter are callable. The pipeline spec
is a declarative data structure — no eval, no new Function, no arbitrary JS execution. Step
count is bounded (max 20). The parent ToolContext (cwd sandbox, usage roll-up) is threaded to
every sub-call unchanged.
Round-trip reduction: a 4-step investigation pipeline (list_dir -> grep_files -> read_file -> calculator) runs in 1 model turn instead of 4, saving 3 LLM API calls and the prompt-token cost of growing conversation context after each round.
How the model calls it:
{
"name": "run_pipeline",
"arguments": {
"steps": [
{
"id": "list-src",
"tool": "list_dir",
"args": { "path": "src" }
},
{
"id": "find-adapters",
"tool": "grep_files",
"args": { "pattern": "class.*Adapter", "path": "src/adapters" }
},
{
"id": "read-loop-adapter",
"tool": "read_file",
"args": { "path": "src/adapters/LoopAgentAdapter.ts" }
}
],
"stop_on_error": false
}
}Threading step outputs:
{
"name": "run_pipeline",
"arguments": {
"steps": [
{
"id": "read-package",
"tool": "read_file",
"args": { "path": "package.json" }
},
{
"id": "count-lines",
"tool": "calculator",
"args": { "expression": "1 + 1" }
}
]
}
}A step output reference ${step_1.output} in any string arg value is replaced with the text
output of step 1 before the step executes. References to future or non-existent steps are left
verbatim.
Parameters:
| Field | Required | Default | Description |
|---|---|---|---|
steps | yes | — | Ordered array of { tool, args, id? } objects. Max 20 steps. |
stop_on_error | no | false | When true, halt on first error step and return partial results. |
Step fields:
| Field | Required | Description |
|---|---|---|
tool | yes | Registered tool name. Unknown names produce an error step (not a throw). |
args | yes | Object matching the tool's schema. String values may embed ${step_N.output}. |
id | no | Label in the result JSON. Defaults to "step_N". |
Extend recipe — to enable the pipeline on a custom adapter, pass enablePipeline: true to
LoopAgentAdapterOptions. The tool is auto-injected and closes over the adapter's full tool
registry at construction time:
new LoopAgentAdapter({
id: 'my-worker',
provider: 'deepseek:deepseek-v4-pro',
model: 'deepseek-v4-pro',
baseURL: 'https://api.deepseek.com',
apiKey: process.env.DEEPSEEK_API_KEY!,
tools: myTools,
enablePipeline: true, // inject run_pipeline
})Both enableSubagents and enablePipeline may be set together; each tool is injected
independently with no interaction.
Worker-internal subagents — delegate_subtask (T313)
Implementer-class loop workers (loop--build) automatically get a delegatesubtask tool that lets the worker spawn a scoped subagent — a focused nested LoopAgentAdapter with its own system prompt, a...
Result projection — configurable worker output fidelity (T328)
By default every worker returns a full Result: the complete output text, usage stats, and any raw provider payload. When a judge or downstream step only needs a summary (e.g. an LLM judge that...