An example Plane Agent built as a Cloudflare Worker. When mentioned in a work item comment, it generates a PRD (Product Requirements Document) using OpenAI and links it back to the issue as a Plane page.
Use this project as a reference for building your own Plane agents.
Plane Agents are AI-powered apps that users can @mention in work item comments. Each mention triggers an Agent Run — a tracked interaction session where the agent can think, act, and respond.
This agent follows a simple tool-chain workflow:
- Receive webhook — Plane sends an
agent_run_activityevent when the agent is mentioned - Get issue details — Fetches the work item title and description via the Plane SDK
- Generate PRD — Sends the issue context to OpenAI to produce a structured PRD
- Create page — Publishes the PRD as a Plane page in the project
- Link to issue — Attaches the page URL back to the original work item
The agent communicates progress back to Plane using Agent Run Activities:
| Activity Type | Purpose |
|---|---|
thought |
Shows reasoning (ephemeral, not visible as a comment) |
action |
Indicates tool execution with parameters |
response |
Final answer to the user (creates a comment) |
elicitation |
Asks the user for more information |
error |
Reports failures |
src/
├── index.ts # Entry point — routes requests (OAuth, webhooks, health)
└── lib/
├── helpers.ts # OAuth configuration and API client setup
├── store.ts # KV storage for workspace credentials
├── templates.ts # HTML template for OAuth success page
├── types.ts # TypeScript types (webhook payloads, tool names)
├── webhook-handler.ts # Webhook processing and token refresh
└── agent/
├── agentClient.ts # Core agent loop — orchestrates LLM calls and tool execution
├── prompt.ts # System prompt defining agent behavior
└── tools.ts # Tool implementations (Plane API + OpenAI)
-
AgentClient(agentClient.ts) — Implements a ReAct-style loop. Each LLM response is prefixed withTHINKING:,ACTION:,RESPONSE:,ELICITATION:, orERROR:to determine the next step. The loop runs until a terminal activity (response/error/elicitation) or max iterations (10). -
Tools (
tools.ts) — Stateless functions called in sequence. Results are stored on theAgentClientinstance so each tool can access the output of previous tools without parameter passing. -
Webhook handler (
webhook-handler.ts) — Validates events, refreshes expired OAuth tokens, and hands off to the agent client.
git clone <repo-url>
cd prd-agent
npm installwrangler kv namespace create CREDENTIALS_KVCopy the namespace ID into wrangler.jsonc.
- Go to your Plane workspace settings
- Navigate to Apps > Create App
- Configure:
- App Name: PRD Agent
- Redirect URL:
https://<your-worker-url>/oauth/callback - Webhook URL:
https://<your-worker-url>/webhook - Enable App Mentions (this makes it an agent)
- Enable the
agent_run_user_promptwebhook event
- Note the Client ID and Client Secret
Update wrangler.jsonc with your values:
Set secrets:
wrangler secret put PLANE_CLIENT_SECRET
wrangler secret put OPENAI_API_KEYnpm run deploy- Visit
https://<your-worker-url>/oauth/setup - Authorize the app for your workspace
- The agent is now ready to use
Mention the agent in any work item comment:
@PRDAgent create a PRD for this issue
The agent will generate a PRD, create a page, and link it to the issue.
npm run dev # Start local dev server (wrangler dev)
npm run deploy # Deploy to Cloudflare
npm run cf-typegen # Generate TypeScript types for Cloudflare bindings| Variable | Type | Description |
|---|---|---|
PLANE_CLIENT_ID |
var | Plane OAuth Client ID |
PLANE_CLIENT_SECRET |
secret | Plane OAuth Client Secret |
PLANE_BASE_URL |
var | Plane API base URL |
PLANE_BASE_APP_URL |
var | Plane app base URL |
SERVER_URL |
var | This worker's public URL |
OPENAI_API_KEY |
secret | OpenAI API key |
| Endpoint | Method | Description |
|---|---|---|
/ |
GET | Root greeting |
/health |
GET | Health check |
/oauth/setup |
GET | Start OAuth installation flow |
/oauth/callback |
GET | OAuth callback handler |
/webhook |
POST | Plane webhook receiver |
MIT
{ "vars": { "PLANE_CLIENT_ID": "<your-client-id>", "PLANE_BASE_URL": "https://api.plane.so", "PLANE_BASE_APP_URL": "https://app.plane.so", "SERVER_URL": "https://<your-worker-url>" } }