Skip to main content
Build an agent that answers users in a discussion, next to Sidekick. Nothing it writes reaches the customer. For customer threads, see support agents.
Bring your own agent is currently in beta and free to use.

How a turn works

  1. A user opens Ask Sidekick, picks your agent, and sends a prompt.
  2. Plain sends discussion.message_created.
  3. Your agent reports IN_PROGRESS, works, posts an answer, reports IDLE.
  4. For an action a user must decide, the agent asks for approval and waits on discussion.tool_call_approval_resolved.

What you need

1

Create a custom agent

Create a machine user with Type set to Custom agent (on an existing one, Change type). That is what makes it appear in Ask Sidekick.On Add API key, expand the non-recommended groups and grant threadDiscussion and threadDiscussionMessage:
  • threadDiscussion:read and threadDiscussion:edit: read, report status, ask for approvals, resolve or reopen. Status reporting needs both.
  • threadDiscussionMessage:create and threadDiscussionMessage:edit: post answers. upsertDiscussionToolCall also needs threadDiscussion:read.
Add thread:read and customer:read if the agent reads the thread the discussion is on.
2

Subscribe to webhooks

Go to Settings → Webhooks, click Add webhook target, pin version 2026-09-06, and subscribe to discussion.*. Copy the signing secret from Settings → Request signing.
3

Install the SDKs

@team-plain/graphql 3.0.0 or newer and @team-plain/webhooks 1.9.0 or newer.

Decide whether to answer

Your own replies come back as webhooks. Answer only when all of these hold: Call myMachineUser once at startup. Deduplicate on message.id.
Answer the HTTP request with 200 before you start working to avoid webhook retries.

Post an answer

sendDiscussionMessage posts Markdown as the machine user’s public name. That call is what marks the discussion unread.
Check error on the response.

Report what your agent is doing

updateDiscussionAgentStatus: IN_PROGRESS when the turn starts, IDLE when it ends (including after a failure: post the error as a message first).
You cannot set TOOL_CALL_APPROVAL_PENDING or UNKNOWN. Asking for an approval sets pending. You cannot set IDLE or IN_PROGRESS while an approval is open.

Report tool calls

upsertDiscussionToolCall is keyed by an id you choose. Report PENDING, then the same id as SUCCESS or ERROR.
  • toolCallId: yours, unique in the discussion, 1–256 characters [A-Za-z0-9_-]
  • text: required every write, max 2000 characters
  • error: required on ERROR, max 4000 characters
  • SUCCESS and ERROR are final; a later write returns result: NOOP

Gate an action on a user

Report the call, then ask for approval. Plain shows a card with text as the heading, justification underneath, and Approve / Deny.
The toolCallId must already be PENDING. Asking again returns the same approval. The discussion moves to TOOL_CALL_APPROVAL_PENDING. Then discussion.tool_call_approval_requested, and discussion.tool_call_approval_resolved when a user decides:
  • APPROVED: run the call, then upsertDiscussionToolCall with the outcome.
  • DENIED: do not run it and do not report ERROR. Plain already failed the call with reviewerNote.
  • If you stop waiting: report ERROR.
Most decisions happen on the card. resolveDiscussionApproval is for your own review tooling. An agent’s API key is refused: it cannot approve itself.

Resolve the discussion

agentStatus is the turn. changeThreadDiscussionStatus opens or resolves the discussion.
Pass OPEN to reopen. Resolve when the user needs nothing further.

Example and caveats

Two working agents on this API, one per repository: example-aisdk-assistant-agent on the Vercel AI SDK, and example-eve-assistant-agent on eve, Vercel’s agent framework.
  • Keep one model session per discussion, not per message.
  • Nothing posted in a discussion reaches the customer.