Autonomous AI agents that live inside your Plane workspace as virtual team members.
Each agent has a Plane user account, gets assigned tickets, posts comments, moves cards through states, writes code, opens PRs — exactly like a human developer, but running 24/7 in a Docker container.
No extra AI cost. plane-agents runs on top of your existing Claude Code subscription (Pro or Max). It uses your Claude Code OAuth token — not a raw Anthropic API key. If you already use Claude Code, you already have everything you need.
Plane (cloud or self-hosted)
│
└── Python poller — every 60s, picks one ticket, spawns CTO agent
│
├── Ticket in Todo + "write the spec" comment
│ └── CTO Agent
│ └── PM Agent → reads codebase → writes spec → updates description → moves to Review Specs
│ (human reviews spec, moves to Ready for Dev)
│
└── Ticket in Ready for Dev
└── CTO Agent
├── reads ALL comments → derives workflow modifiers
├── Dev Agent → implements, pushes branch
├── Reviewer Agent → code review (loop, max 2 rounds)
├── QA Agent → runs tests, verifies AC (loop, max 2 rounds)
└── opens PR → moves ticket to In Review
Live progress streams to docker compose logs in real time — every tool call the agents make is visible as they work.
| Agent | Role |
|---|---|
| CTO | Sole orchestrator. Fetches ticket context, routes to PM (spec) or directly runs the Dev pipeline (implementation). |
| PM | Reads the codebase to understand the platform, then writes the product spec as HTML from the business/user perspective. Updates the ticket description and moves to Review Specs. |
| Dev | Implements the change, runs quality gates (lint, type-check, build), pushes the branch. |
| Reviewer | Reviews the pushed branch for bugs, security issues, and pattern violations. Reports findings to CTO. |
| QA | Runs the test suite and verifies acceptance criteria. Reports failures to CTO. |
Todo → Review Specs → Ready for Dev → In Progress → Ready for Review
↑ ↑ ↓
└────── human reviews spec ──────────────────────────────── human merges PR
Human gates are baked in: the agent cannot move past Review Specs or Ready for Review on its own. Those transitions require a human.
Comments are how you steer agents. Natural language — no special syntax.
The CTO reads your comment to decide whether to write a spec.
| You comment… | CTO does… |
|---|---|
"can you write the spec for this?" |
PM agent writes spec, moves to Review Specs |
"write up the requirements for this" |
PM agent writes spec, moves to Review Specs |
| (no comment) | Nothing — CTO waits for a human instruction |
The state machine is strict. A ticket must go through Review Specs and be manually moved to Ready for Dev before the Dev pipeline fires. There is no shortcut from Todo directly to implementation.
Once a ticket reaches Ready for Dev, the CTO reads every comment before starting and derives workflow modifiers. The latest human comment is the current instruction.
| You comment… | CTO does… |
|---|---|
"skip QA, we'll test manually" |
Pipeline runs Dev → Reviewer → PR (no QA) |
"code is already pushed, just review and open the PR" |
Skips implementation entirely |
"use branch feat/export-v2" |
Checks out existing branch, skips Dev |
"focus only on the backend" |
Dev scopes changes to backend only |
"don't open a PR yet" |
Runs full pipeline but stops before PR |
- A hosted or self-hosted Plane instance
- One Plane user per agent (e.g.
dev-agent@yourcompany.com), added to the workspace + project - An API key for each agent user (Profile → API Tokens)
- A GitHub PAT with
reposcope for the target repository - A Claude Code subscription (Pro or Max) — agents run using your existing Claude Code OAuth token, not a raw API key. Run
claude setup-tokenlocally to generate one.
Make sure these five states exist in your project (exact names, or override via env vars):
Todo → Review Specs → Ready for Dev → In Progress → Ready for Review
Download env.example from the latest release and fill in your values:
# Plane connection
PLANE_API_BASE=https://app.plane.so # or your self-hosted URL
PLANE_API_KEY=plane_api_xxxxxxxxxxxx
PLANE_WORKSPACE_SLUG=my-workspace
PLANE_AGENT_USER_EMAIL=dev-agent@company.com
# Edition: commercial (EE / Cloud, default) | community (CE / OSS)
PLANE_EDITION=commercial
# Project to watch (identifier, not UUID — e.g. WEB, INFRA)
PLANE_PROJECT_IDENTIFIER=WEB
# State names — must match exactly what's in Plane settings
PLANE_STATE_TODO=Todo
PLANE_STATE_REVIEW_SPECS=Review Specs
PLANE_STATE_READY_FOR_DEV=Ready for Dev
PLANE_STATE_IN_PROGRESS=In Progress
PLANE_STATE_IN_REVIEW=Ready for Review
# Repository the agent will code against
AGENT_REPO=myorg/myrepo
AGENT_REPO_DEFAULT_BRANCH=main
AGENT_GIT_NAME=Dev Agent
AGENT_GIT_EMAIL=dev-agent@company.com
# Auth
GH_TOKEN=ghp_xxxxxxxxxxxx # repo scope
CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-xxx # run: claude setup-token
# Tuning
POLL_INTERVAL=60
LOG_LEVEL=INFODownload docker-compose.yml from the latest release and configure one service per agent:
services:
web-agent:
image: ghcr.io/tools-plus/plane-agents:latest
env_file: .env.web
volumes:
- web-agent-code:/code
restart: unless-stopped
infra-agent:
image: ghcr.io/tools-plus/plane-agents:latest
env_file: .env.infra
volumes:
- infra-agent-code:/code
restart: unless-stopped
volumes:
web-agent-code:
infra-agent-code:docker compose up -d
docker compose logs -f web-agentdocker run -d \
--name web-agent \
--env-file .env.web \
-v web-agent-code:/code \
--restart unless-stopped \
ghcr.io/tools-plus/plane-agents:latestIn Plane, assign any ticket to your agent user, then:
- Move to Todo and comment "can you write the spec for this?" → PM agent writes the description
- Review the spec, move to Ready for Dev → agent picks it up within
POLL_INTERVALseconds - Comment anything at any time to steer the pipeline
| Variable | Required | Default | Description |
|---|---|---|---|
PLANE_API_BASE |
✅ | — | Plane instance URL |
PLANE_API_KEY |
✅ | — | API key for the agent's Plane user |
PLANE_WORKSPACE_SLUG |
✅ | — | Workspace slug |
PLANE_EDITION |
— | commercial |
commercial (EE/Cloud) or community (CE/OSS) |
PLANE_AGENT_USER_EMAIL |
✅ | — | Email of the agent's Plane user |
PLANE_PROJECT_IDENTIFIER |
✅ | — | Project identifier (e.g. WEB, INFRA) |
PLANE_STATE_TODO |
— | Todo |
State name for Todo |
PLANE_STATE_REVIEW_SPECS |
— | Review Specs |
State name for Review Specs |
PLANE_STATE_READY_FOR_DEV |
— | Ready for Dev |
State name for Ready for Dev |
PLANE_STATE_IN_PROGRESS |
— | In Progress |
State name for In Progress |
PLANE_STATE_IN_REVIEW |
— | In Review |
State name for Ready for Review |
AGENT_REPO |
✅ | — | GitHub repo (e.g. myorg/myrepo) |
AGENT_REPO_DEFAULT_BRANCH |
— | main |
Default branch to branch from |
AGENT_GIT_NAME |
— | — | Git commit author name |
AGENT_GIT_EMAIL |
— | — | Git commit author email |
GH_TOKEN |
✅ | — | GitHub PAT with repo scope |
CLAUDE_CODE_OAUTH_TOKEN |
✅ | — | Claude Code OAuth token (claude setup-token) |
POLL_INTERVAL |
— | 60 |
Poll interval in seconds |
LOG_LEVEL |
— | INFO |
INFO or DEBUG |
All agent behaviour is defined in markdown files baked into the image. To customise any agent without rebuilding, mount a folder at /extensions:
docker run \
-v $(pwd)/my-extensions:/extensions \
--env-file .env.web \
ghcr.io/tools-plus/plane-agents:latestAny .md file named after an agent is appended to that agent's base definition at startup. The base instructions stay intact — your content is additive only.
| File | Agent extended |
|---|---|
cto.md |
CTO — routing rules, workflow modifiers, pipeline behaviour |
pm.md |
PM — spec format, sections, tone |
dev.md |
Dev — stack-specific rules, forbidden files, lint commands |
reviewer.md |
Reviewer — review checklist, security rules |
qa.md |
QA — test commands, coverage thresholds |
Example my-extensions/dev.md:
## Project-Specific Rules
- Backend is Django, frontend is React. API in apps/api/, frontend in web/
- Never modify files under legacy/ — that module is frozen
- Always run `make lint` before pushing
- All new API endpoints need a test in tests/api/Skills (like /plane) use a different naming convention — append .skill.md:
| File | Skill extended |
|---|---|
plane.skill.md |
/plane — ticket context fetching |
Example my-extensions/plane.skill.md:
## Additional Context
- Also fetch the parent epic if one exists
- Include label names in the summaryAt startup you'll see:
[init] Extending agent: dev.md
[init] 1 agent extension(s) applied
[init] Extending skill: plane.md
Every task gets a directory in /code/.inbox/task-<id>/ inside the container:
task-1750123456-abc12345/
cto-output.log ← full CTO + all subagent output (written at end)
summary.json ← final status, pr_url, decisions
000-cto-plan.json ← workflow modifiers parsed from comments
001-pm-completion.json ← PM spec summary (PM route)
002-dev-complete.json ← branch + files changed (Dev route)
003-reviewer-findings.json
004-qa-findings.json
...
# Watch live progress (tool calls stream every ~2s)
docker compose logs -f
# List recent tasks
docker exec web-agent ls /code/.inbox/
# Check task outcome
docker exec web-agent cat /code/.inbox/<task-id>/summary.jsongit clone https://github.com/tools-plus/plane-agents.git
cd plane-agents
docker build \
--platform=linux/amd64,linux/arm64 \
-t plane-agents:local \
-f docker/Dockerfile .Same image, different env file — one agent per project/repo combination:
| Agent | Project | Repo | Env file |
|---|---|---|---|
web-agent |
WEB | myorg/plane |
.env.web |
infra-agent |
INFRA | myorg/helm-charts |
.env.infra |
docs-agent |
DOCS | myorg/docs |
.env.docs |
Agents are fully independent — no shared state, no inter-agent communication. Plane is the shared state.
MIT