Common Patterns

Pipeline

Choose one agent, a runner, nested agents, or a pipeline based on the job shape.

Start with one runner around one agent. Add orchestration only when the job has a real boundary: a deterministic step, a specialist capability, a parallel branch, a typed extraction stage, batching, or a separately testable process.

Many production workflows do not need a pipeline. They need a clear harness runner.

One Agent in a Runner

Use one agent when one promptable runtime can own the task and a small set of tools is enough.

const agent = new AgentBuilder("support", model)
  .instructions("Answer support questions and use tools when account data is needed.")
  .tools(createSupportTools(scope))
  .defaultMaxTurns(3)
  .build();

const response = await agent.prompt(message).send();

This is the simplest shape to trace, test, and run in Studio. Prefer it until the workflow has a boundary that should be named and tested separately.

Runner Before Pipeline

Use a runner when the workflow is mostly application wiring: auth, input validation, history, scoped tools, context, trace metadata, persistence, and error mapping.

export async function runSupportTurn(input: SupportRunnerInput) {
  const user = await input.auth.requireUser();
  const history = await input.conversations.loadMessages(input.conversationId);
  const agent = createSupportAgent(model, { user, services: input.services });

  const response = await agent
    .prompt([...history, Message.user(input.message)])
    .withTrace({ name: "support-chat", userId: user.id })
    .send();

  await input.conversations.append(input.conversationId, response.messages);
  return response.output;
}

Do not add a pipeline just to hide application setup. If the steps are not reusable or independently testable, the runner is the right abstraction.

Agent as a Tool

Use an agent tool when a subdomain has stable instructions and should be callable by another agent.

const refundsAgent = new AgentBuilder("refunds", model)
  .description("Answers refund-policy questions.")
  .instructions("Answer only refund-related questions.")
  .build();

const triageAgent = new AgentBuilder("triage", model)
  .instructions("Route refund questions to the refund specialist.")
  .tool(
    refundsAgent.asTool({
      name: "ask_refunds_agent",
      maxTurns: 2,
    }),
  )
  .defaultMaxTurns(3)
  .build();

Keep nested agents narrow. A broad agent calling another broad agent usually makes traces harder to understand and quality harder to evaluate.

Pipeline

Use a pipeline when the job has explicit typed stages that should be tested, reused, or observed independently.

const pipeline = new PipelineBuilder<string>()
  .step((input) => input.trim())
  .prompt(triageAgent)
  .extract(ticketExtractor)
  .step((ticket) => ({
    ...ticket,
    needsEscalation: ticket.priority === "high",
  }))
  .build();

Pipelines are a good fit for preprocessing, enrichment, extraction, branching, batching, and post-processing. They are a poor fit for hiding route logic or permission policy.

Decision Table

UseWhen
one agentone conversational runtime can own the task
runnerthe complexity is app context, persistence, tracing, or error mapping
agent toolanother agent owns a narrow specialist boundary
pipelinethe job has explicit typed stages or deterministic steps
direct service callthe step does not need a model
extractormodel output must match a schema for downstream code
parallel branchesindependent model or service work can run at the same time

Practical Rule

Reach for the smallest named boundary that matches the job. A runner names a product workflow. An agent names model behavior. A tool names product capability. A pipeline names typed process stages.