Skip to main content
Real-world tasks often push against the limits of a single agent. A research request might need deep web search, code execution, and finally a polished summary — three concerns that benefit from three different system prompts, three different tool sets, and potentially three different models. When you feel the urge to write a sprawling system prompt that says “first search, then analyze, then format”, that’s the signal to split. Vibes has no dedicated multi-agent API. A sub-agent is just a regular Agent whose run() call lives inside a tool() execute function. The framework handles everything else: the orchestrator’s model decides when to call the tool, Vibes executes it, appends the result to history, and the run continues. Nesting is unlimited and usage aggregates automatically.

The agent-as-tool pattern

The fundamental building block is wrapping one agent’s run() call inside a tool(). The outer agent gets a tool it can call by name; the inner agent does focused work and returns a string. From the model’s perspective it’s just a tool call — there’s nothing special about the implementation.
(1) The specialist has a narrow system prompt. It doesn’t need to know about synthesis or formatting — it just researches.
(2) tool() is the only glue needed. The specialist’s run() call is a plain async function call inside execute.
(3) result.output is a string here because searchSpecialist has no outputSchema. If the specialist returned a structured type, you’d get that instead.
(4) The orchestrator’s system prompt is completely decoupled from the specialist’s. You can swap the specialist model, rewrite its prompt, or add tools to it without touching the orchestrator.
You don’t need to import any special multi-agent module. The only imports you need are Agent, tool, and z — the same ones you use for any other agent.

Why multi-agent instead of more tools?

Before adding a sub-agent, ask: can I solve this with another tool on the same agent? Use a sub-agent when:
  • The sub-task needs a different persona — a strict grader, a creative writer, a terse summarizer. Mixing prompts in one agent produces confused output.
  • The sub-task needs different tools that should be invisible to the orchestrator. If the orchestrator doesn’t need to call read_file directly, don’t clutter its tool list.
  • You want to reuse the same specialist from multiple orchestrators, keeping a single source of truth for its instructions and tools.
  • The output of one stage is structured input for the next stage and you want that boundary enforced by a Zod schema.
Avoid a sub-agent when the orchestrator can do the work in one model call without confusion — the extra round trip costs tokens and latency.

Passing dependencies through the hierarchy

Dependencies flow through the deps field on RunOptions. When your tools receive a RunContext, the ctx.deps field holds whatever you passed at run time. A sub-agent called inside a tool can receive its own deps at call time.
(1) The sub-agent only receives what it needs. It never sees userId — good for least-privilege design.
If the orchestrator and specialist share a dep type, you can pass ctx.deps directly: deps: ctx.deps. For narrower sharing, destructure only the fields the specialist needs.

Aggregating usage across the hierarchy

Token usage aggregates automatically. result.usage on the top-level run reflects the combined cost of the orchestrator plus every sub-agent call made during that run — you do not need to track sub-agent usage separately.
This works because the RunContext.usage object is shared within a run and sub-agents accumulate into the same counter through the tool’s ctx. Sub-agents started in separate agent.run() calls (outside of a tool context) maintain their own usage counters.

Structured output from sub-agents

Sub-agents can return structured data just like any agent. Declare an outputSchema on the specialist, and the returned result.output is fully typed.
(1) Tool execute must return a string or plain object. Serializing the structured output lets the orchestrator see the full structure in its message history.

Common patterns

Orchestrator + specialists

One orchestrator agent with multiple specialist tools. The orchestrator routes tasks; specialists execute them. Good for open-ended tasks where the model should decide the order.

Pipeline

Agent A produces output that feeds directly into Agent B. Use message history (messageHistory option) or tool chaining. Good when stages are deterministic and sequential.

Dynamic routing

A router tool that calls different specialists based on task type. Implement with an if/else in the tool’s execute — no special routing API required.

Peer agents

Two agents that can each call the other as a tool. Useful for critique/review loops where one generates and one evaluates. Set maxTurns carefully to bound recursion.

Full example: research pipeline

Here’s a complete three-agent system: a search specialist, a summarizer, and an orchestrator that coordinates them.
(1) Use a smaller model for specialists. They do focused work with a tight prompt — Haiku is often sufficient and significantly cheaper.
(2) The summarizer declares a Zod outputSchema. The framework injects a final_result tool and validates the output automatically.
(3) JSON.stringify converts the structured output to a string so the orchestrator can read it in its message history.
(4) The orchestrator coordinates and synthesizes, which benefits from a stronger model’s reasoning ability.
(5) With three tool calls in the plan, maxTurns: 6 gives buffer for the model to think before and after each tool call.

Dynamic routing

When the right specialist depends on what the user asked, route inside the tool’s execute function:
(1) The router is plain TypeScript. You have the full language available — switch, Map lookups, database queries — whatever the routing logic requires.

Tools

Define tools and wrap any async function

Dependencies

Pass runtime deps through RunContext