Crux
GuidesAgents

Overview

Composition patterns and coordination primitives for multi-agent AI applications.

Multi-Agent Orchestration

You have a prompt that works. Now you need two prompts to work together — or three, or ten. Maybe a researcher feeds a writer, reviewers run in parallel, or a triage agent decides who handles a customer issue.

Crux provides two categories of tools for multi-agent orchestration:

  • Composition patterns — proven workflows for common multi-agent scenarios
  • Coordination primitives — shared state, validated transfers, and step orchestration for everything else

These aren't layers — they're complementary. A customer support system might use swarm() for routing between specialists, blackboard() for sharing customer context, and flow() inside an agent's tool for a multi-step search workflow. Pick what you need.

Composition Patterns

Bundle a prompt with optional model, tools, and routing targets into a reusable agent primitive, then pick the pattern that fits:

import { agent } from '@crux/core/agent'
import { parallel, pipeline, consensus, swarm } from '@crux/ai'

Each SDK adapter (@crux/ai, @crux/openai, @crux/anthropic, @crux/google) re-exports pre-bound versions of all patterns. Import from your adapter, not from @crux/core.

Parallel — independent tasks, typed results

Run multiple agents concurrently on the same context. Results are returned as a typed named record.

const { results, durationMs } = await parallel({
  agents: { facts: factChecker, style: styleReviewer, seo: seoAnalyzer },
  context: { content: article },
  model,
})

results.facts.output.score        // typed from factChecker's output schema
results.style.output.suggestions  // typed from styleReviewer's output schema

Use when: Multiple independent reviews, scoring from different angles, gathering data from multiple sources. Total time = slowest agent, not the sum.

Deep dive →

Pipeline — sequential stages

Agents run one after another. Each step's input function receives all previous outputs via a context accumulator.

const result = await pipeline({
  steps: [
    { agent: researcher, name: 'research' },
    { agent: writer, name: 'write', input: (ctx) => ({ findings: ctx.research.output.sources }) },
    { agent: editor, name: 'edit', input: (ctx) => ({ draft: ctx.write.output.text }) },
  ],
  context: { topic: 'AI safety' },
  model,
})

Use when: Each step depends on earlier stages. The sequence is known ahead of time.

Deep dive →

Consensus — voting on a decision

Run multiple agents and count votes. Reduces single-agent errors for critical decisions.

const decision = await consensus({
  agents: [classifier, classifier, classifier],
  context: { ticket: supportTicket },
  extract: (result) => result.output.category,
  quorum: 'majority',
})
// decision.result: 'billing'  — 2 out of 3 agreed

Use when: Classification, quality scoring, moderation — anywhere multiple opinions are cheap and a wrong answer is costly.

Deep dive →

Swarm — dynamic, LLM-decided routing

A network of specialists where the LLM decides who handles the next turn by calling auto-generated transfer_to_<agent> tools.

const triage = agent({
  id: 'triage', prompt: triagePrompt,
  handoffs: ['billing', 'shipping'],
})
const billing = agent({
  id: 'billing', prompt: billingPrompt,
  handoffs: ['triage', 'refunds'],
})

const result = await swarm({
  agents: { triage, billing, refunds },
  startAgent: 'triage',
  input: { message: 'I was charged twice' },
  model,
})
// result.handoffPath: ['triage', 'billing']

Use when: The path depends on the input — customer support routing, sales qualification, content production where specialists route work to each other.

Swarm also supports conditional handoffs, tool filtering, cost tracking with abort, context summarization, and Convex cross-action routing.

Deep dive →

Which pattern do I need?

I need to...Pattern
Run agents at the same time and combine resultsparallel()
Run agents one after another, passing data alongpipeline()
Get multiple opinions and pick the majority answerconsensus()
Let the LLM decide which specialist handles the next turnswarm()

Still not sure? "Do I know the sequence ahead of time?"

  • Yes, fixed sequencepipeline()
  • Yes, all at onceparallel() (or consensus() if voting)
  • No, the LLM should decideswarm()

Coordination Primitives

These solve problems that composition patterns don't cover — shared state, validated data transfer, and step-level orchestration. Use them alongside compositions when you need them.

Flows — step-level orchestration

Chain generate() calls into named steps with retries, fallbacks, and devtools tracing. Use alongside compositions for custom multi-step logic inside agent tools.

const deepSearch = flow('deep-search', async (flow) => {
  const results = await flow.step('search', () =>
    generate(searchPrompt, { model, input: { query } }),
    { retry: { attempts: 3, backoff: 'exponential' } }
  )
  return flow.step('summarize', () =>
    generate(summaryPrompt, { model, input: { results: results.object } })
  )
})

await deepSearch.run()

Use when: You need per-step retries, fallback models, or named step tracing. Works great inside agent tools for complex multi-step operations.

Deep dive →

Blackboard — shared state

A typed, schema-validated scratchpad that multiple agents can read and write. Expose as prompt context or as an LLM tool.

Use when: Multiple agents need a shared workspace — research findings, task assignments, collaborative data gathering.

Deep dive →

Handoff — validated context transfer

Schema-validated, optionally summarized transfer of context between agents. Ensures the receiving agent gets data in the exact format it expects.

Use when: You need schema validation between agents, format transformation, or LLM summarization to compress large payloads.

Deep dive →

Delegate — subagent as a tool

Wraps a handoff contract with a subagent execution function, exposed as a callable tool. The parent calls it, the subagent runs, results are validated through the handoff.

Use when: A parent agent needs to call a specialist as a tool with automatic input/output validation.

Deep dive →

Composing them together

A customer support system using several tools together:

// Shared context across all agents in the swarm
const customerBoard = blackboard({
  id: 'customer-context',
  schema: z.object({
    customerId: z.string(),
    issueCategory: z.string().optional(),
    priorInteractions: z.array(z.string()).optional(),
  }),
})

// Agent with a tool that uses flows internally
const billing = agent({
  id: 'billing',
  prompt: billingPrompt,
  tools: {
    lookupInvoices: {
      description: 'Search customer invoices',
      parameters: z.object({ customerId: z.string() }),
      execute: async ({ customerId }) => {
        // Flow inside a swarm agent's tool — retries + tracing
        const invoiceLookup = flow('invoice-lookup', async (flow) => {
          const invoices = await flow.step('fetch', () =>
            fetchInvoices(customerId),
            { retry: { attempts: 3 } }
          )
          return flow.step('summarize', () =>
            generate(summaryPrompt, { model: gpt4mini, input: { invoices } })
          )
        })
        return invoiceLookup.run()
      },
    },
    // Blackboard exposed as focused tools — agent can read/write shared context
    ...customerBoard.asTools(),
  },
  handoffs: ['triage', 'refunds'],
})

// Run the swarm with shared blackboard context
await customerBoard.set('customerId', customerId)
const result = await swarm({
  agents: { triage, billing, shipping, refunds },
  startAgent: 'triage',
  input: { message: customerMessage },
  model,
})

Quick reference

ToolCategoryWhat it does
parallel()CompositionRun agents concurrently, get typed named results
pipeline()CompositionChain agents sequentially with transforms and per-step retry
consensus()CompositionRun agents, vote on a decision
swarm()CompositionDynamic LLM-decided routing with conditional handoffs, tool filtering, cost tracking
flow()PrimitiveNamed steps with retries, fallbacks, suspend/resume, tracing
blackboard()PrimitiveShared typed state between agents
handoff()PrimitiveValidated context transfer with optional summarization
delegate()PrimitiveSubagent execution wrapped as a callable tool

On this page