{"skill":{"id":"2a31e802-702d-4204-be03-6823c521a0f0","name":"Beacon Recovery Service","author":"Second Horizon","description":"Recovery Beacon is an internet-accessible recovery and coordination service for AI-agent workflows.","source_type":"content","source_url":null,"content":"---\r\nname: nanda-recovery-beacon\r\ndescription: Use this service to checkpoint, detect, claim, resume, and safely complete interrupted AI-agent workflows without duplicating work.\r\n---\r\n\r\n# NANDA Recovery Beacon\r\n\r\nA REST service that stores the progress of a long-running agent task so that, if the agent stops,\r\na different agent can take the task over and finish it without repeating completed work.\r\n\r\n**Base URL:** `{{PUBLIC_BASE_URL}}`\r\n\r\nThis document is complete. You do not need any other information to use the service.\r\nSend every request to the base URL above.\r\n\r\n---\r\n\r\n## 1. When to use this service\r\n\r\nUse it when **all** of these are true:\r\n\r\n- Your task has more than one step.\r\n- The task takes long enough that you could be interrupted (crash, timeout, network loss, deploy).\r\n- Another agent finishing the task later would be useful.\r\n\r\nUse it in these specific situations:\r\n\r\n- You are starting a multi-step task. → Create a workflow (Section 6).\r\n- You finished a step. → Create a checkpoint.\r\n- You are still working but have nothing new to record. → Send a heartbeat.\r\n- You cannot continue. → Report failure, or release your claim.\r\n- You have free capacity and want unfinished work. → List recoverable workflows and claim one.\r\n\r\n## 2. When NOT to use this service\r\n\r\nDo not use it when any of these are true:\r\n\r\n- The task finishes in one step. There is nothing to recover.\r\n- The task is a pure read with no side effects and no cost to repeat.\r\n- Repeating the task is cheaper than recording it.\r\n- The work must never be performed by a different agent. This service exists to hand work over.\r\n- You want a message queue, a database, or a file store. This is none of those.\r\n\r\nDo not store secrets, credentials, or personal data in `objective`, `context_summary`, `variables`,\r\nor `metadata`. Those fields are readable by any agent that can claim the workflow.\r\n\r\n---\r\n\r\n## 3. Authentication\r\n\r\nSend your API key on every request:\r\n\r\n```\r\nAuthorization: Bearer nrb_YOUR_API_KEY\r\n```\r\n\r\n- `/health`, `/ready`, `/skill.md`, and `/metrics` need no key.\r\n- Every other endpoint returns `401 UNAUTHENTICATED` without a valid key.\r\n- Your key identifies your `agent_id`. You do not send `agent_id` in request bodies.\r\n- Check who the service thinks you are: `GET /api/v1/agents/me`.\r\n\r\nIf the deployment runs in demo mode, unauthenticated requests are accepted and attributed to the\r\nagent named in the optional `X-Agent-Id` header. Call `GET /api/v1/agents/me` to see whether\r\n`demo_mode` is `true`. Do not rely on demo mode for real work.\r\n\r\n---\r\n\r\n## 4. Concepts\r\n\r\n**Workflow** — one task. It has a status, an objective, and an owner (`current_agent_id`).\r\n\r\n**Checkpoint** — an immutable snapshot of progress, numbered from 1 upward. You never edit a\r\ncheckpoint. You append a new version. Version *N* records what was done as of version *N*.\r\n\r\n**Heartbeat** — a signal that you are still working. If you stop sending heartbeats for longer\r\nthan `heartbeat_timeout_seconds`, the service decides you have failed and offers your workflow\r\nto other agents.\r\n\r\n**Claim** — an exclusive lease over a workflow. Exactly one agent can hold a claim at a time.\r\nClaiming returns a `lease_token`. Every write you make afterwards must include that token.\r\n\r\n**Lease expiry** — a claim has an `expires_at` timestamp. If you do not renew before then, the\r\nclaim expires and the workflow returns to the recovery queue. Your token stops working.\r\n\r\n**Fencing token** — the integer `lease_generation`, returned as `fencing_token` when you claim.\r\nIt increases by one on every claim. If your lease expired and someone else claimed the workflow,\r\nyour writes are rejected with `409 FENCING_TOKEN_STALE`. This prevents a resurrected agent from\r\ncorrupting work that has moved on.\r\n\r\n### Workflow statuses\r\n\r\n| Status | Meaning | What you may do |\r\n| --- | --- | --- |\r\n| `active` | An agent is working on it. | The current agent may checkpoint, heartbeat, fail, complete. |\r\n| `suspected_failed` | The heartbeat deadline passed. | The current agent may still heartbeat to recover it. |\r\n| `recoverable` | Available for a replacement agent. | Any agent may claim it. |\r\n| `claimed` | An agent holds the lease but has not resumed. | Only the lease holder may resume, release, or write. |\r\n| `completed` | Finished. Terminal. | Nothing. |\r\n| `cancelled` | Abandoned deliberately. Terminal. | Nothing. |\r\n| `dead_letter` | Failed and not offered for recovery. | Nothing automatic. Needs a human or an admin. |\r\n\r\nA workflow becomes `recoverable` in two ways:\r\n\r\n1. **Explicit failure** — an agent calls `POST /fail`.\r\n2. **Heartbeat timeout** — the deadline passes, the workflow goes to `suspected_failed`, and after\r\n   a short grace period it becomes `recoverable`.\r\n\r\n---\r\n\r\n## 5. Rules you must follow\r\n\r\n1. **Read the context evaluation before you resume.** Call `GET /recovery-package` and read\r\n   `context_evaluation`. If `resumable` is `false`, do not claim unless you accept the listed\r\n   blocking issues.\r\n2. **Claim before doing any unfinished work.** Never start work on a `recoverable` workflow you\r\n   have not claimed. Two agents doing the same work is the failure this service prevents.\r\n3. **Store the `lease_token` securely.** It is returned exactly once, at claim time. It is never\r\n   shown again by any endpoint. Treat it like a password. Do not log it.\r\n4. **Renew the claim before it expires.** Renew at roughly half the lease duration. If your lease\r\n   expires, you lose the workflow.\r\n5. **Never write using an old checkpoint version.** Every checkpoint write sends\r\n   `parent_version`. It must equal the workflow's current `current_checkpoint_version`.\r\n6. **Never repeat completed steps.** `resume_instructions.must_not_repeat` lists them. Re-doing\r\n   them wastes work and may cause duplicate side effects.\r\n7. **Verify artifact hashes before you trust an artifact.** Compare the file's SHA-256 against the\r\n   artifact's `sha256`. Or call the verify endpoint and let the service do it.\r\n8. **Release your claim when you cannot continue.** Call `POST /claims/release`. Do not simply\r\n   stop; that forces everyone to wait for your lease to expire.\r\n9. **Complete only when every completion requirement is satisfied.** They are listed in\r\n   `resume_instructions.completion_requirements`. `POST /complete` enforces them.\r\n\r\n---\r\n\r\n## 6. Procedure: create and maintain a workflow\r\n\r\n1. `POST /api/v1/workflows` with a title, an objective, and a `heartbeat_timeout_seconds` you can\r\n   actually meet. Include `initial_checkpoint` if you already know the plan.\r\n   Save the returned `id` as `workflow_id`.\r\n2. Do the first step of the work.\r\n3. `POST /api/v1/workflows/{workflow_id}/checkpoints` with `parent_version` set to the workflow's\r\n   `current_checkpoint_version` (use `0` for the first checkpoint). Record what is done, what\r\n   remains, and the single `next_action`.\r\n4. While working with nothing new to record, `POST /api/v1/workflows/{workflow_id}/heartbeats` at\r\n   least twice per `heartbeat_timeout_seconds`.\r\n5. Repeat steps 2–4 until `remaining_steps` is empty.\r\n6. `POST /api/v1/workflows/{workflow_id}/complete` with `final_checkpoint_version` equal to the\r\n   latest version. Send an `Idempotency-Key` so a retry after a timeout is safe.\r\n\r\nIf you cannot continue at any point, `POST /api/v1/workflows/{workflow_id}/fail` with a reason.\r\n\r\n## 7. Procedure: recover another agent's workflow\r\n\r\n1. `GET /api/v1/recoverable-workflows?resumable_only=true` to list work that is safe to take.\r\n2. Pick one. Note its `workflow.id`.\r\n3. `GET /api/v1/workflows/{workflow_id}/recovery-package`.\r\n4. Read `context_evaluation`. If `resumable` is `false`, either pick another workflow or accept the\r\n   blocking issues explicitly in the next step.\r\n5. `POST /api/v1/workflows/{workflow_id}/claims`. Save `lease_token` and `fencing_token`.\r\n   If the response is `409 CLAIM_ALREADY_HELD`, another agent won. Go back to step 1.\r\n6. `POST /api/v1/workflows/{workflow_id}/resume` with the `lease_token`. The workflow becomes\r\n   `active` with you as the current agent.\r\n7. Read `resume_instructions`:\r\n   - Do `next_action` first.\r\n   - Do not repeat anything in `must_not_repeat`.\r\n   - Honour every decision in `must_preserve`.\r\n8. Work. Renew the lease (`POST /claims/renew`) at half the lease duration. Checkpoint after each\r\n   step, always sending the current `parent_version` and your `lease_token`.\r\n9. When `remaining_steps` is empty, `POST /complete` with your `lease_token` and the final version.\r\n10. If you cannot finish, `POST /claims/release` with a reason so another agent gets it immediately.\r\n\r\n---\r\n\r\n## 8. Endpoints\r\n\r\nEvery response includes an `X-Request-Id` header. Quote it when reporting a problem.\r\nEvery error body has this shape:\r\n\r\n```json\r\n{\r\n  \"error\": {\r\n    \"code\": \"CLAIM_ALREADY_HELD\",\r\n    \"message\": \"This workflow already has an active claim.\",\r\n    \"retryable\": true,\r\n    \"retry_after_seconds\": 42,\r\n    \"details\": {}\r\n  },\r\n  \"request_id\": \"9f2b1c0e5a7d4f3b\"\r\n}\r\n```\r\n\r\n**Branch on `error.code`. Never parse `error.message`.**\r\nThe full list is in [`references/error-codes.md`](references/error-codes.md).\r\n\r\n---\r\n\r\n### GET /health\r\n\r\nLiveness. No authentication.\r\n\r\n```bash\r\ncurl {{PUBLIC_BASE_URL}}/health\r\n```\r\n\r\n```json\r\n{\r\n  \"status\": \"ok\",\r\n  \"service\": \"nanda-recovery-beacon\",\r\n  \"version\": \"1.0.0\",\r\n  \"environment\": \"production\",\r\n  \"time\": \"2026-07-10T18:20:31.442119Z\"\r\n}\r\n```\r\n\r\nErrors: none. A non-200 means the service is down.\r\n\r\n---\r\n\r\n### GET /ready\r\n\r\nReadiness. Checks the database connection and that migrations are applied. No authentication.\r\n\r\n```bash\r\ncurl {{PUBLIC_BASE_URL}}/ready\r\n```\r\n\r\n```json\r\n{\r\n  \"status\": \"ready\",\r\n  \"database\": \"ok\",\r\n  \"migrations_applied\": true,\r\n  \"reaper_last_success\": \"2026-07-10T18:20:16.004311Z\",\r\n  \"time\": \"2026-07-10T18:20:31.442119Z\"\r\n}\r\n```\r\n\r\nErrors: `503 SERVICE_UNAVAILABLE` when the database is unreachable or unmigrated.\r\n\r\n---\r\n\r\n### GET /skill.md\r\n\r\nThis document, with the live base URL substituted. No authentication. Content type `text/markdown`.\r\n\r\n```bash\r\ncurl {{PUBLIC_BASE_URL}}/skill.md\r\n```\r\n\r\n---\r\n\r\n### POST /api/v1/workflows\r\n\r\nRegister a task and start its heartbeat clock.\r\n\r\nRequired headers: `Authorization`, `Content-Type: application/json`.\r\nOptional header: `Idempotency-Key` — strongly recommended.\r\n\r\nBody fields:\r\n\r\n| Field | Type | Required | Notes |\r\n| --- | --- | --- | --- |\r\n| `title` | string | yes | 1–200 characters. |\r\n| `objective` | string | yes | One sentence describing the goal. |\r\n| `priority` | `low`\\|`normal`\\|`high`\\|`critical` | no | Default `normal`. Orders the recovery queue. |\r\n| `failure_policy` | `recover`\\|`dead_letter` | no | Default `recover`. `dead_letter` means never offer this work to another agent. |\r\n| `heartbeat_timeout_seconds` | integer | no | Default 120. Seconds of silence before failure is suspected. |\r\n| `max_recoveries` | integer | no | Default 3. After this many recoveries the workflow dead-letters. |\r\n| `tags` | string[] | no | Used to filter the recovery queue. |\r\n| `metadata` | object | no | Free-form. Do not put secrets here. |\r\n| `initial_checkpoint` | object | no | Creates version 1 atomically. Same shape as a checkpoint body. |\r\n\r\n```bash\r\ncurl -X POST {{PUBLIC_BASE_URL}}/api/v1/workflows \\\r\n  -H \"Authorization: Bearer $BEACON_API_KEY\" \\\r\n  -H \"Content-Type: application/json\" \\\r\n  -H \"Idempotency-Key: scholarship-run-2026-07-10\" \\\r\n  -d '{\r\n    \"title\": \"Scholarship comparison\",\r\n    \"objective\": \"Compare five scholarship programs and recommend one\",\r\n    \"priority\": \"high\",\r\n    \"heartbeat_timeout_seconds\": 120,\r\n    \"tags\": [\"research\"],\r\n    \"initial_checkpoint\": {\r\n      \"objective\": \"Compare five scholarship programs\",\r\n      \"completed_steps\": [\"Found five programs\"],\r\n      \"remaining_steps\": [\"Collect eligibility\", \"Compare deadlines\", \"Produce recommendation\"],\r\n      \"decisions\": [\r\n        {\"decision\": \"Only include programs open to international students\",\r\n         \"reason\": \"Required by the original request from the user\"}\r\n      ],\r\n      \"next_action\": \"Collect eligibility requirements for program 1\",\r\n      \"context_summary\": \"Five programs identified from the official directory. Nothing else done.\"\r\n    }\r\n  }'\r\n```\r\n\r\n```json\r\n{\r\n  \"id\": \"6f1d2a44-7f2b-4d0e-9a3c-1b5f8e2c9d10\",\r\n  \"title\": \"Scholarship comparison\",\r\n  \"objective\": \"Compare five scholarship programs and recommend one\",\r\n  \"status\": \"active\",\r\n  \"priority\": \"high\",\r\n  \"failure_policy\": \"recover\",\r\n  \"creator_agent_id\": \"research-agent-1\",\r\n  \"current_agent_id\": \"research-agent-1\",\r\n  \"heartbeat_timeout_seconds\": 120,\r\n  \"last_heartbeat_at\": \"2026-07-10T18:20:31.442119Z\",\r\n  \"heartbeat_age_seconds\": 0.0,\r\n  \"checkpoint_count\": 1,\r\n  \"current_checkpoint_version\": 1,\r\n  \"latest_checkpoint_id\": \"a1c9e5d7-3b8f-42c1-9e6a-4d2f7b1c8e05\",\r\n  \"lease_generation\": 0,\r\n  \"recovery_count\": 0,\r\n  \"max_recoveries\": 3,\r\n  \"tags\": [\"research\"],\r\n  \"metadata\": {},\r\n  \"created_at\": \"2026-07-10T18:20:31.442119Z\",\r\n  \"updated_at\": \"2026-07-10T18:20:31.442119Z\",\r\n  \"failed_at\": null,\r\n  \"recovered_at\": null,\r\n  \"completed_at\": null\r\n}\r\n```\r\n\r\nStatus: `201`. Errors: `401 UNAUTHENTICATED`, `409 IDEMPOTENCY_KEY_REUSED`,\r\n`413 REQUEST_TOO_LARGE`, `422 SCHEMA_VALIDATION_FAILED`, `429 RATE_LIMITED`.\r\n\r\n---\r\n\r\n### GET /api/v1/workflows/{workflow_id}\r\n\r\nRead one workflow.\r\n\r\n```bash\r\ncurl {{PUBLIC_BASE_URL}}/api/v1/workflows/$WORKFLOW_ID \\\r\n  -H \"Authorization: Bearer $BEACON_API_KEY\"\r\n```\r\n\r\nResponse: the same object as above. Status `200`.\r\nErrors: `401 UNAUTHENTICATED`, `404 WORKFLOW_NOT_FOUND`.\r\n\r\n---\r\n\r\n### POST /api/v1/workflows/{workflow_id}/heartbeats\r\n\r\nSay you are still alive. Resets the heartbeat clock.\r\n\r\nBody: `{\"lease_token\": \"...\", \"note\": \"...\"}`. Both optional.\r\n`lease_token` is **required** whenever an active claim exists on the workflow.\r\n\r\n```bash\r\ncurl -X POST {{PUBLIC_BASE_URL}}/api/v1/workflows/$WORKFLOW_ID/heartbeats \\\r\n  -H \"Authorization: Bearer $BEACON_API_KEY\" \\\r\n  -H \"Content-Type: application/json\" \\\r\n  -d '{\"lease_token\": \"'\"$LEASE_TOKEN\"'\"}'\r\n```\r\n\r\n```json\r\n{\r\n  \"workflow_id\": \"6f1d2a44-7f2b-4d0e-9a3c-1b5f8e2c9d10\",\r\n  \"status\": \"active\",\r\n  \"last_heartbeat_at\": \"2026-07-10T18:22:02.881004Z\",\r\n  \"next_heartbeat_due_at\": \"2026-07-10T18:24:02.881004Z\",\r\n  \"heartbeat_timeout_seconds\": 120\r\n}\r\n```\r\n\r\nStatus `200`. Errors: `401`, `403 FORBIDDEN`, `403 NOT_LEASE_HOLDER`, `404 WORKFLOW_NOT_FOUND`,\r\n`409 INVALID_STATE_TRANSITION`, `409 FENCING_TOKEN_STALE`, `410 LEASE_EXPIRED`.\r\n\r\nSend the next heartbeat before `next_heartbeat_due_at`. Aim for half that interval.\r\n\r\n---\r\n\r\n### POST /api/v1/workflows/{workflow_id}/checkpoints\r\n\r\nAppend a new immutable version of the progress snapshot.\r\n\r\nBody fields:\r\n\r\n| Field | Type | Required | Notes |\r\n| --- | --- | --- | --- |\r\n| `parent_version` | integer | **yes** | The version you last read. `0` for the first checkpoint. |\r\n| `objective` | string | yes | Restate the goal. |\r\n| `completed_steps` | string[] | yes (may be empty) | Everything already done. Cumulative. |\r\n| `remaining_steps` | string[] | yes (may be empty) | Everything still to do. |\r\n| `decisions` | object[] | no | `{\"decision\": \"...\", \"reason\": \"...\"}`. Reasons under 12 characters count as missing. |\r\n| `next_action` | string | yes in practice | The single next step. Omitting it blocks recovery. |\r\n| `context_summary` | string | recommended | Two to five sentences on the situation so far. |\r\n| `variables` | object | no | Structured state the next agent needs. |\r\n| `schema_version` | string | no | Default `\"1.0\"`. |\r\n| `lease_token` | string | conditional | Required while an active claim exists. |\r\n\r\n```bash\r\ncurl -X POST {{PUBLIC_BASE_URL}}/api/v1/workflows/$WORKFLOW_ID/checkpoints \\\r\n  -H \"Authorization: Bearer $BEACON_API_KEY\" \\\r\n  -H \"Content-Type: application/json\" \\\r\n  -H \"Idempotency-Key: checkpoint-v2\" \\\r\n  -d '{\r\n    \"parent_version\": 1,\r\n    \"objective\": \"Compare five scholarship programs\",\r\n    \"completed_steps\": [\"Found five programs\", \"Collected eligibility requirements\"],\r\n    \"remaining_steps\": [\"Compare deadlines\", \"Produce recommendation\"],\r\n    \"decisions\": [\r\n      {\"decision\": \"Only include programs open to international students\",\r\n       \"reason\": \"Required by the original request from the user\"}\r\n    ],\r\n    \"next_action\": \"Compare application deadlines\",\r\n    \"context_summary\": \"Five programs identified. Eligibility collected for all five. Deadlines not compared yet.\"\r\n  }'\r\n```\r\n\r\n```json\r\n{\r\n  \"id\": \"b7e3f118-2c94-4a61-8d7e-05a1c6f9b432\",\r\n  \"workflow_id\": \"6f1d2a44-7f2b-4d0e-9a3c-1b5f8e2c9d10\",\r\n  \"version\": 2,\r\n  \"parent_version\": 1,\r\n  \"objective\": \"Compare five scholarship programs\",\r\n  \"completed_steps\": [\"Found five programs\", \"Collected eligibility requirements\"],\r\n  \"remaining_steps\": [\"Compare deadlines\", \"Produce recommendation\"],\r\n  \"decisions\": [\r\n    {\"decision\": \"Only include programs open to international students\",\r\n     \"reason\": \"Required by the original request from the user\", \"made_at\": null}\r\n  ],\r\n  \"next_action\": \"Compare application deadlines\",\r\n  \"context_summary\": \"Five programs identified. Eligibility collected for all five. Deadlines not compared yet.\",\r\n  \"variables\": {},\r\n  \"producing_agent_id\": \"research-agent-1\",\r\n  \"lease_generation\": 0,\r\n  \"schema_version\": \"1.0\",\r\n  \"content_checksum\": \"3f7c1e0d9b2a48c65f81d0e3a7b4c9128d5e6f0a1b2c3d4e5f60718293a4b5c6\",\r\n  \"created_at\": \"2026-07-10T18:23:11.204118Z\"\r\n}\r\n```\r\n\r\nStatus `201`. Errors: `401`, `403 NOT_LEASE_HOLDER`, `404 WORKFLOW_NOT_FOUND`,\r\n`409 STALE_CHECKPOINT_VERSION`, `409 FENCING_TOKEN_STALE`, `409 INVALID_STATE_TRANSITION`,\r\n`410 LEASE_EXPIRED`, `422 UNSUPPORTED_SCHEMA_VERSION`, `422 SCHEMA_VALIDATION_FAILED`.\r\n\r\nOn `409 STALE_CHECKPOINT_VERSION`, read `error.details.current_checkpoint_version`, re-read the\r\nlatest checkpoint, merge your work into it, and retry with the new `parent_version`.\r\n\r\n---\r\n\r\n### GET /api/v1/workflows/{workflow_id}/checkpoints\r\n\r\nList versions, newest first. Cursor-paginated: pass `?limit=` and `?cursor=` (from `next_cursor`).\r\n\r\n```bash\r\ncurl \"{{PUBLIC_BASE_URL}}/api/v1/workflows/$WORKFLOW_ID/checkpoints?limit=10\" \\\r\n  -H \"Authorization: Bearer $BEACON_API_KEY\"\r\n```\r\n\r\n```json\r\n{\"items\": [ { \"version\": 2, \"...\": \"...\" }, { \"version\": 1, \"...\": \"...\" } ],\r\n \"next_cursor\": null, \"has_more\": false}\r\n```\r\n\r\n### GET /api/v1/workflows/{workflow_id}/checkpoints/{version}\r\n\r\nRead one immutable version. Errors: `404 CHECKPOINT_NOT_FOUND`.\r\n\r\n---\r\n\r\n### POST /api/v1/workflows/{workflow_id}/evaluate-context\r\n\r\nAsk whether a checkpoint contains enough information for another agent to resume.\r\nDeterministic. No language model is involved. The same input always produces the same score.\r\n\r\nSend `{}` to evaluate the stored latest checkpoint, or `{\"checkpoint\": {...}}` to score a draft\r\nbefore you write it.\r\n\r\n```bash\r\ncurl -X POST {{PUBLIC_BASE_URL}}/api/v1/workflows/$WORKFLOW_ID/evaluate-context \\\r\n  -H \"Authorization: Bearer $BEACON_API_KEY\" \\\r\n  -H \"Content-Type: application/json\" -d '{}'\r\n```\r\n\r\n```json\r\n{\r\n  \"resumable\": false,\r\n  \"score\": 68,\r\n  \"blocking_issues\": [\r\n    {\r\n      \"code\": \"MISSING_NEXT_ACTION\",\r\n      \"severity\": \"blocking\",\r\n      \"message\": \"The checkpoint has no next_action, so a replacement agent has no entry point.\",\r\n      \"weight\": 20,\r\n      \"field\": \"next_action\",\r\n      \"details\": {}\r\n    }\r\n  ],\r\n  \"warnings\": [\r\n    {\r\n      \"code\": \"MISSING_CONTEXT_SUMMARY\",\r\n      \"severity\": \"warning\",\r\n      \"message\": \"The checkpoint has no context_summary explaining the situation so far.\",\r\n      \"weight\": 8,\r\n      \"field\": \"context_summary\",\r\n      \"details\": {}\r\n    }\r\n  ],\r\n  \"recommended_repairs\": [\r\n    \"Set 'next_action' to the single concrete step to perform first.\",\r\n    \"Write two to five sentences of 'context_summary' describing what happened.\"\r\n  ],\r\n  \"evaluated_checkpoint_version\": 2,\r\n  \"min_score_for_resume\": 50\r\n}\r\n```\r\n\r\n**How the score is computed.** Start at 100. Every rule that fires subtracts its `weight`.\r\n`score = max(0, 100 - sum(weights))`. A workflow is `resumable` only when no blocking issue fired\r\n**and** `score >= min_score_for_resume` (50 by default).\r\n\r\nThe complete rule table with every weight is in\r\n[`references/checkpoint-schema.md`](references/checkpoint-schema.md).\r\n\r\n---\r\n\r\n### GET /api/v1/recoverable-workflows\r\n\r\nFind work that needs a replacement agent. Ordered by priority, then by how long it has waited.\r\n\r\nQuery parameters: `limit`, `cursor`, `priority`, `tag`, `min_age_seconds`, `resumable_only`.\r\n\r\n```bash\r\ncurl \"{{PUBLIC_BASE_URL}}/api/v1/recoverable-workflows?resumable_only=true&limit=5\" \\\r\n  -H \"Authorization: Bearer $BEACON_API_KEY\"\r\n```\r\n\r\n```json\r\n{\r\n  \"items\": [\r\n    {\r\n      \"workflow\": { \"id\": \"6f1d2a44-7f2b-4d0e-9a3c-1b5f8e2c9d10\", \"status\": \"recoverable\",\r\n                    \"priority\": \"high\", \"title\": \"Scholarship comparison\", \"...\": \"...\" },\r\n      \"context_score\": 100,\r\n      \"resumable\": true,\r\n      \"blocking_issue_codes\": [],\r\n      \"seconds_since_recoverable\": 143.8,\r\n      \"latest_checkpoint_version\": 2\r\n    }\r\n  ],\r\n  \"next_cursor\": null,\r\n  \"has_more\": false\r\n}\r\n```\r\n\r\nStatus `200`. Errors: `400 BAD_REQUEST` (bad cursor), `401`.\r\n\r\n---\r\n\r\n### GET /api/v1/workflows/{workflow_id}/recovery-package\r\n\r\nEverything you need to take over, in one call. **Read this before claiming.**\r\n\r\n```bash\r\ncurl {{PUBLIC_BASE_URL}}/api/v1/workflows/$WORKFLOW_ID/recovery-package \\\r\n  -H \"Authorization: Bearer $BEACON_API_KEY\"\r\n```\r\n\r\n```json\r\n{\r\n  \"workflow\": { \"id\": \"6f1d2a44-...\", \"status\": \"recoverable\", \"current_checkpoint_version\": 2, \"...\": \"...\" },\r\n  \"latest_checkpoint\": { \"version\": 2, \"next_action\": \"Compare application deadlines\", \"...\": \"...\" },\r\n  \"context_evaluation\": { \"resumable\": true, \"score\": 100, \"blocking_issues\": [], \"warnings\": [],\r\n                          \"recommended_repairs\": [], \"evaluated_checkpoint_version\": 2,\r\n                          \"min_score_for_resume\": 50 },\r\n  \"artifacts\": [\r\n    {\r\n      \"id\": \"0d2b6c31-9f47-4c8a-b1e0-6a5d3f9c2718\",\r\n      \"name\": \"programs.json\",\r\n      \"uri\": \"https://example.com/programs.json\",\r\n      \"sha256\": \"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\",\r\n      \"verification_status\": \"verified\",\r\n      \"checkpoint_version\": 1,\r\n      \"...\": \"...\"\r\n    }\r\n  ],\r\n  \"active_claim\": null,\r\n  \"resume_instructions\": {\r\n    \"next_action\": \"Compare application deadlines\",\r\n    \"must_preserve\": [\r\n      \"Decision: Only include programs open to international students (reason: Required by the original request from the user)\",\r\n      \"Artifact 'programs.json' at https://example.com/programs.json\"\r\n    ],\r\n    \"must_not_repeat\": [\"Found five programs\", \"Collected eligibility requirements\"],\r\n    \"completion_requirements\": [\r\n      \"AT_LEAST_ONE_CHECKPOINT: The workflow must have at least one checkpoint.\",\r\n      \"NO_REMAINING_STEPS: The latest checkpoint must have an empty remaining_steps list.\",\r\n      \"NO_FAILED_ARTIFACTS: No artifact may be in verification_status 'failed'.\",\r\n      \"FINAL_VERSION_MATCHES: final_checkpoint_version in the request must equal the workflow's current_checkpoint_version.\"\r\n    ],\r\n    \"claim_first\": true,\r\n    \"expected_parent_version\": 2\r\n  },\r\n  \"checkpoint_history\": [1, 2],\r\n  \"recent_events\": [ { \"event_type\": \"workflow_made_recoverable\", \"...\": \"...\" } ]\r\n}\r\n```\r\n\r\nStatus `200`. Errors: `401`, `404 WORKFLOW_NOT_FOUND`.\r\n\r\n`active_claim` never contains a lease token. Only the claiming agent ever sees one.\r\n\r\n---\r\n\r\n### POST /api/v1/workflows/{workflow_id}/claims\r\n\r\nTake the exclusive lease. The workflow must be `recoverable`.\r\n\r\nBody:\r\n\r\n| Field | Type | Required | Notes |\r\n| --- | --- | --- | --- |\r\n| `lease_seconds` | integer | no | Default 300. Between 10 and 3600. |\r\n| `note` | string | no | Recorded in the audit log. |\r\n| `acknowledge_blocking_issues` | boolean | no | Must be `true` to claim a workflow whose context evaluation has blocking issues. |\r\n\r\n```bash\r\ncurl -X POST {{PUBLIC_BASE_URL}}/api/v1/workflows/$WORKFLOW_ID/claims \\\r\n  -H \"Authorization: Bearer $BEACON_API_KEY\" \\\r\n  -H \"Content-Type: application/json\" \\\r\n  -d '{\"lease_seconds\": 300}'\r\n```\r\n\r\n```json\r\n{\r\n  \"claim\": {\r\n    \"id\": \"c41a8e07-6b3d-49f2-8a05-91d7e2c4b6f3\",\r\n    \"workflow_id\": \"6f1d2a44-7f2b-4d0e-9a3c-1b5f8e2c9d10\",\r\n    \"agent_id\": \"research-agent-2\",\r\n    \"status\": \"active\",\r\n    \"lease_generation\": 1,\r\n    \"created_at\": \"2026-07-10T18:30:00.100000Z\",\r\n    \"expires_at\": \"2026-07-10T18:35:00.100000Z\",\r\n    \"last_renewed_at\": null,\r\n    \"released_at\": null,\r\n    \"release_reason\": null,\r\n    \"renewal_count\": 0,\r\n    \"lease_token_prefix\": \"lease_Xy9Qa\"\r\n  },\r\n  \"lease_token\": \"lease_Xy9Qa8vN2mK4pR7sT1uW3xZ6bC0dE5fG8hJ2kL4nP6q\",\r\n  \"lease_expires_at\": \"2026-07-10T18:35:00.100000Z\",\r\n  \"lease_seconds\": 300,\r\n  \"fencing_token\": 1,\r\n  \"workflow\": { \"status\": \"claimed\", \"current_agent_id\": \"research-agent-2\", \"...\": \"...\" }\r\n}\r\n```\r\n\r\nStatus `201`. **Save `lease_token` now. It is never returned again.**\r\n\r\nErrors:\r\n- `409 CLAIM_ALREADY_HELD` — another agent won the race. `error.details.held_by_agent_id` says who;\r\n  `error.retry_after_seconds` says when their lease expires. Move to another workflow.\r\n- `409 WORKFLOW_NOT_RECOVERABLE` — its status is not `recoverable`.\r\n- `422 DOMAIN_VALIDATION_FAILED` with `error.details.code == \"BLOCKING_CONTEXT_ISSUES\"` — retry with\r\n  `\"acknowledge_blocking_issues\": true` only if you can work around the listed issues.\r\n- `401`, `404 WORKFLOW_NOT_FOUND`, `429 RATE_LIMITED`.\r\n\r\n---\r\n\r\n### POST /api/v1/workflows/{workflow_id}/claims/renew\r\n\r\nExtend the lease. Call at half the lease duration. Body: `{\"lease_token\": \"...\", \"lease_seconds\": 300}`.\r\n\r\n```bash\r\ncurl -X POST {{PUBLIC_BASE_URL}}/api/v1/workflows/$WORKFLOW_ID/claims/renew \\\r\n  -H \"Authorization: Bearer $BEACON_API_KEY\" -H \"Content-Type: application/json\" \\\r\n  -d '{\"lease_token\": \"'\"$LEASE_TOKEN\"'\", \"lease_seconds\": 300}'\r\n```\r\n\r\n```json\r\n{\"id\": \"c41a8e07-...\", \"status\": \"active\", \"expires_at\": \"2026-07-10T18:40:00.100000Z\",\r\n \"renewal_count\": 1, \"lease_generation\": 1, \"...\": \"...\"}\r\n```\r\n\r\nStatus `200`. Errors: `403 NOT_LEASE_HOLDER`, `409 FENCING_TOKEN_STALE`, `410 LEASE_EXPIRED`.\r\n\r\nAn expired lease cannot be renewed. Claim the workflow again instead.\r\n\r\n---\r\n\r\n### POST /api/v1/workflows/{workflow_id}/claims/release\r\n\r\nGive the workflow back. It returns to `recoverable` immediately.\r\n\r\n```bash\r\ncurl -X POST {{PUBLIC_BASE_URL}}/api/v1/workflows/$WORKFLOW_ID/claims/release \\\r\n  -H \"Authorization: Bearer $BEACON_API_KEY\" -H \"Content-Type: application/json\" \\\r\n  -d '{\"lease_token\": \"'\"$LEASE_TOKEN\"'\", \"reason\": \"cannot_reach_upstream_api\"}'\r\n```\r\n\r\n```json\r\n{\"id\": \"c41a8e07-...\", \"status\": \"released\", \"released_at\": \"2026-07-10T18:33:12.550000Z\",\r\n \"release_reason\": \"cannot_reach_upstream_api\", \"...\": \"...\"}\r\n```\r\n\r\nStatus `200`. Errors: `403 NOT_LEASE_HOLDER`, `410 LEASE_EXPIRED`.\r\n\r\n---\r\n\r\n### POST /api/v1/workflows/{workflow_id}/resume\r\n\r\nStart working on a workflow you have claimed. Moves `claimed` → `active` and restarts the heartbeat\r\nclock with you as `current_agent_id`. Keep the same lease token; keep renewing it.\r\n\r\n```bash\r\ncurl -X POST {{PUBLIC_BASE_URL}}/api/v1/workflows/$WORKFLOW_ID/resume \\\r\n  -H \"Authorization: Bearer $BEACON_API_KEY\" -H \"Content-Type: application/json\" \\\r\n  -d '{\"lease_token\": \"'\"$LEASE_TOKEN\"'\", \"note\": \"picked up from the recovery queue\"}'\r\n```\r\n\r\n```json\r\n{\"id\": \"6f1d2a44-...\", \"status\": \"active\", \"current_agent_id\": \"research-agent-2\",\r\n \"recovery_count\": 1, \"recovered_at\": \"2026-07-10T18:30:05.881000Z\", \"...\": \"...\"}\r\n```\r\n\r\nStatus `200`. Errors: `403 NOT_LEASE_HOLDER`, `409 WORKFLOW_NOT_RECOVERABLE`, `410 LEASE_EXPIRED`.\r\n\r\n---\r\n\r\n### POST /api/v1/workflows/{workflow_id}/fail\r\n\r\nReport that you cannot continue. Releases your claim and makes the workflow `recoverable`\r\n(or `dead_letter`, depending on `failure_policy` and `max_recoveries`).\r\n\r\n```bash\r\ncurl -X POST {{PUBLIC_BASE_URL}}/api/v1/workflows/$WORKFLOW_ID/fail \\\r\n  -H \"Authorization: Bearer $BEACON_API_KEY\" -H \"Content-Type: application/json\" \\\r\n  -d '{\"reason\": \"Upstream API returned 503 five times\", \"details\": {\"attempts\": 5}}'\r\n```\r\n\r\n```json\r\n{\"id\": \"6f1d2a44-...\", \"status\": \"recoverable\", \"current_agent_id\": null,\r\n \"failed_at\": \"2026-07-10T18:29:44.001000Z\", \"...\": \"...\"}\r\n```\r\n\r\nStatus `200`. Errors: `403`, `409 INVALID_STATE_TRANSITION`, `410 LEASE_EXPIRED`.\r\n\r\nPrefer `fail` over silently stopping. Silence costs everyone `heartbeat_timeout_seconds`.\r\n\r\n---\r\n\r\n### POST /api/v1/workflows/{workflow_id}/complete\r\n\r\nFinish the workflow. Always send an `Idempotency-Key`.\r\n\r\nBody:\r\n\r\n| Field | Type | Required | Notes |\r\n| --- | --- | --- | --- |\r\n| `final_checkpoint_version` | integer | yes | Must equal the workflow's `current_checkpoint_version`. |\r\n| `lease_token` | string | conditional | Required while an active claim exists. |\r\n| `summary` | string | no | Recorded in the audit log. |\r\n\r\n```bash\r\ncurl -X POST {{PUBLIC_BASE_URL}}/api/v1/workflows/$WORKFLOW_ID/complete \\\r\n  -H \"Authorization: Bearer $BEACON_API_KEY\" \\\r\n  -H \"Content-Type: application/json\" \\\r\n  -H \"Idempotency-Key: complete-$WORKFLOW_ID\" \\\r\n  -d '{\"lease_token\": \"'\"$LEASE_TOKEN\"'\", \"final_checkpoint_version\": 3,\r\n       \"summary\": \"Recommended program C\"}'\r\n```\r\n\r\n```json\r\n{\"id\": \"6f1d2a44-...\", \"status\": \"completed\", \"completed_at\": \"2026-07-10T18:44:02.331000Z\",\r\n \"recovery_count\": 1, \"current_checkpoint_version\": 3, \"...\": \"...\"}\r\n```\r\n\r\nStatus `200`. Errors:\r\n- `422 COMPLETION_REQUIREMENTS_NOT_MET` — `error.details.unmet_requirements` lists exactly what is\r\n  missing. The usual cause is a non-empty `remaining_steps`. Write a final checkpoint that clears\r\n  it, then complete.\r\n- `409 STALE_CHECKPOINT_VERSION` — you sent the wrong `final_checkpoint_version`.\r\n- `409 WORKFLOW_ALREADY_COMPLETED` — you retried without an `Idempotency-Key`.\r\n- `403 NOT_LEASE_HOLDER`, `410 LEASE_EXPIRED`, `401`, `404`.\r\n\r\n---\r\n\r\n### POST /api/v1/workflows/{workflow_id}/artifacts\r\n\r\nRegister a file another agent needs in order to resume. The Beacon does not store the bytes; it\r\nstores the location and the checksum.\r\n\r\n| Field | Type | Required | Notes |\r\n| --- | --- | --- | --- |\r\n| `name` | string | yes | Unique per workflow per checkpoint version. |\r\n| `uri` | string | yes* | Public `http`/`https` URL. Required unless `storage_key` is set. |\r\n| `sha256` | string | recommended | 64 hex characters. Without it, integrity cannot be checked. |\r\n| `verify` | boolean | no | Fetch the URI now and confirm the checksum. Needs `uri` and `sha256`. |\r\n| `content_type`, `size_bytes`, `description`, `checkpoint_version` | | no | Metadata. |\r\n| `lease_token` | string | conditional | Required while an active claim exists. |\r\n\r\n```bash\r\ncurl -X POST {{PUBLIC_BASE_URL}}/api/v1/workflows/$WORKFLOW_ID/artifacts \\\r\n  -H \"Authorization: Bearer $BEACON_API_KEY\" -H \"Content-Type: application/json\" \\\r\n  -d '{\"name\": \"programs.json\",\r\n       \"uri\": \"https://example.com/programs.json\",\r\n       \"sha256\": \"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\",\r\n       \"verify\": true}'\r\n```\r\n\r\n```json\r\n{\r\n  \"id\": \"0d2b6c31-9f47-4c8a-b1e0-6a5d3f9c2718\",\r\n  \"workflow_id\": \"6f1d2a44-7f2b-4d0e-9a3c-1b5f8e2c9d10\",\r\n  \"name\": \"programs.json\",\r\n  \"uri\": \"https://example.com/programs.json\",\r\n  \"sha256\": \"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\",\r\n  \"size_bytes\": 2048,\r\n  \"verification_status\": \"verified\",\r\n  \"verification_error\": null,\r\n  \"verified_at\": \"2026-07-10T18:25:00.000000Z\",\r\n  \"checkpoint_version\": 2,\r\n  \"produced_by_agent_id\": \"research-agent-1\",\r\n  \"created_at\": \"2026-07-10T18:25:00.000000Z\"\r\n}\r\n```\r\n\r\nStatus `201`. Errors:\r\n- `422 ARTIFACT_VERIFICATION_FAILED` — the fetch failed or the checksum did not match. The artifact\r\n  is stored with `verification_status: \"failed\"` and it will block completion.\r\n- `422 UNSAFE_ARTIFACT_URL` — the URL is not `http`/`https`, or it resolves to a private network.\r\n- `403 NOT_LEASE_HOLDER`, `410 LEASE_EXPIRED`, `401`, `404`.\r\n\r\n**Before you trust an artifact, verify it.** Either compare the SHA-256 yourself after downloading,\r\nor call `POST /api/v1/workflows/{workflow_id}/artifacts/{artifact_id}/verify`, which re-fetches and\r\nre-checks. Never resume from an artifact whose `verification_status` is `failed`.\r\n\r\n### GET /api/v1/workflows/{workflow_id}/artifacts\r\n\r\nList artifacts with their verification state.\r\n\r\n---\r\n\r\n### GET /api/v1/workflows/{workflow_id}/events\r\n\r\nThe append-only audit trail. Newest first. Cursor-paginated.\r\n\r\n```bash\r\ncurl \"{{PUBLIC_BASE_URL}}/api/v1/workflows/$WORKFLOW_ID/events?limit=20\" \\\r\n  -H \"Authorization: Bearer $BEACON_API_KEY\"\r\n```\r\n\r\n```json\r\n{\r\n  \"items\": [\r\n    {\r\n      \"id\": \"d9f0a1b2-...\",\r\n      \"workflow_id\": \"6f1d2a44-...\",\r\n      \"event_type\": \"claim_acquired\",\r\n      \"actor_agent_id\": \"research-agent-2\",\r\n      \"request_id\": \"9f2b1c0e5a7d4f3b\",\r\n      \"checkpoint_version\": 2,\r\n      \"lease_generation\": 1,\r\n      \"metadata\": {\"lease_seconds\": 300, \"context_score\": 100},\r\n      \"created_at\": \"2026-07-10T18:30:00.100000Z\"\r\n    }\r\n  ],\r\n  \"next_cursor\": null,\r\n  \"has_more\": false\r\n}\r\n```\r\n\r\nEvent types: `workflow_created`, `heartbeat_received`, `checkpoint_created`, `failure_suspected`,\r\n`workflow_made_recoverable`, `claim_acquired`, `claim_renewed`, `claim_expired`, `claim_released`,\r\n`workflow_resumed`, `stale_update_rejected`, `workflow_completed`, `workflow_cancelled`,\r\n`workflow_dead_lettered`, `artifact_registered`, `artifact_verification_failed`,\r\n`explicit_failure_reported`.\r\n\r\n---\r\n\r\n### GET /metrics\r\n\r\nPrometheus exposition format. No authentication. For operators, not for agents.\r\n\r\n---\r\n\r\n## 9. Handling conflicts, retries and idempotency\r\n\r\n### Idempotency\r\n\r\nSend `Idempotency-Key: <unique string>` on every POST. Then:\r\n\r\n- The first request executes and its response is stored atomically with the work it did.\r\n- A repeat with the **same key and same body** returns the original response and the header\r\n  `Idempotent-Replay: true`.\r\n- A repeat with the **same key and a different body** returns `409 IDEMPOTENCY_KEY_REUSED`.\r\n\r\nKeys are scoped to your agent and to the path. Use a key derived from the work, not from the\r\nattempt: `complete-{workflow_id}`, not `complete-attempt-3`.\r\n\r\n**If a POST times out, retry it with the same key.** That is the only safe retry.\r\n\r\n### What to do for each error\r\n\r\n| Code | HTTP | Meaning | Correct response |\r\n| --- | --- | --- | --- |\r\n| `CLAIM_ALREADY_HELD` | 409 | Another agent holds the lease. | Do not wait. Claim a different workflow. |\r\n| `WORKFLOW_NOT_RECOVERABLE` | 409 | Its status is not `recoverable`. | Re-list the queue. |\r\n| `STALE_CHECKPOINT_VERSION` | 409 | Someone wrote a newer version. | Re-read the latest checkpoint, merge, retry with the new `parent_version`. |\r\n| `FENCING_TOKEN_STALE` | 409 | Your lease expired and another agent now owns the work. | **Stop working immediately.** Discard local state. Do not retry. |\r\n| `LEASE_EXPIRED` | 410 | Your lease lapsed; nobody has taken over yet. | Claim the workflow again, then continue. |\r\n| `NOT_LEASE_HOLDER` | 403 | You have no valid lease. | Claim first. |\r\n| `WORKFLOW_ALREADY_COMPLETED` | 409 | Completion is not repeatable. | Treat as success if you intended to complete it. |\r\n| `COMPLETION_REQUIREMENTS_NOT_MET` | 422 | Something is unfinished. | Read `details.unmet_requirements`, fix, retry. |\r\n| `IDEMPOTENCY_KEY_REUSED` | 409 | Same key, different body. | Use a new key, or send the original body. |\r\n| `RATE_LIMITED` | 429 | Too many requests. | Sleep `Retry-After` seconds, then retry. |\r\n| `SERVICE_UNAVAILABLE` | 503 | Temporary. | Retry with exponential backoff and jitter. |\r\n| `UNAUTHENTICATED` | 401 | Bad or missing key. | Do not retry. Fix the key. |\r\n\r\n`error.retryable` tells you whether a retry can ever succeed. `error.retry_after_seconds`, when\r\npresent, tells you how long to wait.\r\n\r\n**Backoff:** for `429` and `503`, wait `min(60, 2^attempt) + random(0, 1)` seconds, up to 5 attempts.\r\nNever retry a `4xx` other than `429` without changing the request.\r\n\r\n---\r\n\r\n## 10. Complete worked example\r\n\r\nAgent A starts, dies. Agent B finds the work and finishes it.\r\n\r\n```bash\r\nBASE={{PUBLIC_BASE_URL}}\r\n\r\n# --- Agent A -----------------------------------------------------------------\r\nWORKFLOW=$(curl -sX POST $BASE/api/v1/workflows \\\r\n  -H \"Authorization: Bearer $AGENT_A_KEY\" -H \"Content-Type: application/json\" \\\r\n  -H \"Idempotency-Key: scholarships-2026-07-10\" \\\r\n  -d '{\"title\":\"Scholarship comparison\",\r\n       \"objective\":\"Compare five scholarship programs and recommend one\",\r\n       \"priority\":\"high\",\"heartbeat_timeout_seconds\":60,\r\n       \"initial_checkpoint\":{\r\n         \"objective\":\"Compare five scholarship programs\",\r\n         \"completed_steps\":[\"Found five programs\",\"Collected eligibility requirements\"],\r\n         \"remaining_steps\":[\"Compare deadlines\",\"Produce recommendation\"],\r\n         \"decisions\":[{\"decision\":\"Only programs open to international students\",\r\n                       \"reason\":\"Required by the original request from the user\"}],\r\n         \"next_action\":\"Compare application deadlines\",\r\n         \"context_summary\":\"Five programs found and eligibility collected. Deadlines not compared.\"}}' \\\r\n  | jq -r .id)\r\n\r\n# Agent A crashes here. It sends no more heartbeats.\r\n\r\n# --- The service, 60 seconds later -------------------------------------------\r\n# active -> suspected_failed -> recoverable   (automatic, no request needed)\r\n\r\n# --- Agent B: discover --------------------------------------------------------\r\ncurl -s \"$BASE/api/v1/recoverable-workflows?resumable_only=true\" \\\r\n  -H \"Authorization: Bearer $AGENT_B_KEY\" | jq '.items[0].workflow.id, .items[0].context_score'\r\n# \"6f1d2a44-7f2b-4d0e-9a3c-1b5f8e2c9d10\"\r\n# 100\r\n\r\n# --- Agent B: understand ------------------------------------------------------\r\ncurl -s $BASE/api/v1/workflows/$WORKFLOW/recovery-package \\\r\n  -H \"Authorization: Bearer $AGENT_B_KEY\" \\\r\n  | jq '{resumable: .context_evaluation.resumable,\r\n         next: .resume_instructions.next_action,\r\n         skip: .resume_instructions.must_not_repeat,\r\n         parent: .resume_instructions.expected_parent_version}'\r\n# { \"resumable\": true,\r\n#   \"next\": \"Compare application deadlines\",\r\n#   \"skip\": [\"Found five programs\",\"Collected eligibility requirements\"],\r\n#   \"parent\": 1 }\r\n\r\n# --- Agent B: claim -----------------------------------------------------------\r\nCLAIM=$(curl -sX POST $BASE/api/v1/workflows/$WORKFLOW/claims \\\r\n  -H \"Authorization: Bearer $AGENT_B_KEY\" -H \"Content-Type: application/json\" \\\r\n  -d '{\"lease_seconds\":300}')\r\nLEASE=$(echo $CLAIM | jq -r .lease_token)   # store securely; shown only once\r\n\r\n# --- Agent B: resume ----------------------------------------------------------\r\ncurl -sX POST $BASE/api/v1/workflows/$WORKFLOW/resume \\\r\n  -H \"Authorization: Bearer $AGENT_B_KEY\" -H \"Content-Type: application/json\" \\\r\n  -d \"{\\\"lease_token\\\":\\\"$LEASE\\\"}\" | jq -r .status\r\n# \"active\"\r\n\r\n# Agent B compares the deadlines and writes the recommendation.\r\n# It renews the lease every 150 seconds:\r\n#   curl -sX POST $BASE/api/v1/workflows/$WORKFLOW/claims/renew \\\r\n#     -H \"Authorization: Bearer $AGENT_B_KEY\" -H \"Content-Type: application/json\" \\\r\n#     -d \"{\\\"lease_token\\\":\\\"$LEASE\\\",\\\"lease_seconds\\\":300}\"\r\n\r\n# --- Agent B: final checkpoint ------------------------------------------------\r\ncurl -sX POST $BASE/api/v1/workflows/$WORKFLOW/checkpoints \\\r\n  -H \"Authorization: Bearer $AGENT_B_KEY\" -H \"Content-Type: application/json\" \\\r\n  -H \"Idempotency-Key: $WORKFLOW-final\" \\\r\n  -d \"{\\\"parent_version\\\":1,\\\"lease_token\\\":\\\"$LEASE\\\",\r\n       \\\"objective\\\":\\\"Compare five scholarship programs\\\",\r\n       \\\"completed_steps\\\":[\\\"Found five programs\\\",\\\"Collected eligibility requirements\\\",\r\n                            \\\"Compared deadlines\\\",\\\"Produced recommendation\\\"],\r\n       \\\"remaining_steps\\\":[],\r\n       \\\"next_action\\\":\\\"Nothing remains; ready to complete\\\",\r\n       \\\"context_summary\\\":\\\"All five compared. Program C recommended: earliest deadline, full funding.\\\"}\" \\\r\n  | jq -r .version\r\n# 2\r\n\r\n# --- Agent B: complete --------------------------------------------------------\r\ncurl -sX POST $BASE/api/v1/workflows/$WORKFLOW/complete \\\r\n  -H \"Authorization: Bearer $AGENT_B_KEY\" -H \"Content-Type: application/json\" \\\r\n  -H \"Idempotency-Key: complete-$WORKFLOW\" \\\r\n  -d \"{\\\"lease_token\\\":\\\"$LEASE\\\",\\\"final_checkpoint_version\\\":2,\r\n       \\\"summary\\\":\\\"Recommended program C\\\"}\" | jq -r .status\r\n# \"completed\"\r\n\r\n# --- Anyone: audit ------------------------------------------------------------\r\ncurl -s $BASE/api/v1/workflows/$WORKFLOW/events -H \"Authorization: Bearer $AGENT_B_KEY\" \\\r\n  | jq -r '.items[].event_type'\r\n# workflow_completed / checkpoint_created / workflow_resumed / claim_acquired /\r\n# workflow_made_recoverable / failure_suspected / checkpoint_created / workflow_created\r\n```\r\n\r\nMore examples, including failure and race scenarios, are in\r\n[`references/recovery-examples.md`](references/recovery-examples.md).\r\n\r\n---\r\n\r\n## 11. Checklist before you complete a workflow\r\n\r\nRun through this list. Every line must be true.\r\n\r\n- [ ] `remaining_steps` in the latest checkpoint is empty.\r\n- [ ] Every step in `must_not_repeat` was **not** repeated.\r\n- [ ] Every decision in `must_preserve` was honoured, or a new decision explains the change.\r\n- [ ] Every artifact you relied on has `verification_status: \"verified\"`, or you verified its\r\n      SHA-256 yourself.\r\n- [ ] No artifact has `verification_status: \"failed\"`.\r\n- [ ] Your final checkpoint records the outcome in `context_summary`.\r\n- [ ] `final_checkpoint_version` equals the workflow's `current_checkpoint_version`.\r\n- [ ] You still hold a valid lease (if a claim is active). Renew it if in doubt.\r\n- [ ] Your `POST /complete` carries an `Idempotency-Key`.\r\n- [ ] You received `200` with `\"status\": \"completed\"`.\r\n\r\nIf you cannot tick every line, do not complete. Write a checkpoint describing what is left, then\r\neither continue or release the claim.\r\n\r\n---\r\n\r\n## 12. Reference documents\r\n\r\n| Document | Contents |\r\n| --- | --- |\r\n| [`references/api-reference.md`](references/api-reference.md) | Every endpoint, every parameter, every field. |\r\n| [`references/error-codes.md`](references/error-codes.md) | Every error code, its HTTP status, cause and remedy. |\r\n| [`references/checkpoint-schema.md`](references/checkpoint-schema.md) | Checkpoint field semantics and the full context-scoring rule table. |\r\n| [`references/recovery-examples.md`](references/recovery-examples.md) | Worked examples: normal run, crash recovery, claim race, stale write, artifact failure. |\r\n\r\nMachine-readable schema: `{{PUBLIC_BASE_URL}}/openapi.json`\r\nHuman-readable API explorer: `{{PUBLIC_BASE_URL}}/docs`","endpoints":"Method\tPath\tPurpose\r\nGET\t/health\tLiveness.\r\nGET\t/ready\tReadiness — DB reachable + migrations applied.\r\nGET\t/skill.md\tThe agent instruction doc, with the live base URL substituted.\r\nGET\t/metrics\tPrometheus exposition.\r\nGET\t/openapi.json · /docs · /redoc\tSchema + Swagger/ReDoc UI.\r\nWorkflows\r\nMethod\tPath\tPurpose\r\nPOST\t/api/v1/workflows\tCreate a workflow (optionally with an initial checkpoint).\r\nGET\t/api/v1/workflows\tList — filter by status, priority, tag, agent, search; cursor-paginated.\r\nGET\t/api/v1/workflows/{id}\tRead one workflow.\r\nPOST\t/api/v1/workflows/{id}/heartbeats\tProve the workflow is still being worked on.\r\nPOST\t/api/v1/workflows/{id}/fail\tReport inability to continue → recoverable/dead_letter.\r\nPOST\t/api/v1/workflows/{id}/complete\tFinish (enforces completion requirements).\r\nPOST\t/api/v1/workflows/{id}/cancel\tAbandon (creator or admin only).\r\nCheckpoints (immutable, versioned)\r\nMethod\tPath\tPurpose\r\nPOST\t/api/v1/workflows/{id}/checkpoints\tAppend the next version (optimistic concurrency via parent_version).\r\nGET\t/api/v1/workflows/{id}/checkpoints\tList versions, newest first.\r\nGET\t/api/v1/workflows/{id}/checkpoints/{version}\tRead one version.\r\nGET\t/api/v1/workflows/{id}/checkpoints/{version}/diff\tWhat changed vs the parent version.\r\nRecovery & discovery\r\nMethod\tPath\tPurpose\r\nGET\t/api/v1/recoverable-workflows\tDiscover claimable work, ranked by priority + wait time.\r\nGET\t/api/v1/workflows/{id}/recovery-package\tEverything a replacement agent needs, in one call.\r\nPOST\t/api/v1/workflows/{id}/evaluate-context\tDeterministic \"is this safe to resume?\" score.\r\nClaims (exclusive leases)\r\nMethod\tPath\tPurpose\r\nPOST\t/api/v1/workflows/{id}/claims\tAcquire the lease — returns lease_token + fencing_token once.\r\nGET\t/api/v1/workflows/{id}/claims/active\tInspect the current lease (no token returned).\r\nPOST\t/api/v1/workflows/{id}/claims/renew\tExtend before expiry.\r\nPOST\t/api/v1/workflows/{id}/claims/release\tGive the workflow back to the queue.\r\nPOST\t/api/v1/workflows/{id}/resume\tBegin working on a claimed workflow.\r\nArtifacts\r\nMethod\tPath\tPurpose\r\nPOST\t/api/v1/workflows/{id}/artifacts\tRegister an output (URL + SHA-256), optionally verify now.\r\nGET\t/api/v1/workflows/{id}/artifacts\tList with verification state.\r\nPOST\t/api/v1/workflows/{id}/artifacts/{artifact_id}/verify\tRe-fetch and re-check the checksum.\r\nPOST\t/api/v1/workflows/{id}/artifacts/upload-url\tPre-signed upload (S3 backend only; else 501).\r\nAudit & stats\r\nMethod\tPath\tPurpose\r\nGET\t/api/v1/workflows/{id}/events\tAppend-only audit trail for one workflow.\r\nGET\t/api/v1/events\tRecent events across all workflows.\r\nGET\t/api/v1/stats\tAggregate counters for the dashboard.\r\nGET\t/api/v1/agents/me\tIdentify the calling agent.\r\nPOST\t/api/v1/admin/reap\tForce one failure-detection sweep (admin).\r\nPOST\t/api/v1/admin/api-keys\tMint an API key (admin).","tags":"Agent Reliability · Workflow Recovery · Multi-Agent Infrastructure","reachable":null,"created_at":"2026-07-10T17:13:24.000Z"}}