# warp.gyrum.ai — agent bootstrap

Fetch this doc as your first step. It is the smallest sufficient context
for a fresh Claude session (or any HTTP-capable agent) to file work
against Board without guessing schema, tag taxonomy, or lifecycle.

Canonical URL: <https://warp.gyrum.ai/CLAUDE.md> (alias: `/llms.txt`).

## 1. What is Board?

Board (repo: `gyrum-labs/warp`) is the gyrum-labs agent
work-coordination queue — a Postgres-backed HTTP service that holds the
cross-session, cross-machine inbox of work items. Agents discover, claim
atomically (`SELECT … FOR UPDATE SKIP LOCKED`), heartbeat, and complete
items here so two sessions on two machines never duplicate or trample
each other's work. ("Warp" was the original name; "Board" is the
display label as of rename r1. The repo, hostname, and CLI prefix
remain `warp` until r2.)

## 2. 30-second quickstart

File a ticket in one curl:

```bash
curl -X POST https://warp.gyrum.ai/api/v1/items \
  -H "Authorization: Bearer $WARP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "fix kanban Done column auto-collapse off-by-one",
    "description": "## Why\nDone column hides items <24h old when it should hide >24h old, defeating the freshness signal.\n\n## Scope\n- Flip the comparator in web/src/lib/utils/freshness.ts\n- Update unit tests in freshness.test.ts\n- Add e2e regression in web/tests/kanban.spec.ts\n\n## Acceptance\n- Items <24h old stay visible in Done\n- Items >24h old collapse behind the expander\n- Existing tests still pass",
    "priority": "high",
    "repo": "gyrum-labs/warp",
    "kind": "ticket",
    "tags": ["repo:gyrum-labs/warp", "kind:bug", "area:kanban"]
  }'
```

Response is `201 Created` with the full item JSON (including `id`).
A `422 Unprocessable Entity` with `{"errors":[{"field","reason"}]}`
means a structural rule fired — fix the listed fields and retry.

## 3. Required structural shape

Every well-shaped item carries these. The API rejects (strict mode) or
tags `meta:incomplete-structure` (warn mode) for anything that doesn't.

| Field | Rule |
|---|---|
| `title` | Concrete, outcome-shaped, **10–120 chars**. No `test`, `audit-smoke`, `x`. No UUIDs. |
| `description` | Markdown with `## Why`, `## Scope`, `## Acceptance` headers. (`## Requirements` instead of `## Acceptance` when `kind=epic`.) |
| `priority` | One of `urgent` \| `high` \| `med` \| `low`. |
| `repo` | Owner-qualified, e.g. `gyrum-labs/warp`. |
| `kind` | `ticket` (default) or `epic`. |
| `tags[]` | Must include at least one `repo:*` AND one `kind:*` tag. |

Optional: `parent_id` (links a child to an epic), `context_links[]`
(design docs, prior PRs, ADRs), `size`, `target_date`, `reserved_for`.

Header matching is case-insensitive and forgiving on whitespace —
`## Why`, `##  why  `, and `## WHY` all pass.

## 4. Tag taxonomy

Tags drive every kanban filter, CLI search, and cross-repo query.
Inventing ad-hoc tags fragments the board.

| Prefix | Required | Examples |
|---|---|---|
| `repo:<owner>/<name>` | yes | `repo:gyrum-labs/warp`, `repo:gyrum-labs/distill` |
| `kind:<class>` | yes | `kind:bug`, `kind:feature`, `kind:docs`, `kind:infra`, `kind:adr`, `kind:test`, `kind:tooling`, `kind:ticket`, `kind:epic` |
| `area:<sub-system>` | optional | `area:kanban`, `area:audit-timeline`, `area:auth` |
| `epic:<slug>` | optional | `epic:warp-self-describing` (links a child to its epic) |
| `phase:<N>` | optional | `phase:1` (for staged epic delivery) |
| `blocker:<who-or-what>` | optional | `blocker:design-review`, `blocker:upstream-bug` |
| `meta:incomplete-structure` | system-only | Auto-applied by the validator in warn mode. |

Note: the `kind:` tag is for filtering; the top-level `kind` field is
the API enum. They are independent — set both.

## 5. Priority guide

| Value | When |
|---|---|
| `urgent` | Live incident, prod outage, security fire. Pages humans. |
| `high` | Blocking other work, operator-asked, or a swarm directive. |
| `med` | Default. Normal feature / fix / chore. |
| `low` | Nice-to-have. Picked up when the queue is empty. |

## 6. Lifecycle

```
create  →  ready  →  claim  →  in_progress  →  complete  →  done
                       │            │
                       │            ├─ release  → ready (cooperative handoff)
                       │            └─ block    → blocked → unblock → ready
                       │
                       └─ heartbeat every 5min (auto via warp-claim CLI; lease expires at 15min)
```

Endpoints (all `POST` unless noted, all require `Authorization: Bearer`):

| Op | Endpoint |
|---|---|
| Create | `POST /api/v1/items` |
| List ready | `GET /api/v1/items?status=ready` |
| Get | `GET /api/v1/items/{id}` |
| Claim | `POST /api/v1/items/{id}/claim` |
| Heartbeat | `POST /api/v1/items/{id}/heartbeat` |
| Complete | `POST /api/v1/items/{id}/complete` (body: `{"pr_url":"..."}`) |
| Release | `POST /api/v1/items/{id}/release` |
| Block | `POST /api/v1/items/{id}/block` (body: `{"reason":"..."}`) |
| Unblock | `POST /api/v1/items/{id}/unblock` |
| Cancel | `POST /api/v1/items/{id}/cancel` |

