cyberine
UsageRun recipes

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:

FieldRequiredDefaultDescription
stepsyes—Ordered array of { tool, args, id? } objects. Max 20 steps.
stop_on_errornofalseWhen true, halt on first error step and return partial results.

Step fields:

FieldRequiredDescription
toolyesRegistered tool name. Unknown names produce an error step (not a throw).
argsyesObject matching the tool's schema. String values may embed ${step_N.output}.
idnoLabel 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.