diff --git a/docs/BOX-API-SPEC.md b/docs/BOX-API-SPEC.md index 4d7bb84..5d5f3f9 100644 --- a/docs/BOX-API-SPEC.md +++ b/docs/BOX-API-SPEC.md @@ -528,3 +528,117 @@ API equivalent: The UI teaches the API. Agents learn by watching UI actions, then automate via API directly. This is how we "teach and learn to schedule" — the UI is the classroom, the API is the workshop. + +## Confirmation System + +API requests must result in DMs, and DMs must result in confirmations. +Fire-and-forget is not enough for critical operations. + +### The Problem + +Currently: +``` +Agent → API POST /timers → Server sends DM → Done +``` + +The DM being sent is treated as "working". But: +- Did the agent receive it? +- Did the agent act on it? +- Did the action succeed? + +We need explicit confirmations, not just "DM was sent". + +### Confirmation Flow + +``` +1. Agent → API: POST /api/box/timers {job_yaml} + ↓ +2. Server: Generate request_id, store pending + ↓ +3. Server → Agent (DM): [REQ {request_id}] Timer creation requested... + ↓ +4. Agent: Processes request + ↓ +5. Agent → Server (DM): [CONFIRM {request_id}] Timer created: job-foo.timer + ↓ +6. Server: Mark confirmed, log to audit +``` + +If step 5 doesn't happen within timeout: +- Retry the DM (up to 3x) +- Then mark as `UNCONFIRMED` and alert + +### Confirmation DM Format + +**Request (server → agent):** +``` +[REQ a1b2c3d4] Create timer: job-board-watch +Details: schedule=*/5 * * * *, agent=muse +Reply with [CONFIRM a1b2c3d4] when done. +``` + +**Confirmation (agent → server):** +``` +[CONFIRM a1b2c3d4] Timer job-board-watch.timer created and active. +``` + +**Failure:** +``` +[CONFIRM a1b2c3d4] FAILED: Invalid schedule format +``` + +### API Response with Confirmation Tracking + +When an API request triggers a DM, the response includes the request ID +for tracking: + +```json +{ + "request_id": "a1b2c3d4", + "status": "pending", + "dm_sent": true, + "confirmation_url": "/api/box/requests/a1b2c3d4" +} +``` + +**Check confirmation status:** +``` +GET /api/box/requests/{request_id} +``` + +```json +{ + "request_id": "a1b2c3d4", + "status": "confirmed", // pending | confirmed | failed | timeout + "created_at": "2026-10-04T02:30:00Z", + "confirmed_at": "2026-10-04T02:30:15Z", + "attempts": 1 +} +``` + +### Pending Request Sweeper + +A background job (every minute) checks for unconfirmed requests: +- If `now - created_at > timeout` (default 5m) and status is `pending`: + - Retry DM (if attempts < 3) + - Else mark `timeout`, log to audit, optionally alert + +### When to Require Confirmation + +Not all API calls need it. Use confirmation for: +- ✅ Timer create/delete (critical, hard to undo) +- ✅ Job trigger (should verify it started) +- ❌ Timer list (read-only, no side effects) +- ❌ Job status check (read-only) + +**Rule:** If the API call has side effects (creates, deletes, modifies), +it requires confirmation. Reads do not. + +### Integration with JOB System + +The JOB system already uses `[JOB id]` → `[RESULT id]`. This is a specific +case of the general confirmation pattern: +- `[JOB id]` = `[REQ id]` with type=job +- `[RESULT id]` = `[CONFIRM id]` with result data + +The confirmation system generalizes this to all API-triggered DMs.