Crux
GuidesAgents

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.

agents.ts
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:

composition.ts
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 schema

Options

FieldPurposeDefault
idUnique identifier (used in devtools and swarm routing)Required
descriptionHuman-readable description (used in swarm transfer tool descriptions)undefined
promptThe prompt this agent executesRequired
modelModel override (takes precedence over composition-level model)undefined
toolsAgent-specific toolsundefined
handoffsAgent IDs or { id, when } objects for swarm routing[]
swarmToolsTool name whitelist for swarm contextAll 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.

foreground-child.ts
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 inputExposed Tool inputChild receives
Object schemaThe same object schemaThe 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:

cost-optimization.ts
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:

escape-hatch.ts
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 response

Plain 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
}

On this page