Tubeless agents
Agents are pipelines.
An agent is a pipeline of LLM turns and tool calls. The model chooses what runs next. Tubeless runs the steps and keeps track of what happened.
Tool calls are steps. Subagents are child pipelines. You can trace the whole run, reuse a pipeline as a tool, or put an agent in the middle of a larger workflow.
verifierSubagent / child pipeline
Each model turn can choose a new batch of tools or finish. The graph grows as the agent works; the turns above show one possible run.
Use agents wherever you use pipelines.
A pipeline can ask an agent to investigate a problem, then pass its answer to the next step. An agent can call an existing pipeline as a tool, or delegate work to another agent.
It is the same execution model in both directions. Inputs and outputs are validated. Each child agent has its own state, and its work counts toward the parent’s execution limits.
pipeline → agent → next stepUse fromPipeline to run an agent inside a workflow and consume its answer.
agent → tool → pipeline or subagentUse pipelineTool to give an agent a capability you have already built.
Example: use an agent’s answer in the next pipeline step
import { createSteps, definePipeline } from "tubeless";
import { definePipelineCommand } from "tubeless/cli";
import { DelegatingAgent } from "./agent-delegation.js";
import { textOptions } from "./agent.js";
const { step, fromPipeline } = createSteps(textOptions);
// Agents compose like any other child pipeline; the validated options are forwarded.
const answer = fromPipeline("answer", { pipeline: DelegatingAgent });
const report = step("report", {
dependsOn: [answer],
description: "Consume the agent's validated answer in ordinary pipeline work.",
run: ({ answer }) => answer.answer,
});
export const AgentPipeline = definePipeline({
id: "agent-pipeline",
description: "Embed a delegating agent in an ordinary pipeline, including dry-run previews.",
steps: [answer, report],
finalize: report,
});
export const AgentPipelineCommand = definePipelineCommand(AgentPipeline, {
summarize: (result) => [result],
});Start with a model and a task.
Save this as agent.ts. It gives the model a coding prompt, project instructions from AGENTS.md, conversation history, and six workspace tools.
readeditwritebashlistsearch
Add your own tools and instructions as needed. The core is provider-independent: use the OpenAI adapter shown here, supply another model callback, or write your own decision logic with defineAgent.
import { defineModelAgent } from "tubeless/agent";
import { openaiModel } from "tubeless/agent/openai";
/** Run from the workspace the agent should inspect and edit; live runs make paid API requests. */
export const CodingAgent = defineModelAgent({
id: "coding-agent",
name: "Workspace coding agent",
description: "Investigate a task, edit the workspace, and verify the result with a model.",
implementationVersion: "coding-agent-v5",
model: openaiModel({ reasoningEffort: "high" }),
});Install Tubeless and set OPENAI_API_KEY in your environment. From a disposable workspace, run with Bun 1.3.14+:
npm install tubeless
bunx tubeless run --trace agent.ndjson ./agent.ts -- \
--task "Find and fix the failing test, then verify the change."
bunx tubeless history --trace agent.ndjson This makes paid model calls and uses your machine’s file and shell permissions. Run it in a workspace you are comfortable letting it change.
Runs in your process.
Set limits on turns, calls, nesting, and concurrency. Cancel with an AbortSignal. Record history to NDJSON or SQLite. The runtime has no dependencies, and you do not need a separate service to run an agent.
Inspect which tools were called, what failed, and which subagent did the work. Parent and child runs stay connected in the history.
Execution is in process; crash-safe resume is not supported yet. Workspace tools use host permissions and are not sandboxed.