From 1c57d2290d98663ee9a3cf2e3dbfc6fa5021d70c Mon Sep 17 00:00:00 2001 From: Dave Date: Fri, 25 Sep 2026 22:14:24 +0200 Subject: [PATCH] =?UTF-8?q?docs(spec):=20Phase=208=20=E2=80=94=20agent=20A?= =?UTF-8?q?PI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Design for a bearer-token-authenticated REST API letting an external AI coding agent read every project/card the owner has and write back exactly three things: a comment, subtasks, or a column move. Follows the same auth pattern as the existing CRON_SECRET-protected route. Agent-authored comments are prefixed so they're never mistaken for something the owner typed, mirroring Phase 7's "Logged" tag principle. MCP explicitly deferred to a later, thinner follow-up. Co-Authored-By: Claude Sonnet 5 --- .../2026-09-25-phase-8-agent-api-design.md | 175 ++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 docs/superpowers/specs/2026-09-25-phase-8-agent-api-design.md diff --git a/docs/superpowers/specs/2026-09-25-phase-8-agent-api-design.md b/docs/superpowers/specs/2026-09-25-phase-8-agent-api-design.md new file mode 100644 index 0000000..fc8e4b9 --- /dev/null +++ b/docs/superpowers/specs/2026-09-25-phase-8-agent-api-design.md @@ -0,0 +1,175 @@ +# Phase 8: Agent API — Design + +## Problem + +The owner wants to point an external AI coding agent (Claude Code, MiniMax, +or whichever tool is in front of them at the time) at their own backlog — +read what's in the Inbox and on the board, draft an implementation plan for +a card, and write that plan back onto the card — without that agent having +a browser session or a PocketBase login of its own. + +## Goals + +- A machine-to-machine API an external agent can call with a single static + credential, no session, no login flow. +- Read access across every client/project the owner has — the agent needs + to see what's already in progress, not just one Inbox in isolation. +- Write access limited to exactly three actions: comment on a card, add + subtasks to a card, move a card to a different column. +- An agent-authored comment must be honestly marked as agent-drafted, + never indistinguishable from something the owner typed — the same + principle Phase 7's "Logged" tag exists for, applied here. +- A plain REST API, not MCP, for now — the owner hasn't settled on which + agent tool will be the primary consumer, and a REST API is callable by + anything with a fetch/curl-capable tool. MCP can be layered on top later + as a thin wrapper without redesigning anything here. + +## Non-goals + +- No per-client or per-project scoping of what the agent can see. This + surface exists for the owner's own tooling; it is inherently + full-visibility, the same way the admin pages are. +- No OAuth, no multiple API credentials, no per-agent permissions. One + token, one trust level — full owner-equivalent access — matching this + app's existing single-owner architecture (the same posture as the + PocketBase superuser credentials it already holds). +- No MCP server in this phase. Explicitly deferred. +- No card creation, deletion, or field edits beyond the three write actions + listed above. An agent that wants to change a card's title, priority, or + description does so by leaving a comment for the owner to act on, not by + editing the card directly. + +## Authentication + +A single bearer token, `AGENT_API_TOKEN`, generated once (e.g. `openssl rand +-hex 32`) and stored in `.env.local`/Coolify — never in a client bundle, +never logged. Checked on every request: + +``` +Authorization: Bearer +``` + +This is the exact pattern already used by `/api/cron/reconcile` (`CRON_SECRET`) +— nothing new is being introduced to this codebase's security model, just a +second secret of the same kind for a second machine caller. A request with +no token, a wrong token, or the token unset in the environment gets a 401 +without touching PocketBase. + +This is a full owner-equivalent credential. Anyone holding it can read every +client's board and post/move/comment as the owner. It must be treated with +the same care as the PocketBase superuser credentials. + +## Routes + +All under `/api/agent/`, all reading and writing through the superuser +PocketBase connection (`getAdminPb()`), since this surface has no per-caller +identity to scope by — the token itself is the identity, and that identity +is "the owner." + +### `GET /api/agent/projects` + +Every project across every client, regardless of status filtering used +elsewhere in the app (the agent should be able to see paused/completed +projects too, not just active ones — it is planning, not browsing). + +```json +[ + { + "id": "...", "name": "WFDash Web App", "slug": "wfdash-webapp", + "client": { "id": "...", "name": "WF", "slug": "wf" }, + "kind": "project", "status": "active" + } +] +``` + +### `GET /api/agent/projects/:projectId/cards` + +Every card in the project, any column, sorted by column position then card +position — the same order the board itself renders in. An optional +`?column=inbox` query parameter filters to the Inbox column only (matching +`is_inbox`, not a literal column name, since column names are editable). + +```json +[ + { + "id": "...", "number": 42, "title": "...", "description": "...", + "priority": "medium", "target_date": "", "size_band": "medium", + "column": { "id": "...", "name": "Inbox", "is_inbox": true, "is_done": false } + } +] +``` + +### `GET /api/agent/cards/:cardId` + +Everything above for one card, plus: +- `subtasks`: existing checklist items (`title`, `done`) +- `comments`: existing comments (`author` name, `body`, `created`) +- `recentCommits`: the last 10 linked commits' summaries (or raw subject if + unsummarised) and dates — enough for the agent to know what work has + already touched this card without pulling full commit history + +### `POST /api/agent/cards/:cardId/comments` + +Body: `{ "body": string }`. + +The stored comment is prefixed before being written, so no caller can +accidentally or deliberately post something indistinguishable from the +owner's own words: + +``` +🤖 Drafted by your agent: + + +``` + +`author` is resolved server-side to the app's own owner user record (looked +up by `role = "owner"` — there is exactly one, by this app's design) — never +taken from the request. The comment renders through the exact same +`comments` list every card already has; no new UI work is needed. + +### `POST /api/agent/cards/:cardId/subtasks` + +Body: `{ "titles": string[] }`. Creates one subtask per title, appended +after the card's existing subtasks in position order (same fractional +spacing every other subtask-creation path in this app uses). Each title is +trimmed and capped at the schema's 200-character limit; an empty array or +an array of only-empty strings is rejected with 400 rather than silently +creating nothing. + +### `PATCH /api/agent/cards/:cardId/move` + +Body: `{ "column": string }` — a column **name** (e.g. `"Backlog"`), not an +id, since the agent has no reason to know internal PocketBase ids. Resolved +case-insensitively against the card's own project's columns; an unknown +name is rejected with 400 listing the project's actual column names, so a +caller can self-correct without a separate lookup call. + +The card is appended to the end of the target column (highest existing +position in that column, plus the standard spacing constant) — there is no +concept of "drop position" for a machine caller, so it always goes last, +exactly like a freshly created card does. + +## Error handling + +Every route returns the existing `jsonError(message, status)` / +`jsonOk(data)` shape already used by `/api/cron/reconcile` and the webhook +routes — no new response envelope invented for this surface. 401 for a +missing/wrong token, 404 for an unknown project/card id, 400 for a +malformed body or an unresolvable column name. + +## Testing + +Integration tests hit these routes directly with `fetch`, the same pattern +used throughout `tests/integration/` — a request with no token or the wrong +token must be refused before touching PocketBase; a request with the right +token must perform exactly the described read/write and nothing else; the +comment-prefixing and column-name resolution get their own specific +assertions since they are the two places a subtle bug would be easy to miss +(a missing prefix silently defeats the whole "honestly marked" requirement). + +## Out of scope for this spec + +- An MCP server wrapping these routes — a natural, small follow-up once a + primary agent tool is settled on, not part of this phase. +- Any UI in the app itself for managing or rotating `AGENT_API_TOKEN` — it + is set the same way `CRON_SECRET` already is, by hand, in the environment.