Agent
Bundle a prompt with optional model and tools into a reusable agent primitive for composition utilities.
Compositions need the same prompt+model+tools bundle everywhere: define it once with agent(). 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. For restart-safe keyed conversations, bind an adapter GenerationModel and open a durable Agent Session.
import { agent } from "@use-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: { search: searchTool }, // optional: named agent-specific tools
handoffs: ["editor"], // optional: agents this agent can route to in a swarm
swarmTools: ["search"], // optional: tool whitelist for swarm context
});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 "@use-crux/ai";
const { results } = await parallel({
id: "agent-parallel-1",
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 schemaOptions
| 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 |
Let an agent call a child agent
Put an Agent directly in another Agent's tools map when the parent model
should decide whether to invoke a specialized child and wait for its answer.
The map key is the tool name the parent model sees; the child's id remains
its execution identity.
import { z } from "zod";
import { agent, prompt } from "@use-crux/ai";
const researcher = agent({
id: "research-agent",
description: "Research one topic and return a short answer",
prompt: prompt({
id: "research-prompt",
input: z.object({ topic: z.string() }),
output: z.object({ answer: z.string() }),
prompt: ({ input }) => `Research: ${input.topic}`,
}),
});
const coordinator = agent({
id: "coordinator",
prompt: prompt({
id: "coordinator-prompt",
input: z.object({ question: z.string() }),
prompt: ({ input }) => input.question,
}),
tools: {
research: researcher,
},
});
const { results } = await parallel({
id: "answer-question",
agents: { coordinator },
context: { question: "Why do leaves change color?" },
model,
});
console.log(results.coordinator.output);This is progressive disclosure: the parent initially sees only the child's
name, description, and input schema. The child's prompt, context, tools, and
execution are used only if the parent calls research. Prefer an ordinary Tool
for a bounded function or API call. Prefer delegate() when application code
must own a custom handoff, execution callback, or typed framework context.
Use a direct child Agent when the model should choose a self-contained Agent
whose declared prompt and tools already describe the work.
The child's Prompt input becomes the model-facing Tool schema as follows:
| Child Prompt input | Exposed Tool input | Child receives |
|---|---|---|
| Object schema | The same object schema | The validated object |
| No input schema | {} | {} |
| Non-object or mixed-root schema | { input: childSchema } | The validated value inside input |
Without a wrapper, the call is foreground: Crux accepts one child Work, waits
for it, and returns the child's exact validated structured or multimodal output
as the Tool result.
In Run Detail, the call stays nested under the parent Agent: its ordinary Tool
call contains a separate child Agent row. What that child generates and which
tools it calls follow your existing observability capture and redaction policy.
Invalid Tool input is rejected before child Work begins. The child receives
only its declared input and runs with its own prompt, use, and tools; it does
not inherit the parent's prompt, tools, history, request details, or runtime
controls. Cancellation follows the attached Work ancestry, while child failure,
cancellation, and timeout use the ordinary Tool-loop error path.
Wrap a child with backgroundable() when the parent model should be able to
start the same child without waiting, inspect its process-local status, and
retrieve its result on a later model step:
import { backgroundable } from "@use-crux/core/agent";
const coordinator = agent({
id: "coordinator",
prompt: coordinatorPrompt,
tools: { research: backgroundable(researchAgent) },
});This is a model-facing, process-local capability—not an application Work API or a durable job system. See Background Agent Work for the lifecycle, limits, and durable alternatives.
Per-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({
id: "agent-pipeline-1",
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({
id: "agent-parallel-2",
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 "@use-crux/core/agent";
if (isAgent(entry)) {
// entry is Agent, has .id, .prompt, .model, .tools, .handoffs
} else {
// entry is a plain async function
}Parallel
Run agents concurrently with typed results.
Pipeline
Chain agents sequentially with typed data flow.
Consensus
Multiple agents vote on a decision.
Swarm
Dynamic peer-to-peer routing via LLM-decided handoffs.
API Reference
Full agent API.
Background Agent Work
Let a coordinator start and later rejoin process-local child Work.