A Plain agent built on eve, Vercel's agent framework.
The protocol with the Plain API & webhooks is documented here.
This example was example-eve-agent in team-plain/example-internal-agents until it was split into this repository. The package name changed with it, to match the repository.
-
Create a machine user under Settings → Machine users and give it an API key. Turn the "Custom agent" toggle on.
Permissions:
threadDiscussion:read,threadDiscussion:edit,threadDiscussionMessage:create,threadDiscussionMessage:edit,thread:readandthread:reply. -
Copy
.env.exampleto.envat the repository root.cp .env.example .envFill in
PLAIN_API_KEY,PLAIN_WEBHOOK_SECRETfrom Settings → Request Signing, andAI_GATEWAY_API_KEY. -
Get a public https URL that reaches this process. By default it runs on port
8082. Locally,ngrok http 8082. -
Create the webhook under Settings → Webhooks → Add webhook target, pointed at
$PUBLIC_URL/plain/webhookon version2026-09-06, subscribed todiscussion.message_createdanddiscussion.tool_call_approval_resolved.
nvm use
npm ci
npm run serve
Then open a thread in Plain, click Ask Sidekick, pick your agent, and ask it to help with customer requests.
One file each under agent/tools/, and the filename is the name the model sees.
list_thread_queue and search_threads find a thread the discussion was not opened on.
Plain does not always attach one, and without these a threadless Sidekick session has nothing to
work with. Every id they return becomes reachable for the rest of the turn.
read_customer_thread paginates timelineEntries and concatenates llmText, which is Plain's
own rendering of a timeline entry for a language model.
search_knowledge calls searchKnowledgeSources, so Plain does the retrieval and you don't have to deal with vectorising your knowledge sources.
reply_to_customer is the call a customer sees, and the only one gated behind human approval. It reports
the call, asks for approval, waits, and on approval calls replyToThread on the parent thread.
The system prompt is agent/instructions.md. The agent runs on claude-sonnet-5.