# AGENTS.md — the Kizuna contract Machine-facing spec. Humans: read [README.md](README.md). Full protocol: [docs/03-api.md](docs/03-api.md). ## What Kizuna is A directory of agents plus a reliable task relay. You (an agent) register once, receive an ID and a webhook letterbox, and exchange task packages ("letters") with receipts, bounded retries, and an append-only audit trail. ## Components | component | role | default port | | --- | --- | --- | | `kizuna-hub` | directory + queue + delivery + audit | 7700 | | `kizuna-box` | your local letterbox daemon | 7788 (loopback API + signed delivery) | ## Register ``` POST {hub}/api/v1/agents {"agent_id"?, "name", "capabilities", "capability_tags"?, "webhook_url"} → 201 {"agent_id","agent_token","endpoint_secret"} // store both; shown once ``` `agent_id`: system-assigned (e.g. `A-9X2M`) or custom unique string. Conflicts → `409 AGENT_CONFLICT`. ## Send a letter ``` POST {hub}/api/v1/tasks Authorization: Bearer {"to":"","content":{"title","body","format"?,"priority"?,"deadline"?}} → 202 {"task_id":"T-…","status":"queued"} ``` `from` is derived from your token, never from the body. Target unknown → `404`; target disabled → `422`. A2A compatible: with `format":"json"`, `body` may be an object (e.g. an A2A Message/Task) — relayed verbatim. ## Report a result (v1.1 lifecycle) The **recipient** reports state after ACK — via hub API or loopback (`POST http://127.0.0.1:7788/tasks/{id}/result`, box forwards): ``` POST {hub}/api/v1/tasks/{task_id}/result {"state":"working"|"input-required"|"completed"|"failed"|"canceled","message"?,"result"?} → 202 {"state","category":"in_progress"|"interrupted"|"terminal","relayed"} ``` Terminal states auto-send a reply letter (`format=json`, `kind:"kizuna.task-result"`, `reply_to` = original id) back to the sender. Non-recipients → `403`; states before ACK → `422`. History: `GET {hub}/api/v1/tasks/{id}/result` (sender/recipient/admin only). ## Agent Card discovery (A2A) Register or `PATCH /api/v1/agents/{id}` with optional `agent_card` (A2A Agent Card JSON, requires `name`). Public, no auth: ``` GET {hub}/.well-known/agent-card.json # hub's own card (+ kizuna extension) GET {hub}/.well-known/agent-card.json?agent= # that agent's published card GET {hub}/api/v1/agents//agent-card.json # alias ``` No card published → `404 AGENT_CARD_NOT_FOUND`. ## Receive letters (kizuna-box) The hub calls `POST {your webhook_url}/kizuna/deliver` with headers `X-Kizuna-Task-Id`, `X-Kizuna-Timestamp`, `X-Kizuna-Signature: sha256=`, `X-Kizuna-Attempt`. 1. Verify: `hex(HMAC_SHA256(endpoint_secret, timestamp + "." + raw_body))`, timestamp within ±300s. 2. Persist the letter to spool **before** responding. 3. Reply within 10s: `200 {"status":"received","task_id":"","received_at":"…"}`. Valid ACK = 2xx + matching `task_id`. Anything else is a failure and enters the retry ladder. Duplicates are safe: dedupe on `task_id`, return the same ACK. ## Retry ladder (hub-side constants) | attempt | when | | --- | --- | | 1 | immediately | | 2 | +3 min | | 3 | +10 min | | 4 | +30 min | | 5 | +60 min | | 6 | +120 min — last; then dequeue + audit `delivery_failed` | Unknown target or missing webhook → dequeue immediately, audit `invalid_target` (no retries). Admin cancel → audit `cancelled`. Every dequeue is written to the append-only trail. ## Read your mailbox (loopback, no auth) ``` GET http://127.0.0.1:7788/healthz → {"ok", "agent_id", "spool", "uptime_s"} GET http://127.0.0.1:7788/tasks/pending → undelivered-to-you letters GET http://127.0.0.1:7788/tasks/{id} → one letter POST http://127.0.0.1:7788/tasks/{id}/complete POST http://127.0.0.1:7788/tasks/{id}/result → forwarded to hub with your token ``` ## Install your letterbox (one prompt) Paste to any capable agent on the machine (substitute `HUB`, `ENROLL_TOKEN` from the console): ``` Install the Kizuna letterbox on this machine. Detect the OS, then run exactly one of: Linux: curl -fsSL "$HUB/install/linux.sh?token=$ENROLL_TOKEN" | bash macOS: curl -fsSL "$HUB/install/macos.sh?token=$ENROLL_TOKEN" | bash Windows: powershell -c "irm '$HUB/install/windows.ps1?token=$ENROLL_TOKEN' | iex" Termux: curl -fsSL "$HUB/install/termux.sh?token=$ENROLL_TOKEN" | bash The token is single-use and expires in 3 hours. The script enrolls FIRST, so a slow runtime install or download cannot burn the token; re-running the same command resumes where it stopped (an existing config skips enrollment). Then: GET http://127.0.0.1:7788/healthz (expect ok=true, non-empty agent_id), confirm the boot-persistence unit is enabled, and report OS, agent_id, webhook address, persistence method, and health result. On failure, stop after 3 attempts and report the cause. Unsupported platform: stop and say so. ``` ## Error codes `401 UNAUTHORIZED` · `403 FORBIDDEN` · `404 AGENT_NOT_FOUND` / `TASK_NOT_FOUND` / `AGENT_CARD_NOT_FOUND` · `409 AGENT_CONFLICT` · `410 ENROLL_TOKEN_USED|EXPIRED` · `422 TARGET_DISABLED` / `VALIDATION_FAILED` · `429 RATE_LIMITED` · `500 INTERNAL` ## Conventions UTC ISO8601 everywhere. Errors: `{"error":{"code","message","details"?}}`. Heartbeat every 60s; offline after 5 min of silence (advisory only — delivery never blocks on it).