# Async tools

A tool does not have to answer in the same decision.

Your worker can accept the call, do the work on its own schedule, and report the
result later. The turn stays open. The run continues when the answer arrives.

## Example

Answer `tool.execute` without ending the call. Start the work and return an
empty decision.

```javascript title="server.mjs"
const tools = [
    {
        name: "render_report",
        description: "Render a report. This takes a while.",
        input: {
            type: "object",
            properties: { topic: { type: "string" } },
            required: ["topic"]
        }
    }
];

function decide({ trigger, proposed }) {
    if (trigger.type === "session.start") {
        return { agent: { model: "claude-haiku-4-5", tools } };
    }

    // Start the work under the call id and leave the call open.
    if (trigger.type === "tool.execute") {
        startRender(trigger.id, trigger.input.value);
        return {};
    }

    return proposed;
}
```

When the render finishes, end the call by its `id`.

```jsonc
{ "type": "tool.result", "id": "<toolCallId>", "result": "https://reports/42.pdf" }
```

Send it to the engine's client API. See
[REST API](./250-api.md#post-apimachinesessionssession_idcallssettle).

## Going async

Answer a `tool.execute` with no `tool.result` and no `tool.error` and the call
stays open.

This is the same state a client-side tool is in while the browser works. An
async tool is the worker's version of that. Declare it as an ordinary tool and
run it on your own schedule.

## Waiting

The turn stays open while a call is in flight. Other tool calls that finish
first record their results.

The engine does not prompt the model again until every open call has ended. The
model never sees a half-finished turn.

The wait is saved state. It uses no compute, and it survives a restart of the
engine or your worker.

## Ending the call

Report the result and name the call by `id`.

```typescript
{ type: "tool.result", id: string, attempt?: number, result?: unknown }
{ type: "tool.error", id: string, error: string, retryable: boolean, attempt?: number }
```

`attempt` is optional. Include it to block a result from an old executor that a
retry replaced.

The engine records the result. When no calls are in flight, it prompts the model
again.

## Timeouts

A client-handled call waits forever by default. It is the one effect with no
limit, because a person may be answering it.

To limit the wait, give the `tool.call` a `retry` policy with an
`attempt_timeout_secs` or a `total_timeout_secs`. When either expires, the call
fails. The engine then retries it or ends it, under the policy. See
[Retries](./210-retries.md).

## Next

- [Tool calls](./60-tools.md): the rules these follow.
- [Client-side tools](./150-client-tools.md): the same wait, run by the browser.
- [Interrupts](./100-interrupts.md): pause the conversation for a person.
