Skip to main content
Custom tools execute in your application. For work that cannot finish while the agent is waiting, return a running result promptly, continue the external job, then send a final result when the session is idle. This keeps tool calling from blocking the chat for the full lifetime of an external job. The kickoff releases the pending tool call so the agent can respond and the conversation can continue while your application finishes the work in the background.

How the flow works

  1. The stream emits agent.custom_tool_use.
  2. Your application starts the external workflow.
  3. Within five minutes, post user.custom_tool_result with status: "running".
  4. That kickoff resolves the blocked custom tool call and lets the current agent turn continue.
  5. When the external workflow finishes and the session is idle, post a second result with status: "completed" or "failed".
  6. Herm updates the original running result, emits session.tool_updated, and starts a new agent turn with the final text.
The final result starts a new turn. If another turn is running, the API returns 409 invalid_state. Final custom tool results cannot use delivery: "when_idle"; wait for session.status_idle and retry from your application.

Start the workflow

The pending tool call allows approximately five minutes for a response. Send the kickoff well before that deadline:
status: "running" is not a progress-only event. It is the response to the pending tool call, so the original agent turn resumes immediately with the kickoff text as tool output. Herm does not poll or supervise the external job. Store the sessionId, tool_use_id, and the accepted result event id with your job record.

Complete the workflow

After observing session.status_idle, send the final result with the same tool_use_id:
For failures, use status: "failed", set is_error: true, and include enough context in content for the model to respond appropriately. When a matching running result exists, the completion replaces that result in event history. Its text is submitted to the agent as a new prompt, which starts a separate turn. Without a matching running result, the final result is appended as a new event and still starts a turn.

Update live tool UI

Live streams receive an ephemeral session.tool_updated event:
Patch the existing event identified by payload.event_id with payload.payload. The patch event itself is not retained. The client posting the completion should also apply the updated event returned in the 202 response in case its stream misses the ephemeral patch.

Retry safety

Final-result submissions are not idempotent. Retrying a completion after it succeeded can start another agent turn. Record successful completion responses and only retry when you know the first request was not accepted. An immediate or running result can return 409 no_active_turn after its event was accepted if the session stopped waiting for it. Reconcile event history before retrying to avoid duplicate results.

Permission policies

Custom tools use the same always_allow, always_ask, and always_deny permission policies as other tools. With always_ask, handle the preceding agent.approval_request before expecting agent.custom_tool_use. Your application remains responsible for authorization, input validation, retries, idempotency, and side-effect safety.
Last modified on July 23, 2026