# How to Implement `user.custom_tool_result` for Tool Call Responses in Anthropic Managed Agents

> Learn how to implement user.custom_tool_result to send custom tool execution results back to Anthropic managed agents. Follow our guide for seamless integration.

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

---

**Emit a `user.custom_tool_result` event containing the tool output and the original `custom_tool_use_id` to return custom tool execution results back to the managed agent's event stream.**

The `anthropics/cwc-workshops` repository demonstrates the complete lifecycle of custom tool integration in Claude-managed agents. When a model invokes a custom tool via `agent.custom_tool_use`, your host code must execute the corresponding Python function and bridge the result back through the `user.custom_tool_result` event type. This pattern ensures deterministic request-response pairing and maintains transcript continuity for model reasoning.

## Understanding the `user.custom_tool_result` Event Loop

The event loop in [`ship-your-first-managed-agent/provided.py`](https://github.com/anthropics/cwc-workshops/blob/main/ship-your-first-managed-agent/provided.py) implements a three-stage handshake for custom tool execution. This mechanism separates sandboxed tool calls (handled internally) from custom tools that require host-side Python execution.

### Detecting Custom Tool Requests

When the event stream returns an `agent.custom_tool_use` event, the system distinguishes it from standard sandbox tools. The event contains a unique identifier, tool name, and input arguments that your handler must process.

In [`ship-your-first-managed-agent/provided.py`](https://github.com/anthropics/cwc-workshops/blob/main/ship-your-first-managed-agent/provided.py) (lines 72-73), the detection logic branches based on event type:

```python
elif ev.type == "agent.custom_tool_use":
    # Custom tool detected - requires host-side execution

    tool_boxes[ev.id] = box  # Store placeholder for async result matching

```

The code stores a placeholder "box" in a `tool_boxes` dictionary using the event's `id` as the key. This temporary storage enables the system to match asynchronous responses when they arrive.

### Executing Handlers and Emitting Results

Once detected, the host invokes your custom `handle_tool` function and wraps the return value in a properly structured `user.custom_tool_result` event. The critical requirement is preserving the `custom_tool_use_id` to link the response to the original request.

From [`ship-your-first-managed-agent/provided.py`](https://github.com/anthropics/cwc-workshops/blob/main/ship-your-first-managed-agent/provided.py) (lines 84-92):

```python
result = handle_tool(ev.name, ev.input)
events = [{
    "type": "user.custom_tool_result",
    "custom_tool_use_id": ev.id,
    "content": result,
}]
client.beta.events.create(agent_id=agent.id, events=events)

```

**Key implementation details:**
- The `handle_tool` function executes your local Python implementation
- The `custom_tool_use_id` must exactly match the originating event's `id`
- The `content` field accepts a string containing the tool output
- Events are submitted via the Anthropic client's `beta.events.create` method

### Consuming Results in the Event Stream

When the `user.custom_tool_result` event returns to the event loop, the system retrieves the stored placeholder and injects the content into the conversation transcript as an assistant message.

From [`ship-your-first-managed-agent/provided.py`](https://github.com/anthropics/cwc-workshops/blob/main/ship-your-first-managed-agent/provided.py) (lines 200-209):

```python
elif ev.type == "user.custom_tool_result":
    box = tool_boxes.pop(ev.custom_tool_use_id, None)
    if box:
        transcript.append({"role": "assistant", "content": ev.content})

```

This insertion allows the model to reason about the tool output as part of the ongoing dialogue, effectively treating the custom tool result as if the model had generated the text itself.

## Implementing Custom Tool Handlers

Custom tools are defined in [`ship-your-first-managed-agent/agent.py`](https://github.com/anthropics/cwc-workshops/blob/main/ship-your-first-managed-agent/agent.py) by extending the `handle_tool` function. The following example adds a weather lookup tool:

```python

# ship-your-first-managed-agent/agent.py

def handle_tool(name: str, args: dict) -> str:
    if name == "weather":
        city = args.get("city", "San Francisco")
        # Production implementations would call an external API

        return f"The weather in {city} is sunny, 72°F."
    return f"unknown tool {name}"

```

The existing event loop in [`provided.py`](https://github.com/anthropics/cwc-workshops/blob/main/provided.py) automatically routes the returned string through `user.custom_tool_result`. No additional modifications are required to the event emission logic when adding new tools—only the handler implementation needs extension.

## Complete Implementation Example

The following script demonstrates the full round-trip from tool request to result consumption:

```python
from anthropic import Anthropic

# Assume client and AGENT_ID are initialized

# 1. Model requests custom tool execution

client.beta.events.create(
    agent_id=AGENT_ID,
    events=[{
        "type": "agent.custom_tool_use",
        "id": "tool-123",
        "name": "weather",
        "input": {"city": "Berlin"},
    }],
)

# 2. Host receives the event, executes handle_tool("weather", {"city": "Berlin"})

#    and automatically emits user.custom_tool_result via provided.py logic

# 3. Subsequent event stream reads include the result:

#    {"type": "user.custom_tool_result", "custom_tool_use_id": "tool-123", "content": "..."}

```

## Manual Event Emission for Edge Cases

While [`provided.py`](https://github.com/anthropics/cwc-workshops/blob/main/provided.py) handles automatic emission in standard workflows, you may need to manually emit results when operating outside the built-in loop. Maintain the exact schema structure:

```python
result = "Tool execution output here"

client.beta.events.create(
    agent_id=AGENT_ID,
    events=[{
        "type": "user.custom_tool_result",
        "custom_tool_use_id": "tool-123",  # Must match original request ID

        "content": result,
    }],
)

```

**Critical requirements for manual emission:**
- The `custom_tool_use_id` must match the corresponding `agent.custom_tool_use` event's `id`
- The `content` field must be a string
- The event type must be exactly `user.custom_tool_result`

## Summary

- **`user.custom_tool_result`** bridges custom tool execution back to the managed agent's conversation stream
- **Deterministic pairing** relies on matching `custom_tool_use_id` values between request and response events
- **Implementation flow**: Detect `agent.custom_tool_use` → Execute `handle_tool` → Emit `user.custom_tool_result` → Consume result in transcript
- **Source files**: Core logic resides in [`ship-your-first-managed-agent/provided.py`](https://github.com/anthropics/cwc-workshops/blob/main/ship-your-first-managed-agent/provided.py), while tool definitions belong in [`ship-your-first-managed-agent/agent.py`](https://github.com/anthropics/cwc-workshops/blob/main/ship-your-first-managed-agent/agent.py)
- **Extensibility**: Adding tools requires only extending the `handle_tool` function without modifying event loop mechanics

## Frequently Asked Questions

### What happens if the custom_tool_use_id doesn't match?

If the `custom_tool_use_id` in your `user.custom_tool_result` event does not match any pending tool ID stored in `tool_boxes`, the event loop cannot associate the result with the original request. According to the [`provided.py`](https://github.com/anthropics/cwc-workshops/blob/main/provided.py) implementation (lines 200-209), the code attempts to pop from `tool_boxes` with a default of `None`, and if no box is found, the result is silently ignored rather than inserted into the transcript.

### Can custom tools return structured data like JSON?

Yes, but you must serialize structured data to a string before emitting the `user.custom_tool_result` event. The `content` field accepts string values only. Parse the JSON in your `handle_tool` function, then return `json.dumps(result)` to ensure valid transmission. The model will receive the JSON string and can parse it contextually within the conversation.

### How does this differ from regular sandbox tool_use events?

Standard `agent.tool_use` events execute within Anthropic's managed sandbox environment and return results automatically without host intervention. In contrast, `agent.custom_tool_use` events require your host code to execute Python functions locally and explicitly emit `user.custom_tool_result` events to return data. The custom tool pattern exists specifically for operations that require local system access or proprietary business logic.

### Where is the handle_tool function defined?

The `handle_tool` function is defined in [`ship-your-first-managed-agent/agent.py`](https://github.com/anthropics/cwc-workshops/blob/main/ship-your-first-managed-agent/agent.py) (and [`ship-your-first-managed-agent/agent_complete.py`](https://github.com/anthropics/cwc-workshops/blob/main/ship-your-first-managed-agent/agent_complete.py) for the full example). This function acts as the dispatch layer that maps tool names to your Python implementations. The [`provided.py`](https://github.com/anthropics/cwc-workshops/blob/main/provided.py) file imports and calls this function when processing `agent.custom_tool_use` events, but the actual tool logic resides in your agent implementation file.