Agent
Bundle a prompt with optional model and tools into a reusable agent primitive for composition utilities.
An agent wraps a prompt with optional execution config — a default model, tools, and handoff targets. Agents are the building blocks for all composition patterns: parallel, pipeline, consensus, and swarm.
import { agent } from '@crux/core/agent'
const reviewer = agent({
id: 'content-reviewer',
description: 'Reviews content for quality and accuracy',
prompt: reviewPrompt,
model: gpt4mini, // optional: overrides composition-level model
tools: [searchTool], // optional: agent-specific tools
handoffs: ['editor'], // optional: agents this agent can route to in a swarm
swarmTools: ['search'], // optional: tool whitelist for swarm context
})| Field | Purpose | Default |
|---|---|---|
id | Unique identifier (used in devtools and swarm routing) | Required |
description | Human-readable description (used in swarm transfer tool descriptions) | undefined |
prompt | The prompt this agent executes | Required |
model | Model override (takes precedence over composition-level model) | undefined |
tools | Agent-specific tools | undefined |
handoffs | Agent IDs or { id, when } objects for swarm routing | [] |
swarmTools | Tool name whitelist for swarm context | All tools |
Why agent?
Composition patterns need to know what to execute (prompt), how to execute it (model), and what tools are available. agent() bundles these into a single, frozen, typed object.
Without agent(), you'd pass these separately to every composition call. With it, you define once and reuse across compositions:
import { parallel } from '@crux/ai'
const { results } = await parallel({
agents: { reviewer, factChecker, seoAnalyzer },
context: { content: articleDraft },
model: claude35, // shared default — reviewer's gpt4mini overrides this
})
results.reviewer.output // typed from reviewer's output schema
results.factChecker.output // typed from factChecker's output schemaPer-agent model overrides
When an agent has a model, it takes precedence over the composition's shared model. This lets you use cheap models for simple tasks and expensive models for complex ones:
const classifier = agent({
id: 'classifier',
prompt: classifyPrompt,
model: gpt4mini, // cheap model for classification
})
const writer = agent({
id: 'writer',
prompt: writePrompt,
// no model — uses composition-level default
})
await pipeline({
steps: [
{ agent: classifier, name: 'classify' },
{ agent: writer, name: 'write', input: (ctx) => ({ topic: ctx.classify.output.category }) },
],
model: claude35, // writer uses this; classifier uses gpt4mini
context: { text: userMessage },
})Escape hatch: plain functions
Composition utilities also accept plain async functions alongside agents. Use this for custom logic, external API calls, or wrapping delegates:
const { results } = await parallel({
agents: {
reviewer: reviewerAgent,
external: async (ctx) => {
const resp = await fetch('https://api.example.com/review', {
method: 'POST',
body: JSON.stringify(ctx),
})
return resp.json()
},
},
context: { content: draft },
})
results.reviewer.output // typed AgentResult
results.external.output // external API responsePlain functions skip devtools agent tracing but still appear as flow steps.
Type guard
Use isAgent() to distinguish agents from plain functions at runtime:
import { isAgent } from '@crux/core/agent'
if (isAgent(entry)) {
// entry is Agent — has .id, .prompt, .model, .tools, .handoffs
} else {
// entry is a plain async function
}