> ## Documentation Index
> Fetch the complete documentation index at: https://www.plain.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Tool calls

> Show users which tools your agent ran, and in discussions, wait for approval before running one.

Plain shows your agent's tool calls differently depending on where it is working.

In a discussion, tool calls are interactive: you report each call, the user watches it run, and you can pause a call until the user approves it.

On a thread, you record tool calls as [thread events](/docs/graphql/events/create-thread-event) on the timeline. Those are a log for the team, not something a user can act on.

## In discussions

Your machine user's API key needs to have the `threadDiscussion:read` and `threadDiscussion:edit` permissions.

### Report a tool call

Call `upsertDiscussionToolCall` with a `toolCallId` you define.

The initial tool call should be in a status of `PENDING`. When your tool call completes, update the status to `SUCCESS` or `ERROR` depending on the outcome.

Plain shows the call in the discussion with its duration.

<Snippet file="graphql/discussion-tool-call-upsert.mdx" />

* `toolCallId`: yours, unique within the discussion, 1–256 characters of `[A-Za-z0-9_-]`
* `text`: required on every write, max 2000 characters. This is what the user reads.
* `error`: required on `ERROR`, max 4000 characters
* `SUCCESS` and `ERROR` are final. A later write returns `result: NOOP`.

### Asking for approval

For a call that needs a user's decision, you can call the `requestDiscussionToolCallApproval` mutation.

Plain shows a card with `text` as the heading, `justification` underneath, and **Approve** and **Deny** buttons.

The discussion's `agentStatus` will automatically be set to `TOOL_CALL_APPROVAL_PENDING`.

<Snippet file="graphql/discussion-tool-call-approval-request.mdx" />

When requesting approval, the tool call must be in a status of `PENDING`.

Asking again for the same id returns the same approval. Several approvals can be open on one discussion at once.

Plain sends [`discussion.tool_call_approval_requested`](/docs/webhooks/discussion-tool-call-approval-requested) as an echo, then [`discussion.tool_call_approval_resolved`](/docs/webhooks/discussion-tool-call-approval-resolved) when the user makes a decision:

* **`APPROVED`**: means your tool call was approved and you can run the tool.
* **`DENIED`**: means the user rejected your tool call. Your tool will have already been updated to a status of `FAILED` and will include the user's `reviewerNote` if provided. This is a human-provided message as to why the tool call was rejected.
* If your agent stops waiting, report `ERROR` so the call doesn't stay open.

## On threads

On a thread there is no approval flow. You can record what the agent did by using [thread events](/docs/graphql/events/create-thread-event) so you can see it on a thread's timeline.

The API key needs `threadEvent:create`.

```ts theme={null}
const result = await plain.mutation.createThreadEvent({
  input: {
    threadId: thread.id,
    title: "Searched knowledge",
    components: [
      {
        componentText: {
          text: "Query: `refund policy`. 3 results, top match: **Refunds and cancellations**.",
        },
      },
    ],
    isCollapsed: true,
  },
});

if (result.error) throw new Error(result.error.message);
```

`title` is the line users see on the timeline. `components` hold the detail, built from [UI components](/docs/ui-components). Set `isCollapsed` to `true` so a run with many calls doesn't crowd out the conversation. Pass an `externalId` if you want Plain to reject a duplicate tool call thread event.

If a tool on a thread needs a user's decision, ask for it somewhere a user can answer: post a [note](/docs/agents/notes) that mentions them, or [hand the thread off](/docs/agents/threads#hand-off).
