Skip to main content
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. You can also ask the user a question when your agent needs them to choose before it carries on. On a thread, you record tool calls as thread events 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.
  • 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.
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 as an echo, then 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.

Asking a question

When your agent needs a user to choose before it can carry on, call the requestDiscussionQuestion mutation. Plain shows a card in the discussion with each question and its options. Plain always adds an Other box for a typed answer, so a user picks an option, types their own, or clicks Skip to dismiss the card.
A question card asking which invoice to refund, with two options, an Other box, and Skip and Submit buttons
The discussion’s agentStatus will automatically be set to TOOL_CALL_APPROVAL_PENDING while the card is open.
  • questions: 1–4 per call. A card holds at most 8 across every ask.
  • question: 1–500 characters. Set isMultiSelect to true to let the user pick more than one option.
  • options: 2–4 per question. label is 1–80 characters and unique within the question, and description is 1–240 characters. Put the option you recommend first, and don’t add your own Other option.
  • Asking while a card is open adds the new questions to that card, skipping any with the same text as one already on it. The response returns the whole card.
  • Only the agent the discussion is bound to can ask, and not on a resolved discussion.
Plain sends discussion.question_resolved when the user answers or dismisses the card:
  • ANSWERED: selections holds the option ids the user picked and any otherText they typed. Plain sets the discussion to IN_PROGRESS, so carry on with the answer and set IDLE when the turn ends.
  • DISMISSED: the user closed the card without answering, and selections is empty. Plain sets the discussion to IDLE unless a tool call is still PENDING. Wait for the user’s next message.

On threads

On a thread there is no approval flow. You can record what the agent did by using thread events so you can see it on a thread’s timeline. The API key needs threadEvent:create.
title is the line users see on the timeline. components hold the detail, built from 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 that mentions them, or hand the thread off.