# API: Agents

Give coding agents memory and coordinate them.

## Agents & memory

See [Agent Controls](/docs/agent-controls) and [Agent Hooks](/docs/agent-hooks). Agent telemetry and AI vendor usage have their own [sections below](#agent-telemetry).

### POST /v1/memories

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `codebase_memory`.

Saves something the next person or agent working in a repository should know.

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `repository` | string | Yes | `owner/name` |
| `path` | string | No | The file or directory it applies to. Empty means the whole repository. |
| `kind` | string | No | `note` (default), `decision`, `pitfall` or `incident` |
| `body` | string | Yes | Up to 2000 characters |
| `author` | string | No | Who wrote it |

Returns `201` with the memory: `id`, `repository`, `path`, `kind`, `body`, `author`, `created_at`. Saving the same text again for the same repository, path and kind refreshes the existing memory instead of adding a duplicate.

With memory approval on (Beta, flag `memory_approval`), the memory also has `status`: `approved`, or `pending` until a person approves it. A memory is pending when the workspace setting `memory_approval` is on, or the repository's `bumpkit.yml` sets `memory.require_approval`. With the ephemeral data retention mode, saving a memory fails with `409 NOT_STORED`.

### GET /v1/memories

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `codebase_memory`.

Recalls memories for a path, with its release history. Query `repository` (required) and `path`.

Returns `repository`, `path`, `memories` (the most relevant first, capped in number and total length), `releases` (recent checks that touched the path), `flagged` (`[{kind, count}]` finding kinds seen there) and `agents`. Only approved memories are recalled.

### DELETE /v1/memories/{id}

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `codebase_memory`.

Forgets a memory. Returns `204`, or `404 MEMORY_NOT_FOUND`.

### GET /v1/memories/pending

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flags: `codebase_memory`, `memory_approval`.

Lists memories waiting for approval, newest first. Needs a key tied to a person. Query `repository` and `limit` (1 to 500, default 100). Returns `{"memories": [Memory]}`.

### POST /v1/memories/{id}/approve

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flags: `codebase_memory`, `memory_approval`.

Approves a pending memory. Needs a key tied to a person. A key can't approve a memory saved with a key of the same person, or with a key that was since deleted (`403 FORBIDDEN`). Review those on the Approvals page in the dashboard, or with a teammate's key. Returns `200` with the memory, or `404 NOT_FOUND` if it isn't pending.

### POST /v1/memories/{id}/reject

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flags: `codebase_memory`, `memory_approval`.

Rejects a pending memory. Same rules as approve.

### POST /v1/agent/decisions

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `agent_pause`.

Records a decision an agent hook made about a command, or asks a person to decide. Limited to 240 requests per minute per key.

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `tool` | string | Yes | The agent tool, such as `Bash` |
| `command` | string | Yes | The command. Secrets are redacted again on the server. |
| `decision` | string | Yes | `allow`, `ask` or `deny` |
| `repository` | string | No | `owner/name` |
| `agent` | string | No | Agent name |
| `session` | string | No | Agent session |
| `command_hash` | string | No | SHA-256 hex of the redacted command. Computed when missing or invalid. |
| `rules` | string[] | No | Rule IDs that matched, such as `rm_rf`, `force_push`, `sql_drop` or `secret_write`. Unknown IDs are dropped. |
| `reason` | string | No | Up to 1000 characters |
| `remote` | boolean | No | With `ask`, wait for a person to approve on the dashboard's Approvals page |
| `timeout_seconds` | integer | No | How long a remote ask waits, 5 to 120, default 30 |

Returns `201` with `id`, `repository`, `agent`, `session`, `tool`, `command`, `command_hash`, `rules`, `reason`, `decision`, `status`, `approver_email`, `expires_at`, `decided_at` and `created_at`. `status` is `pending` for a remote ask, otherwise `decided`.

### GET /v1/agent/decisions

Reads stored command decisions for the authenticated person's current workspace. Requires a person-bound API key and access to `agent_pause`; viewer and read-only keys can read. This endpoint does not evaluate commands or change decisions.

| Query | Description |
|-------|-------------|
| `status` | `recent` (default) or `pending` |
| `limit` | 1–500; defaults to 20 for recent or 50 for pending |

`recent` includes decided commands and expired requests. `pending` includes only unexpired pending requests. Both are ordered by creation time, newest first, matching the dashboard. Expired pending records are returned with `status: "expired"`. Returns `{"status":"recent","now":"...","decisions":[AgentDecision]}`. Invalid parameters return `400 VALIDATION_ERROR`.

### GET /v1/agent/decisions/{id}

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `agent_pause`.

Returns a decision. Poll it while `status` is `pending`; it becomes `approved`, `denied` or `expired`. `404 AGENT_DECISION_NOT_FOUND` for an unknown ID.

### POST /v1/agent/work

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `agent_guard`.

Reports the branch and commits an agent session worked on, so the release gate treats them as agent-written. The Bumpkit agent hooks send this for you. Limited to 600 requests per minute per key.

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `agent` | string | Yes | `claude-code`, `cursor`, `codex`, `gemini`, `windsurf`, `cline` or `copilot` |
| `session_id` | string | Yes | 1 to 200 letters, digits, `.`, `_`, `:` or `-` |
| `repository` | string | Yes | `owner/name` or the remote URL |
| `branch` | string | For no commits | The branch the session worked on. Required, and not the default branch, when `commits` is empty. |
| `head_sha` | string | No | The full commit SHA of the branch head |
| `commits` | string[] | No | Up to 200 full commit SHAs the session's branch adds |

Returns `202 {"session": "<id>", "commits": n}`. `403 FORBIDDEN` for a viewer or a key that can't report agent work. `409 PROVENANCE_SESSION_CONFLICT` when another account or key started the session.

### POST /v1/provenance/events

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `change_provenance`.

Records an agent session event from an agent hook. Limited to 600 requests per minute per key.

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `agent` | string | Yes | `claude-code`, `cursor`, `codex`, `gemini`, `windsurf`, `cline` or `copilot` |
| `session_id` | string | Yes | 1 to 200 letters, digits, `.`, `_`, `:` or `-` |
| `event` | string | Yes | `start`, `tool`, `prompt` or `end` |
| `tool` | string | For `tool` | Tool name, up to 64 characters |
| `prompt` | string | No | On `prompt` events. Secrets are redacted and it is cut to 280 characters. |
| `agent_version`, `model` | string | No | The agent's version and model |
| `repository`, `branch`, `head_sha` | string | No | Where the agent is working |
| `sponsor` | string | No | Ignored. The sponsor is the API key's person, or `key:<id>` for a key tied to no person. |

Returns `202 {"session": "<id>", "events": n}`. `403 FORBIDDEN` for a viewer or a key that can't record events. `409 PROVENANCE_SESSION_CONFLICT` when another account or key started the session.

### GET /v1/checks/{id}/provenance

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `change_provenance`.

Who and what produced a checked change. Returns `workspace_id`, `check_id`, `repository`, `pull_request`, `base`, `head`, `head_sha`, `checked_at`, `decided_at`, `agents`, `sponsors`, `sessions`, `trailers`, `approvals`, `approvals_source` and `verification`. A session's `sponsor_verified` is true only when it was reported with a person's key. `404 NOT_FOUND` for an unknown check.

With [agent forensics](/docs/agent-controls#agent-forensics) on (flag `agent_forensics`), the response also has `forensics`: `events`, each with `at`, `kind` (`session`, `guard`, `check`, `gate` or `outcome`), `text` and `source` (`reported` or `recorded`), and `signals`, each with `kind` and `observed`. Signals never block a release.

### GET /v1/checks/{id}/attestation

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `change_provenance`.

The provenance as an in-toto statement (predicate type `https://bumpkit.dev/attestation/change-provenance/v1`). Unsigned: `{"check_id", "signed": false, "statement"}`. Signed: `{"check_id", "signed": true, "envelope"}`, a DSSE envelope with an Ed25519 signature. `500 SIGNING_KEY_INVALID` if attestation signing isn't working; contact Bumpkit support.

### GET /v1/provenance/key

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `change_provenance`.

The public key that signs attestations: `{"signed": true, "keyid", "public_key", "algorithm": "ed25519"}`, or `{"signed": false}`.

### GET /v1/agent/guard-events

> **Beta, early access.** Flag: `agent_pause`.

What the agent guard did to the files of a change. Works with any key and is limited per key. Query `repository` and one `file` for each path. Without a file, `events` is empty.

Returns `{"repository", "events": [{file, decision_id, agent, session, tool, command, decision, status, rules, direct, at, text}]}`, newest first. Each event is a decision other than allow. `direct` is `true` when the decision touched `file` itself, and `false` when it came from a session that worked on `file`. `bumpkit blast-radius` and the `blast_radius` MCP tool read it from a checkout.

### GET /v1/repositories/{owner}/{repo}/agent-hooks

> **Beta, early access.** Flag: `agent_hooks_pr`.

Whether the repository has the [team agent hooks](/docs/agent-hooks#add-the-hooks-with-a-pull-request). Owners, admins and policy admins only. Returns `repository`, `status` (`missing`, `pull_request_open` or `installed`), `branch`, `files` and `pull_request` (`number`, `url`). [`POST /v1/repositories/{owner}/{repo}/agent-hooks`](/docs/agent-hooks#add-the-hooks-with-a-pull-request) opens the pull request and returns the same fields with `201` and `created: true` when it opened one. Use `bumpkit hooks pr --repo OWNER/NAME` to open it and the `agent_hooks_status` MCP tool to read the state.

## Agent policy

> **Beta, early access.** Flag: `org_policies`.

The locked [company-wide agent policy](/docs/agent-hooks#company-wide-agent-policy). These routes need a key tied to a person. Any role can read; only owners and admins can change it. The CLI is `bumpkit agent policy show`, `set`, `history` and `diff`; the MCP tools are `agent_policy_show`, `agent_policy_set`, `agent_policy_history` and `agent_policy_diff`.

| Method | Route | Result |
| --- | --- | --- |
| GET | `/v1/policy/agent` | The latest version: `version`, `content`, `message`, `actor_email` and `created_at`. `404 NOT_FOUND` when the workspace has no agent policy yet |
| PUT | `/v1/policy/agent` | Send `content` (YAML, under 64 KB) and optionally `message` (up to 500 characters). Returns `{"policy", "diff"}`, `400 VALIDATION_ERROR` when the policy doesn't parse, or `409 CONFLICT` when nothing changed |
| GET | `/v1/policy/agent/versions` | `{"versions": [...]}`, newest first. Query `limit`: 1 to 200, default 50 |
| GET | `/v1/policy/agent/versions/{version}` | One version, or `404 NOT_FOUND` |

### GET /v1/agent/policy

> **Beta, early access.** Flag: `agent_pause`.

The signed copy of the policy that agent hooks enforce. Works with any key and is limited per key. Returns `{"payload", "signature", "key_id"}`. `payload` is base64 JSON with `workspace`, `version`, `content`, `issued_at` and `expires_at`, 24 hours after it was issued. With no policy, or without `org_policies`, `version` is `0` and `content` is empty. `503 SIGNING_UNAVAILABLE` when Bumpkit can't sign right now. `bumpkit pause` fetches and caches it for you.

## Agent telemetry

> **Beta, early access.** Flag: `agent_telemetry`.

See [Agent Telemetry](/docs/agent-telemetry). Every route needs a key created by an owner or admin; other keys get `403 FORBIDDEN`. Your exporter doesn't call these: it sends events to the endpoint in the setup guide with an ingest key.

The CLI is `bumpkit agents telemetry`, with `status`, `enable`, `disable`, `mode`, `scope`, `retention`, `identity`, `keys`, `events`, `purge` and `test`. The MCP tools have the same names, such as `agent_telemetry_status` and `agent_telemetry_keys_create`.

| Method | Route | Result |
| --- | --- | --- |
| GET | `/v1/agent-telemetry` | `settings`, `keys`, `stats` (`events`, `commits`, `matched`, `unmatched`, `match_rate`, `rejected`, `last_event_at`, `last_error`, `last_error_at`), the 20 latest `events`, `endpoint`, `header`, `paths`, `kept` and `never_stored` |
| PUT | `/v1/agent-telemetry/settings` | Send the fields below. Returns the saved settings |
| GET | `/v1/agent-telemetry/keys` | `{"keys": [{id, name, prefix, allowed_cidrs, created_at, expires_at, last_used_at, last_used_ip, events, rejected, expired}]}` |
| POST | `/v1/agent-telemetry/keys` | Send `name` (up to 64 characters), optionally `expires_in_days` (1 to 730) and `allowed_cidrs`. Returns `201 {"key", "secret", "managed_settings", "env"}`. The secret is shown once |
| POST | `/v1/agent-telemetry/keys/{id}/rotate` | Replaces the secret and returns the same fields. The old secret stops working |
| DELETE | `/v1/agent-telemetry/keys/{id}` | Revokes the key. Returns `204`, or `404 NOT_FOUND` for an unknown or revoked key |
| GET | `/v1/agent-telemetry/events` | `{"events": [{id, received_at, name, key, session_id, user, repository, branch, commit_sha, agent, matched, matched_by}]}`, newest first. Query `limit`: 1 to 200, default 50 |
| POST | `/v1/agent-telemetry/purge` | Deletes every stored event. Returns `{"purged": n}` |
| POST | `/v1/agent-telemetry/test` | Send `secret` to check an ingest key, or `{}` to check for events. Returns `ok`, `message`, and `key` and `last_seen_at` when known |

Settings fields:

| Field | Type | Description |
|-------|------|-------------|
| `mode` | string | `off`, `observe` or `enforce`. Required |
| `scope` | string | `all` (default), `allow` or `deny` |
| `repositories` | array | `owner/name` values, up to 500. Required for `allow` and `deny` |
| `retention_days` | integer | `1`, `7` (default) or `30` |
| `identity` | string | `keep`, `hash` (default) or `drop` |
| `alerts` | object | `unused_key_days` (0 to 365), `ip_allowlist` (boolean) and `rejection_spike` (0 to 100000). `0` turns an alert off |

## AI vendor usage

> **Beta, early access.** Flag: `vendor_usage`.

See [AI Vendor Usage](/docs/vendor-usage). Every route needs a key created by an owner or admin; other keys get `403 FORBIDDEN`. `{vendor}` is `anthropic`, `claude-enterprise`, `cursor` or `copilot`.

The CLI is `bumpkit agents vendors`, with `status`, `connect`, `disconnect`, `sync`, `retention` and `identities`. The MCP tools have the same names, such as `agent_vendors_status` and `agent_vendors_identities_map`.

| Method | Route | Result |
| --- | --- | --- |
| GET | `/v1/vendor-usage` | `settings` (`retention_days`), `vendors`, `people` and `window_days` |
| PUT | `/v1/vendor-usage/settings` | Send `retention_days`: `30`, `90`, `180` or `365`. Returns the saved settings |
| PUT | `/v1/vendor-usage/vendors/{vendor}` | Send `key`, and `org` for `copilot`. Bumpkit tries the key against the vendor first and answers `400 VALIDATION_ERROR` if the vendor refuses it. Returns the connection with `connected`, `org`, `key_hint`, `connected_at`, `last_synced_at`, `synced_from`, `synced_through`, `rows`, `last_error` and `last_error_at` |
| DELETE | `/v1/vendor-usage/vendors/{vendor}` | Removes the key, the stored usage and the identity mappings. Returns `204`, or `404 NOT_FOUND` when it isn't connected |
| POST | `/v1/vendor-usage/vendors/{vendor}/sync` | Reads the latest usage now. Returns `vendor`, `since`, `until`, `rows` and `message`, or `409 CONFLICT` when a sync started less than a minute ago |
| GET | `/v1/vendor-usage/identities` | `{"identities": [{vendor, user, committer, source}]}` |
| PUT | `/v1/vendor-usage/identities` | Send `vendor`, `user` and `committer` (a GitHub login or commit email). Returns the mapping with `source: "admin"` |
| DELETE | `/v1/vendor-usage/identities` | Query `vendor` and `user`. Returns `204`, or `404 NOT_FOUND` when there's no such mapping |

## Swarms

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it.

Claims keep coding agents on the same repositories from colliding. The CLI and MCP server call the other swarm endpoints for you; see [Agent Swarms](/docs/agent-swarms). Claims work with any key in the workspace. Repositories are `owner/name` or a github.com URL.

### POST /v1/claims

Flag `swarm_claims`. Claims a resource so other agents don't collide with it.

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `repository` | string | Yes | `owner/name` |
| `kind` | string | Yes | `migration`, `path`, `symbol`, `route`, `flag`, `env` or `other` |
| `resource` | string | Yes | Up to 300 characters. A migration is its number (`48`); a route is `METHOD path`. |
| `agent` | string | Yes | Who holds the lease, up to 100 characters |
| `session` | string | Yes | Which run of it, up to 100 characters |
| `note` | string | No | Up to 500 characters |
| `ttl_seconds` | integer | No | Lease length, clamped to 60 seconds to 24 hours. Default 1800. |

Returns `201` with the claim: `id`, `repository`, `kind`, `resource`, `agent`, `session`, `sponsor`, `note`, `created_at`, `renewed_at`, `expires_at`, `released_at`. Claiming again with the same agent and session renews it.

When another agent holds the resource, the response is `409` with a different shape:

```json
{
  "status": "error",
  "error": {
    "code": "CLAIM_HELD",
    "message": "migration 48 is held by codex (session s-2) until 2026-10-22T15:04:05Z.",
    "suggestion": "Claim 49 instead, or wait for the lease to expire."
  },
  "holder": { "id": "...", "kind": "migration", "resource": "48", "agent": "codex" },
  "alternative": "49"
}
```

### GET /v1/claims

Flag `swarm_claims`. Active claims. Query `repository`, `kind`, `agent` and `limit` (default 100, maximum 500). Returns `{"claims": [Claim]}`.

### POST /v1/claims/migration

Flag `swarm_claims`. Claims the next free migration number.

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `repository`, `agent`, `session` | string | Yes | As for claims |
| `dir` | string | No | Migrations directory, default `db/migrations` |
| `known` | integer[] | No | Numbers you already see locally |
| `note` | string | No | Claim note |
| `ttl_seconds` | integer | No | Default 24 hours |

Bumpkit claims the next number nobody is using, here or in open pull requests. Returns `201 {"number": 49, "name": "049", "claim": Claim, "taken": {...}}`.

### POST /v1/claims/{id}/renew

Flag `swarm_claims`. Send `agent`, `session` (must match the holder) and optionally `ttl_seconds`. Returns `200` with the claim. `404 CLAIM_NOT_FOUND`, `410 CLAIM_RELEASED`, or `403 FORBIDDEN` when you aren't the holder.

### POST /v1/claims/{id}/release

Flag `swarm_claims`. Same body as renew. A key created by a workspace owner or admin can release anyone's claim; other keys get `403 FORBIDDEN`, audited as `claim.release.denied`. Bumpkit records who forced the release. Returns `200` with the claim, `released_at` set. `410 CLAIM_RELEASED` if already released.

### GET /v1/swarm

Flags: `swarm_claims`, `conflict_prediction` or `merge_planning`. Stored coordination state, read without recomputing. Needs a key tied to a person. Returns `workspace_id`, `claims`, `intents`, `plans`, `claims_limit`, `intents_limit` and `plans_limit`. Use `bumpkit swarm status` or the `swarm_status` MCP tool.

### GET /v1/merge-plans

Flag: `merge_planning`. Stored merge plans, ordered by repository. Needs a key tied to a person. Returns `workspace_id`, `plans`, `limit` and `order`. Use `bumpkit merge-plans` or the `merge_plans` MCP tool.

### Test database leases

Flag: `test_isolation`. Tracks the [test databases](/docs/agent-swarms#per-agent-test-databases) your agents lease. Works with any key and is limited per key. `bumpkit testdb` creates and drops the databases and calls these for you; the MCP tools are `test_database_lease`, `test_database_release`, `testdb_list` and `testdb_sweep`.

| Method | Route | Result |
| --- | --- | --- |
| POST | `/v1/testdb/leases` | Send `name` (starts with `bk_lease_`), `host` (`host[:port]`), and optionally `agent` and `ttl_seconds` (60 to 86400, default 3600). Returns `201` with `id`, `name`, `host`, `agent`, `expires_at` and `created_at` |
| GET | `/v1/testdb/leases` | `{"leases": [...]}` |
| DELETE | `/v1/testdb/leases` | Query `host` and `name`. Returns `204` |

## Swarm governance

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it.

Rules agents learn from, and history they check before they build. See [Agent Swarms](/docs/agent-swarms). Each endpoint names its flag. Proposing and searching work with any key in the workspace; reviewing lessons needs a key tied to a person.

### POST /v1/lessons

Flag `swarm_lessons`. Proposes a lesson: a repository-wide rule that any agent can propose and only a person can approve.

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `repository` | string | Yes | `owner/name` |
| `body` | string | Yes | The rule, up to 1000 characters |
| `kind` | string | No | `practice` (default), `pitfall`, `decision`, or `dropped` for something the team chose not to build |
| `paths` | string[] | No | Up to 20 paths it applies to. Empty means the whole repository. |
| `areas` | string[] | No | Any of `tests`, `migrations`, `auth`, `api`, `ui`, `authentication`, `authorization`, `billing`, `cryptography` |
| `proposed_by` | string | No | Who proposed it, such as the agent id |
| `supersedes` | string | No | The id of a lesson this replaces. The new one becomes the next version in the same lineage, and approving it supersedes the old one. |

Returns `201` with the lesson: `id`, `repository`, `lineage_id`, `version`, `kind`, `body`, `paths`, `areas`, `status` (`pending`), `proposed_by`, `supersedes`, `reviewed_by`, `reviewed_at`, `retired_by`, `retired_at`, `retire_reason` and `created_at`. Proposing the same text again for the same repository returns the existing lesson. `404 LESSON_NOT_FOUND` when `supersedes` names an unknown lesson.

### GET /v1/lessons

Flag `swarm_lessons`. Query `repository`, `path`, `area` and `status` (`approved` by default, or `pending`, `retired`, `superseded`, `rejected` or `all`). Returns `{"lessons": [Lesson]}`, up to 200.

`GET /v1/memories` also returns the approved `lessons` for the path when the flag is on. Pending, rejected and retired lessons never reach agents.

### POST /v1/lessons/{id}/approve

Flag `swarm_lessons`. Approves a pending lesson. Needs a key tied to a person. A key can't approve a lesson proposed with a key of the same person, or with a key that was since deleted (`403 FORBIDDEN`). Review those in the dashboard inbox, or with a teammate's key. Returns `200` with the lesson. `404 NOT_FOUND` if no pending lesson has that id. Audited as `lesson.approve`.

### POST /v1/lessons/{id}/reject

Flag `swarm_lessons`. Rejects a pending lesson. Same rules as approve; audited as `lesson.reject`.

### POST /v1/lessons/{id}/retire

Flag `swarm_lessons`. Retires an approved lesson. Needs a key tied to a person. Send `{"reason": "..."}`, 10 to 500 characters. Returns `200` with the lesson, or `404 NOT_FOUND`. Audited as `lesson.retire`.

### POST /v1/tried

Flag `tried_before`. Searches release history before you build something: reverted commits, files that were added and later deleted, removed APIs and breaking changes, `decision` and `dropped` lessons, and past gate blocks.

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `query` | string | One of | A few words describing the change |
| `paths` | string[] | One of | Paths it touches, up to 10 |
| `symbols` | string[] | One of | Symbols it touches |
| `repository` | string | No | `owner/name`. Omit to search every repository. |

Returns `200 {"query", "tried", "summary", "evidence"}`. Each evidence item has `kind` (`revert`, `removed_file`, `removed_feature`, `removed_api`, `decision`, `dropped_decision`, `gate_block` or `release`), `title`, `detail`, `repository`, `check_id`, `commit`, `link` (the release page), `commit_link` (GitHub repositories), `score` and `at`. `400 VALIDATION_ERROR` when there is nothing to search for. With the `findings_only` and `ephemeral` data retention modes, commit subjects aren't stored, so fewer matches are found.
