# How to Implement Custom Tool Calls that Require Action and Wait for Server Response in Anthropic's CWC Workshops

> Learn to implement custom tool calls in Anthropic's CWC Workshops. Pause execution with requires_action, perform server-side async work, and resume agent sessions.

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

---

**The CWC-Workshops repository demonstrates how to implement custom tool calls that pause agent execution with a `requires_action` stop-reason, allowing a server-side orchestrator to perform asynchronous work and resume the session by returning a `user.custom_tool_result` matched to the original tool-use ID.**

In the *anthropics/cwc-workshops* repository, the Research Desk workshop provides a production-ready pattern for handling **custom tool calls that require_action** and wait for external server processing. This architecture enables AI agents to initiate complex, long-running operations—such as fanning out analysis tasks to multiple sub-agents—while the main session remains idle until the orchestrator completes the work and signals continuation.

## Understanding the requires_action Flow

The platform implements a specific lifecycle for asynchronous custom tools. When an agent invokes a custom tool, the session enters a paused state that requires external resolution before the conversation can continue.

The execution flow follows these steps:

1. **Agent Invocation**: The head agent calls a custom tool (e.g., `dispatch_analysts`), triggering an `agent.custom_tool_use` event and immediately setting `stop_reason.type` to `"requires_action"` in [`research-desk/src/lib/orchestrator.ts`](https://github.com/anthropics/cwc-workshops/blob/main/research-desk/src/lib/orchestrator.ts).

2. **Orchestrator Detection**: The server-side orchestrator listens for this event type and fans out the requested work (such as creating sub-sessions per ticker symbol).

3. **Result Delivery**: Upon completion, the orchestrator sends a `user.custom_tool_result` payload containing the same `id` as the original tool-use event to satisfy the pending action.

4. **Session Resumption**: The agent receives the result, the `requires_action` state clears automatically, and the head continues processing (for example, synthesizing a ranked report from analyst outputs).

## Declaring Custom Tools in Prompt Files

Before the orchestrator can handle a tool, you must declare it in the prompts directory. Tool definitions reside in `research-desk/prompts/*.md` files and describe the contract between agent and server.

```markdown

# dispatch_analysts

This tool asks the server to run an analyst for each ticker you provide. 
The agent will pause until the server returns a `user.custom_tool_result`.

```

Each markdown file in this directory defines one custom tool. The filename typically matches the tool name used in the orchestrator logic, creating a clear mapping between the agent's available functions and server-side implementations.

## Handling requires_action in the Orchestrator

The core logic for managing **custom tool calls that require_action** lives in [`research-desk/src/lib/orchestrator.ts`](https://github.com/anthropics/cwc-workshops/blob/main/research-desk/src/lib/orchestrator.ts). This file contains the event handler that detects paused sessions and coordinates the asynchronous work.

```typescript
// research-desk/src/lib/orchestrator.ts
export async function handleEvent(event: StreamEvent) {
  if (event.type === "agent.custom_tool_use") {
    switch (event.tool_name) {
      case "dispatch_analysts":
        const analystResults = await fanOutAnalysts(event.args.tickers);
        await sendToolResult({
          id: event.id,
          result: analystResults,
        });
        break;
      // Add additional custom tools here
    }
  }
}

```

The orchestrator checks `event.stop_reason?.type !== "requires_action"` in [`research-desk/src/lib/sessions.ts`](https://github.com/anthropics/cwc-workshops/blob/main/research-desk/src/lib/sessions.ts) before proceeding with normal operations, ensuring that sessions with pending actions are properly quarantined until resolved.

## Sending Results Back to Resume the Agent

To unblock a paused session, you must send a `user.custom_tool_result` with the exact `id` from the original `agent.custom_tool_use` event. The helper function typically posts to your platform's API endpoint:

```typescript
async function sendToolResult(payload: {
  id: string;
  result: any;
}) {
  await fetch("/api/tool-result", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(payload),
  });
}

```

Once this payload reaches the platform, the session automatically resumes because the `requires_action` condition is satisfied. The agent then receives the `result` data as if it were a synchronous tool return value.

## Client-Side Tool Invocation

From the agent's perspective, invoking a custom tool that requires server action follows standard tool-use patterns. The key difference is that the session state indicates idleness rather than completion:

```typescript
// Example client-side usage (head agent)
await agent.runTool("dispatch_analysts", { tickers: ["NVDA", "AMD", "MU"] });
// Session enters requires_action state → orchestrator processes → result arrives → conversation continues

```

The `runTool` method initiates the call, but execution pauses immediately after streaming the `agent.custom_tool_use` event. The agent remains in this idle state—freeing compute resources—until your orchestrator posts the matching result.

## Why requires_action Matters for Asynchronous Operations

Using **custom tool calls that require_action** provides three critical advantages for complex AI workflows:

- **Resource Efficiency**: Rather than busy-waiting or maintaining open connections during long-running operations, the platform holds the session idle, freeing resources while the server performs work.

- **Clear Contract**: The `requires_action` state creates an unambiguous protocol where the server must return a `user.custom_tool_result` with the matching event `id` to unblock the agent.

- **Scalable Fan-Out**: As demonstrated in the Research Desk workshop, this pattern supports fanning out work to multiple sub-agents or external services without blocking the main conversation thread.

## Summary

- **Custom tool calls that require_action** pause agent execution and wait for server-side orchestration in the CWC-Workshops framework.
- Declare tools in `research-desk/prompts/*.md` files to define the agent-facing interface.
- Implement handlers in [`research-desk/src/lib/orchestrator.ts`](https://github.com/anthropics/cwc-workshops/blob/main/research-desk/src/lib/orchestrator.ts) to process `agent.custom_tool_use` events and manage asynchronous work.
- Return results via `user.custom_tool_result` payloads matched by event `id` to automatically resume paused sessions.
- Check `stop_reason.type` in [`research-desk/src/lib/sessions.ts`](https://github.com/anthropics/cwc-workshops/blob/main/research-desk/src/lib/sessions.ts) to handle session state transitions correctly.
- This pattern enables scalable, resource-efficient workflows where agents coordinate with external systems without blocking.

## Frequently Asked Questions

### What is the requires_action stop reason in CWC-Workshops?

The `requires_action` stop reason is a session state that indicates the agent has invoked a **custom tool** requiring external server processing. When this state appears in `event.stop_reason.type`, the agent cannot proceed until the orchestrator sends a matching `user.custom_tool_result`. This mechanism creates a clean separation between the agent's decision-making and long-running server operations.

### How does the orchestrator match results to specific tool calls?

The orchestrator matches results using the event `id` field. When handling an `agent.custom_tool_use` event in [`research-desk/src/lib/orchestrator.ts`](https://github.com/anthropics/cwc-workshops/blob/main/research-desk/src/lib/orchestrator.ts), the code captures `event.id` and includes that same identifier when calling `sendToolResult()`. The platform uses this ID to correlate the `user.custom_tool_result` with the pending action, ensuring the correct session resumes with the correct data.

### Can I implement multiple custom tools in the same orchestrator?

Yes. The switch statement in `handleEvent()` naturally supports multiple tools. Add additional cases for each custom tool name, ensuring each returns a `user.custom_tool_result` with the appropriate payload structure. Each tool declaration in `research-desk/prompts/*.md` corresponds to one case in the orchestrator's event handler.

### Where should I check for requires_action states in my code?

Check `requires_action` states in your session management utilities, specifically in [`research-desk/src/lib/sessions.ts`](https://github.com/anthropics/cwc-workshops/blob/main/research-desk/src/lib/sessions.ts). The code should verify that `event.stop_reason?.type !== "requires_action"` before attempting to process normal conversation events. This check prevents premature processing of events while the session waits for external tool results.