Crux
GuidesCompositions

Parallel

Run multiple agents concurrently and get typed results as a named record.

parallel() runs a named map of agents concurrently and returns all results as a typed record. Use it when agents are independent — reviewing content, analyzing from different angles, or gathering data from multiple sources.

parallel-review.ts
import { parallel } from '@crux/ai'
import { agent } from '@crux/core/agent'

const result = await parallel({
  agents: {
    factCheck: factCheckerAgent,
    style: styleReviewerAgent,
    seo: seoAnalyzerAgent,
  },
  context: { content: articleDraft },
  model: claude35,
})

result.results.factCheck.output  // typed from factCheckerAgent's output schema
result.results.style.output      // typed from styleReviewerAgent's output schema
result.results.seo.output        // typed from seoAnalyzerAgent's output schema
result.durationMs                // total wall-clock time (= slowest agent)

How it works

  1. All agents start executing at the same time (Promise.all)
  2. Each agent receives the same context
  3. When all finish, results are returned as a Record<name, AgentResult<typed>> — each result is typed from the agent's output schema
  4. Each result is an AgentResult with .agentId, .output, .durationMs

Total time is the duration of the slowest agent, not the sum.

Using results

Results are returned as a named record, so you access each agent's output by name with full type safety:

const { results, durationMs } = await parallel({
  agents: { factCheck: factCheckerAgent, style: styleReviewerAgent },
  context: { content: draft },
  model,
})

const approved = results.factCheck.output.score > 0.8
  && results.style.output.score > 0.7

const feedback = [
  ...results.factCheck.output.issues,
  ...results.style.output.suggestions,
]

Error handling

By default, parallel() uses fail-fast semantics — if any agent throws, the entire composition fails immediately.

For best-effort execution, use onError: 'continue':

best-effort.ts
const result = await parallel({
  agents: { a: agentA, b: agentB },
  context: { content: draft },
  model,
  onError: 'continue',
})

// result.results contains successful agents' results
// result.settled contains discriminated results for all agents:
if (result.settled.a.status === 'success') {
  // result.settled.a.value is AgentResult
}
if (result.settled.b.status === 'error') {
  // result.settled.b.error is Error
}

In continue mode, result.settled contains a discriminated union for each agent:

  • { status: 'success', value: AgentResult } — agent succeeded
  • { status: 'error', error: Error } — agent failed

Fallback integration

Per-agent model fallbacks work naturally. Each agent's model can be wrapped with fallback():

import { fallback } from '@crux/core'

const reviewer = agent({
  id: 'reviewer',
  prompt: reviewPrompt,
  model: fallback(claude35, gpt4),  // try Claude first, fall back to GPT-4
})

The composition's onError only triggers after all model fallbacks are exhausted.

Devtools

In the devtools timeline, parallel compositions appear as:

comp  → parallel  3 agents
comp  ← reviewer  1.2s
comp  ← factCheck  0.9s
comp  ← seo  1.8s
comp  ← parallel  3/3 ok  1.8s

On this page