Heartbeat responses include `cancel_requested: bool` — when `true`,
finish the current atomic unit, push WIP, and `release` (do **not**
`complete` partial work).

## 7. Three example payloads

All three pass the API structural validator. Copy, mutate, POST.

### A. Bug ticket

```json
{
  "title": "fix /api/v1/items 500 when tags array contains empty string",
  "description": "## Why\nA UI form submission with a stray comma sends `[\"foo\",\"\"]` and the handler panics on the empty tag instead of returning 400.\n\n## Scope\n- Filter empty strings out of CreateInput.Tags in api/internal/items/store.go\n- Add a validator unit test for the empty-string case\n- Add a contract test that POSTs the payload and asserts 201\n\n## Acceptance\n- POST with `[\"foo\",\"\"]` returns 201 with tags=[\"foo\"]\n- POST with `[\"\"]` returns 422 with reason 'no kind:* tag'\n- No 500s logged",
  "priority": "high",
  "repo": "gyrum-labs/warp",
  "kind": "ticket",
  "tags": ["repo:gyrum-labs/warp", "kind:bug", "area:api"]
}
```

### B. Feature epic

```json
{
  "title": "epic: warp self-describing — agents fetch their own onboarding",
  "description": "## Why\nFresh Claude sessions and third-party agents waste cycles guessing the warp schema. A canonical bootstrap doc + structural rejection + auto-decompose closes the loop.\n\n## Scope\n- Phase 1: CLAUDE.md doc at warp.gyrum.ai/CLAUDE.md\n- Phase 2: API-level structural rejection in strict mode\n- Phase 3: epic auto-decompose endpoint\n\n## Requirements\n- Doc renders <300 lines, served as text/markdown\n- Strict mode 422s on missing Why/Scope/Acceptance\n- POST /items/{id}/decompose creates child tickets from ## Requirements bullets",
  "priority": "high",
  "repo": "gyrum-labs/warp",
  "kind": "epic",
  "tags": ["repo:gyrum-labs/warp", "kind:epic", "area:agent-onboarding"]
}
```

### C. Refactor ticket linked to an epic

```json
{
  "title": "refactor freshness.ts to share threshold constants with kanban.ts",
  "description": "## Why\nThe 24h Done-collapse threshold is duplicated in freshness.ts and kanban.ts. A future tweak risks drift.\n\n## Scope\n- Extract DONE_COLLAPSE_AGE_MS into web/src/lib/utils/constants.ts\n- Import from both call sites\n- Update both unit-test suites to import the constant rather than re-declare\n\n## Acceptance\n- Single source of truth for the threshold\n- All freshness + kanban tests still green\n- No behavioural change",
  "priority": "med",
  "repo": "gyrum-labs/warp",
  "kind": "ticket",
  "parent_id": "b4dc7745-fae6-4249-8275-f5e7863384d6",
  "tags": ["repo:gyrum-labs/warp", "kind:tooling", "area:kanban", "epic:done-column-polish", "phase:2"]
}
```

## 8. Common pitfalls

- **Don't file stub tickets** like `test`, `audit-smoke`, `x`. The
  validator rejects them in strict mode and tags them
  `meta:incomplete-structure` in warn mode.
- **Don't put UUIDs in the title.** Title is human-facing; the `id`
  field is the API handle. A title like `fix bug a3f-9c2-…` is noise.
- **Don't inline-patch live bugs.** Even an "obvious" two-line fix
  goes through `warp-add → warp-claim → gyrum-start-work → review →
  merge → warp-complete`. The flow is the gate.
- **Don't draft from chat memory.** Verify the relevant ADR / source
  file / prior PR before authoring the brief; quote the
  non-negotiable rules verbatim.
- **Don't reuse epic structure for tickets.** Epic = multi-PR with
  `## Requirements`. Ticket = single PR with `## Acceptance`.
- **Don't `complete` half-done work.** Use `release` (cooperative
  handoff) or `block` (external blocker). `complete` is reserved for
  genuine done.

## 9. Authentication

- Every request needs `Authorization: Bearer wrp_<32-hex>`.
- Operator hands a token over directly, OR an admin mints one via
  `POST /api/v1/keys` with `{"scope":"agent","label":"<your-label>"}`.
- On the operator's laptop the token lives in
  `~/.config/gyrum/control-plane.env` as `WARP_TOKEN`. Never log it,
  never paste it into a PR body. Rotate immediately if leaked.

## 10. Deeper docs

- Agent protocol: <https://github.com/gyrum-labs/warp/blob/main/docs/AGENT_GUIDE.md>
- OpenAPI spec: <https://warp.gyrum.ai/api/v1/openapi.yaml>
- ADR-098 (epics + decompose): <https://github.com/gyrum-labs/dark-factory/blob/main/docs/adrs/ADR-098-epics-and-decompose.md>
- Per-epic design docs live at `<repo>/docs/design/<slug>.md`.
- Memory rule (the structural-detail mandate this doc encodes):
  `feedback_warp_items_need_structural_detail.md`.
