How Custom Tools Function in the Research-Desk Workshop: A Complete Technical Guide

The research-desk workshop implements custom tools through a dispatch_analysts function that allows the head-of-research agent to fan out analysis tasks to multiple analyst agents, orchestrated by a long-lived server-side watcher that handles invocation, parallel processing, and result compilation.

The anthropics/cwc-workshops repository demonstrates advanced agent orchestration patterns through its research-desk workshop, where custom tools enable sophisticated multi-agent workflows. Understanding how these custom tools function reveals the architecture behind delegating work from a head agent to specialized analyst agents while maintaining conversation state and ensuring resilience against server restarts.

Declaring Custom Tools in the Provisioning Layer

Custom tools in the research-desk workshop are declared during the agent provisioning phase and attached to specific agent roles. The head-of-research agent receives its capabilities through the HEAD_AGENT_TOOLS array defined in src/lib/provision.ts.

The dispatch_analysts tool accepts an array of stock tickers and an optional focus parameter:

// src/lib/provision.ts
export const HEAD_AGENT_TOOLS: AgentTool[] = [
  {
    name: "dispatch_analysts",
    description: "Dispatch an analyst for each ticker supplied",
    input_schema: {
      type: "object",
      properties: {
        tickers: { type: "array", items: { type: "string" } },
        focus: { type: "string" },
      },
      required: ["tickers"],
    },
  },
];

When the head agent is provisioned, it receives this tool definition and can invoke it during conversations. Upon invocation, the session enters a requires_action state, pausing the conversation until the platform receives a result from the client—in this architecture, the server itself acts as that client.

The Orchestration Mechanism

The workshop implements a long-lived singleton called DeskOrchestrator that manages the lifecycle of custom tool invocations. This orchestrator establishes a persistent watcher through the ensureWatching method, which streams events from the head session and intercepts tool usage.

When the head agent calls dispatch_analysts, the orchestrator detects the event through its event stream:

// src/lib/orchestrator.ts
if (event.type === "agent.custom_tool_use" && event.name === "dispatch_analysts") {
  await this.handleDispatch(client, headSessionId, event);
}

The orchestrator immediately forwards the captured event to handleDispatch, which extracts the tool payload including the tickers array and optional focus string, then initiates the fan-out analysis workflow.

Handling Tool Invocation and Fan-Out

The handleDispatch method serves as the core processing engine for custom tool requests. It creates a DispatchState record to track the operation, then invokes analyzeMany to launch separate analyst sessions for each ticker in parallel.

After all analyst agents complete their work, the orchestrator compiles results through compileDispatchResult and constructs a JSON payload. The system then sends this payload back to the head session as a user.custom_tool_result event tied to the original tool-use ID:

// src/lib/orchestrator.ts – excerpt
const toolUseId = event.id ?? "";
const input = (event.input ?? {}) as { tickers?: unknown; focus?: unknown };
const tickers = Array.isArray(input.tickers) ?: [];
const focus = typeof input.focus === "string" ? input.focus : "";

await client.beta.sessions.events.send(headSessionId, {
  events: [{
    type: "user.custom_tool_result",
    custom_tool_use_id: toolUseId,
    content: [{ type: "text", text: resultPayload }],
  }],
} as never);

This fulfillment pattern unblocks the head agent's conversation, allowing it to receive aggregated analysis results from multiple specialist agents as if they came from a single tool invocation.

Resilience and Backlog Recovery

The research-desk workshop implements robust recovery mechanisms to handle server restarts during active tool operations. The handleBacklog method in src/lib/orchestrator.ts scans recent session events upon startup to identify any unanswered dispatch_analysts calls.

If the system detects pending tool invocations that occurred while the server was offline, it automatically re-processes them through the standard dispatch workflow. This guarantees that no analysis requests are lost due to infrastructure interruptions, ensuring exactly-once execution semantics for the custom tool operations.

Summary

  • Tool Definition: Custom tools are declared in research-desk/src/lib/provision.ts and bound to agents during provisioning, with dispatch_analysts enabling ticker-based analysis delegation.
  • Event Interception: The DeskOrchestrator singleton watches session streams for agent.custom_tool_use events and routes them to handleDispatch.
  • Parallel Execution: The analyzeMany function fans out work to multiple analyst agents simultaneously, while compileDispatchResult aggregates their outputs.
  • Synchronous Response: Results return as user.custom_tool_result events containing JSON payloads, unblocking the head agent's conversation.
  • Fault Tolerance: The handleBacklog mechanism ensures pending tool calls survive server restarts by reprocessing events upon recovery.

Frequently Asked Questions

How does the head agent know when the custom tool has completed?

The head agent's session remains in a requires_action state until the orchestrator sends a user.custom_tool_result event matching the original tool-use ID. This synchronous blocking mechanism ensures the agent waits for all analyst sub-tasks to complete before continuing the conversation.

What happens if the server crashes while analysts are processing tickers?

The handleBacklog method in research-desk/src/lib/orchestrator.ts scans recent events when the server restarts, identifying any dispatch_analysts invocations that lack corresponding results. It automatically re-queues these pending operations, ensuring no analysis requests are lost during infrastructure interruptions.

Can the custom tool handle multiple tickers simultaneously?

Yes. The dispatch_analysts tool accepts an array of tickers and processes them in parallel through the analyzeMany function. Each ticker spawns a separate analyst session, and the orchestrator waits for all parallel analyses to complete before compiling the consolidated result payload.

Where is the analyst agent logic implemented?

The actual analysis execution resides in research-desk/src/lib/analysis.ts, which exports analyzeMany for parallel ticker processing and compileDispatchResult for formatting the aggregated output. The orchestrator coordinates these functions but delegates the domain-specific analysis work to this dedicated module.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →