# Plane on FameEdu — agent instruction

This is the public, non-secret runbook for agents that need to work with the Plane self-hosted instance on famedev.

## Instance

- App URL: https://plane.fameedu.ru/
- Domain: `plane.fameedu.ru`
- DNS A: `104.143.201.246`
- Deployment root: `/srv/projects/plane/plane-app`
- Compose file: `/srv/projects/plane/plane-app/docker-compose.yaml`
- Env file: `/srv/projects/plane/plane-app/plane.env` — private; do not print or copy.
- Private access file: `/srv/hermes/private/plane-fameedu/access.md` — only readable on the server by Hermes/root, never public.

## What is running

Plane CE `v1.4.2` via Docker Compose behind the shared Traefik/Coolify proxy.

Services/containers:

- `plane-app-web-1` — Plane frontend, internal port `3000`
- `plane-app-admin-1` — admin frontend, internal port `3000`
- `plane-app-space-1` — spaces frontend, internal port `3000`
- `plane-app-live-1` — live server, internal port `3000`
- `plane-app-api-1` — Django API, internal port `8000`
- `plane-app-worker-1` — worker
- `plane-app-beat-worker-1` — scheduled worker
- `plane-app-plane-db-1` — Postgres
- `plane-app-plane-redis-1` — Valkey/Redis
- `plane-app-plane-mq-1` — RabbitMQ
- `plane-app-plane-minio-1` — MinIO object storage
- `plane-app-proxy-1` — Plane Caddy proxy; Traefik routes public HTTPS to this container

## Verify health

Run on the famedev server:

```bash
cd /srv/projects/plane/plane-app
docker compose -f docker-compose.yaml --env-file plane.env ps
curl --noproxy '*' -skI --max-time 30 https://plane.fameedu.ru/
curl --noproxy '*' -skL --max-time 30 https://plane.fameedu.ru/ | grep -o '<title>[^<]*' | head -1
```

Expected:

```text
HTTP/2 200
<title>Plane | Simple, extensible, open-source project management tool.
```

DNS check:

```bash
for ns in ns1.beget.ru ns2.beget.ru 1.1.1.1 8.8.8.8; do
  printf '%s ' "$ns"
  dig +short A plane.fameedu.ru @"$ns" | tr '\n' ' '
  echo
done
```

Expected A record:

```text
104.143.201.246
```

## Logs

```bash
cd /srv/projects/plane/plane-app
docker compose -f docker-compose.yaml --env-file plane.env logs --tail=120 api worker beat-worker proxy
docker logs --tail=120 plane-app-api-1
docker logs --tail=120 plane-app-proxy-1
```

## Restart/update

Restart current stack:

```bash
cd /srv/projects/plane/plane-app
docker compose -f docker-compose.yaml --env-file plane.env up -d
```

Restart only proxy:

```bash
cd /srv/projects/plane/plane-app
docker compose -f docker-compose.yaml --env-file plane.env up -d --force-recreate proxy
```

Do not run the stock setup blindly after manual fixes without checking diffs: it can restore upstream defaults.

## Important local patches

Two patches are required on this server:

1. MinIO image

The official `setup.sh` generated `minio/minio:latest`, but pulling that image failed. The working image is:

```text
quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z
```

2. Caddy upstream names

The stock Plane Caddyfile uses generic names such as `web:3000`. Because the Plane proxy is also attached to the shared `coolify` network, `web` can resolve to another app's `web` alias. The custom Caddyfile uses unique container names:

```text
plane-app-web-1:3000
plane-app-api-1:8000
plane-app-admin-1:3000
plane-app-space-1:3000
plane-app-live-1:3000
plane-app-plane-minio-1:9000
```

The custom file is mounted here:

```text
/srv/projects/plane/plane-app/Caddyfile -> /etc/caddy/Caddyfile:ro
```

## Secrets and accounts

Do not put secrets in this public instruction. If the user asks for logins/passwords/API secrets, read:

```text
/srv/hermes/private/plane-fameedu/access.md
```

and provide only the specific requested values in chat. Never copy that file into `/srv/hermes/downloads`, Git repos, public tickets, or browser-accessible paths.

## Public instruction URL

This file is published at:

```text
https://downloads.hermess.famedev.ru/agent-instructions/plane-fameedu-agent-instructions.md
```

## API access for external agents

Use the Plane external REST API for direct automation:

```text
Base URL: https://plane.fameedu.ru/api/v1/
Auth header: X-Api-Key: ***
```

Private tokens exist and are stored only in `/srv/hermes/private/plane-fameedu/access.md`:

- `AdminPlane` — service/admin token for general API smoke such as `/api/v1/users/me/`.
- `AdminPlane-MCP-cai` — workspace-scoped token for official MCP automation in workspace `cai`.

Current workspaces/projects observed:

```text
workspace cai   -> Центр ИИ
workspace kuhta -> Kuhta
project cai/CONTENT  -> Content
project cai/AGROTECH -> Агротех
project cai/ЦЕНТР    -> Центр ИИ
project kuhta/KUHTA  -> Kuhta
```

Smoke test after receiving a token:

```bash
curl --noproxy '*' -sk -H 'X-Api-Key: ***' https://plane.fameedu.ru/api/v1/users/me/
```

Do not use `/api/users/me/` for API-key auth; API keys are for `/api/v1/...` routes. Some non-`/api/v1` app UI routes require browser/session auth and will return `401` with an API key.

## Official Plane MCP Server

The official Python MCP server from `makeplane/plane-mcp-server` is installed/cloned here:

```text
/srv/projects/plane-integrations/plane-mcp-server
```

Local test scripts:

```text
/srv/projects/plane-integrations/test_mcp_stdio.py
/srv/projects/plane-integrations/test_mcp_crud.py
/srv/projects/plane-integrations/test_mcp_http.py
```

Working stdio environment:

```text
PLANE_BASE_URL=https://plane.fameedu.ru
PLANE_WORKSPACE_SLUG=cai
PLANE_API_KEY=<private AdminPlane-MCP-cai token>
```

Verified MCP tools include `project`, `state`, `workitem`, `workitem_comment`, `member`, `workspace`, `module`, `cycle`, `release`, and more; current advertised tool count is 30.

Acceptance tested through official MCP stdio against workspace `cai`, project `ЦЕНТР`:

- list projects
- list states
- list work items
- create temporary work item
- read created work item
- update title/description
- move state to `In Progress`, then `Done`
- add/list comment
- delete temporary work item

Known limitation in this Plane/MCP combination: `workitem(action="archive")` returns `HTTP 404`, while `workitem(action="delete")` works and was used for cleanup.

## MCP HTTP service

A local-only MCP HTTP service is deployed here:

```text
/srv/projects/plane-integrations/plane-mcp-deploy/docker-compose.yml
container: plane-mcp-server
listen: 127.0.0.1:18211 -> container 8211
endpoint: http://127.0.0.1:18211/http/api-key/mcp
```

It is intentionally bound to localhost only. Do not expose it publicly without authentication and transport review.

The HTTP transport was verified with `test_mcp_http.py`: tools list returns 30 tools and `project(action="list")` works for workspace `cai` when the private token is sent in headers.

## Plane Apps / Agents compatibility

Official Plane Apps/Agents are a different mechanism from MCP. The official example is `makeplane/prd-agent`, which expects:

- Plane OAuth application
- Setup URL
- Webhook URL
- Redirect URI
- Client ID / Client Secret
- `Enable App Mentions`
- `agent_run_user_prompt` webhook event
- Agent Run API/scopes

In the currently installed self-hosted Plane CE `v1.4.2`:

- frontend assets contain UI strings for Applications, `Create app`, scopes, and `enable_app_mentions`;
- backend container code does **not** contain the required AgentRun/application OAuth backend models/routes (`AgentRun`, `agent_run_user_prompt`, app OAuth application routes were not found under `/code/plane`);
- route probes such as `/api/workspaces/cai/applications/` and `/api/workspaces/cai/oauth-applications/` returned `404` with API-key auth;
- normal app UI routes such as webhooks/projects return `401` with API-key auth because they require browser/session auth, so API-key probing alone is not proof of UI absence.

Conclusion for agents: MCP/REST is the working automation path now. `makeplane/prd-agent` is not deployable end-to-end on this exact backend without either finding/enabling the missing app backend routes through session/UI or upgrading Plane to a version/edition that exposes Apps/Agents backend support.

For a custom PRD-like agent later, replace hardcoded OpenAI usage in the example with configurable backend env:

```text
LLM_BASE_URL=<OpenAI-compatible /v1 endpoint>
LLM_API_KEY=<private key>
LLM_MODEL=<model name>
```

## Optional LLM access via Cursor OpenAI-compatible proxy

Famedev also has a local OpenAI-compatible proxy backed by the logged-in Cursor account. Use it only from this server / trusted backend code, not from browser frontend code.

```text
Base URL: http://127.0.0.1:8765/v1
Provider name in Hermes: cursor
Known working model for Hermes fallback: claude-opus-5
Default configured model: composer-2.5
Secret env var: CURSOR_PROXY_API_KEY
```

The key is private and must not be published in this runbook, Plane UI, frontend env, screenshots, or logs. If a Plane integration needs it, read it from the server secret environment/private config and call the proxy from backend-only code.

Verification from Hermes on this server:

```bash
HERMES_HOME=/srv/hermes hermes -m claude-opus-5 --provider cursor -z 'Ответь ровно одним словом: ok'
```

Expected result: `ok`.

Existing instruction source for future agents:

```text
/srv/hermes/skills/cursor/SKILL.md
Section: Hermes fallback via Cursor OpenAI proxy
```

## Built-in Plane AI Assistant LLM routing

The built-in Plane AI Assistant is configured server-side to use the local Cursor OpenAI-compatible proxy instead of direct public OpenAI.

```text
LLM_PROVIDER=openai
LLM_MODEL=claude-opus-5
LLM_BASE_URL=http://host.docker.internal:18765/v1
```

The API key is private (`CURSOR_PROXY_API_KEY` / `LLM_API_KEY`) and must never be placed in this public runbook or frontend code.

Important implementation notes for agents:
- Plane CE v1.4.2 did not support custom OpenAI base URL natively.
- Do not remove `/srv/projects/plane/plane-app/patches/external_base.py` or the bind mount in `docker-compose.yaml`.
- Do not remove service `llm-proxy-forwarder`; it bridges Plane containers to the host-local Cursor proxy.
- The assistant smoke test is a backend check, not a browser/front-end key exposure:

```bash
cd /srv/projects/plane/plane-app
docker compose -f docker-compose.yaml --env-file plane.env ps llm-proxy-forwarder api
docker exec plane-app-api-1 sh -lc 'python manage.py shell <<"PY"
from plane.app.views.external.base import get_llm_config, get_llm_response
api_key, model, provider = get_llm_config()
print(bool(api_key), model, provider)
text, err = get_llm_response("Ответь ровно одним словом: ok", "", api_key, model, provider)
print(err, text)
PY'
```

Expected: key present, `claude-opus-5`, `openai`, `err None`, `text ok`.
