Crux
GuidesAgents

Delegate

Call a subagent as a tool from an orchestrator, with the result validated and transformed through a handoff.

Your orchestrator agent needs to call a subagent as a tool and trust what comes back. A delegate does that in one step: it wraps a handoff contract with an execution function, so when the orchestrating agent calls the tool, the subagent runs and the result is validated and transformed through the handoff automatically.

delegate.ts
import { delegate } from "@use-crux/core/agent";
import { z } from "zod";

const researchDelegate = delegate({
  id: "delegate-research",
  argsSchema: z.object({ query: z.string() }),
  handoff: researchToWriter,
  execute: async (args) => await runResearchSubagent(args.query),
});

Using as a tool

For AI SDK-compatible tools where no framework context is needed, .asTools() returns focused tools:

const agent = prompt({
  id: "orchestrator",
  tools: researchDelegate.asTools(),
});

When the agent calls the research tool, the delegate:

  1. Validates the tool args against argsSchema
  2. Runs the subagent via execute()
  3. Passes the result through the handoff's prepare() for validation and transform
  4. Returns the transformed data as a JSON string

Each delegation validates data at three points: the LLM's tool args, the subagent's return value, and the final transformed output. See the API reference for the schema-by-schema breakdown.

Direct execution

Use .run() to execute a delegate programmatically:

const  = await .(
  { : "cloud migration" },
  ,
);

.; // 'delegate-research'
.data;
DelegateResult<unknown>.data: unknown

Transformed data from the handoff.

.; // LLM-generated summary, if the handoff configured one .; // execution time

Typed context

When your subagent needs framework-specific data (action context, user IDs, project IDs, etc.), type the delegate's context so execute and .run() both enforce it:

typed-delegate.ts
import { delegate } from "@use-crux/core/agent";
import { z } from "zod";

type DelegateCtx = {
  actionCtx: ActionCtx;
  projectId: string;
  userId?: string;
};

const researchDelegate = delegate({
  id: "delegate-research",
  argsSchema: z.object({ query: z.string() }),
  handoff: researchToWriter,
  execute: async (args, ctx: DelegateCtx) => {
    // ctx is fully typed, access framework-specific context
    return await ctx.actionCtx.runAction(runResearch, {
      projectId: ctx.projectId,
      query: args.query,
    });
  },
});

See the API reference for the TCtx generic mechanics.

Using with framework-specific tool factories

For frameworks that have their own tool format, use .run() directly instead of .asTools(). In Convex Agent, prefer createTool() from @use-crux/convex/agent so nested delegate work stays observable:

convex-tool.ts
import { createTool } from "@use-crux/convex/agent";

const research = createTool({
  description: "Delegate research to a specialist",
  inputSchema: researchDelegate.argsSchema,
  execute: async (toolCtx, args, options) => {
    const result = await researchDelegate.run(args, {
      actionCtx: ctx,
      projectId,
      userId,
    });
    return result.data;
  },
});

This gives full control over how the framework context maps to the delegate context. See the Convex Agent guide for the full Convex setup.

All delegate executions are instrumented automatically: delegate:start and delegate:complete events appear in Devtools with input/output payload snapshots and duration.

Next steps

On this page