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

> Discover how custom tools function in the research-desk workshop. Learn about dispatching analysis tasks with custom tools, parallel processing, and result compilation in this technical guide.

- Repository: [Anthropic/cwc-workshops](https://github.com/anthropics/cwc-workshops)
- Tags: how-to-guide
- Published: 2026-07-18

---

**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`](https://github.com/anthropics/cwc-workshops/blob/main/src/lib/provision.ts).

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

```ts
// 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:

```ts
// 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:

```ts
// 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`](https://github.com/anthropics/cwc-workshops/blob/main/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`](https://github.com/anthropics/cwc-workshops/blob/main/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`](https://github.com/anthropics/cwc-workshops/blob/main/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`](https://github.com/anthropics/cwc-workshops/blob/main/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.