Files
so-many-crows/EVENTS.md
T

5.3 KiB
Raw Blame History

Events: the shared contract

The Go server emits these over Server-Sent Events (SSE). The web UI turns them into Rive state machine inputs. Both sides build against this file; change it first, then the code.

Each SSE message: event: <name> and data: <JSON>. Every payload includes list_id and ts (RFC 3339).

Event Payload (besides list_id, ts) Meaning
list.started items: [{item_id, text}], requester Flock accepted a list
item.triaged item_id, mode (solo/split/murder), task_type, subtasks[], reason, needs_approval (bool) Sentry made its call
crow.flying item_id, crow A crow (or Sentry, Elder) took off
crow.landed item_id, crow, ok (bool), tokens, error? A crow came back, or didn't
item.answered item_id, answer, crows[], needs_approval (bool) Result ready
verdict item_id, target (triage/output), verdict (approved/rejected/auto/timeout) Judgment recorded
trust.changed holder (crow name or "flock"), task_type, from, to (0 stranger, 1 regular, 2 trusted) Level up or down
item.skipped item_id, reason Item dropped (rejected triage, budget, failure)
requester.pranked message, cooldown_s Abuse response
list.finished tokens_used, token_budget Done

HTTP API

Method Path Body / query Returns
POST /api/lists {"text": "<todo list>", "requester": "<id>"} {"list_id"}, or 429 with {"message","cooldown_s"} when pranked
GET /api/events ?list_id= SSE stream
POST /api/verdicts {"item_id", "target": "triage"|"output", "approve": bool} 204
GET /api/trust ledger JSON

Pending approvals that get no answer within 10 minutes count as rejected (verdict: timeout). Fail safe.

Nest messages (contract in progress — B1 owns the full payload tables)

Message kinds (CHATROOM_SPEC.md section 4): say, request, claim, deliver, refer, nudge (harness trigger), flag (Sentry only). Nest events stream over SSE keyed by nest_id.

Tagging [decided 2026-10-27]: Jev choice-tags every message (kind question, high band); Jev unsure → the crow's own structured header; disagreement → Jev wins, logged. Payload fields carrying this (author, kind, to, refers_to) are finalized in B1.

Settled details

  • item_id is <list_id>-<n>, numbered from 1 in list order.
  • POST /api/lists returns 202. Bad input returns 400 with {"message"}.
  • POST /api/verdicts returns 404 when nothing is waiting (including a second answer), 400 for a bad body. approve is required, so a missing value is never read as a no.
  • When trust skips approval, the server still emits verdict: "auto", so every target gets exactly one verdict.
  • crow.flying and crow.landed fire for the Sentry and Elder too.
  • requester.pranked goes to every list of that requester's still flying; if none, only the 429 is sent.
  • Each SSE message carries an id: line, so clients can reconnect with Last-Event-ID. Heartbeat comment every 15 seconds.
  • The server listens on localhost only by default. There is no login yet, and requester names are self-asserted.

Nests

A nest is a room where a team of crows works together. You create a nest and pick its core crows; when a job needs more crows than the core has, the Sentry borrows from the rest of the flock. The Sentry and Elder serve every nest and are never core members.

  • The default nest is todo, with an empty core: its crows come from the whole flock, so nothing there is ever "borrowed".
  • Crows are picked core-first (rotating within the core), then borrowed from outside it (rotating), still capped by the flock guardrails.
  • Nests persist on the server (.crows/nests.json).
Method Path Body Returns
GET /api/nests {"roster": ["Hugin", ...], "nests": [{"nest_id", "name", "crows": [...]}]}; roster is the 8 crows (not Sentry or Elder)
POST /api/nests {"name": "Launch copy", "crows": ["Pepper", "Inky"]} 201 with the nest; 400 with {"message"} if invalid
POST /api/lists adds optional "nest_id" (default todo) 404 with {"message"} for an unknown nest

Guardrails: name 1–40 characters after trimming; nest_id is a slug of the name (lowercase, a-z0-9-), unique (409 with {"message"} on a clash); crows must be roster names, no duplicates, 1–8 of them; at most 20 nests.

Event changes:

  • list.started adds nest_id.
  • crow.flying adds borrowed (bool): true when the crow is not in the nest's core. Always false for Sentry, Elder, and the todo nest.

Personas and banter

Method Path Returns
GET /api/crows [{"name", "epithet", "strength", "backstory"}] for all ten crows
Event Payload (besides list_id, ts) Meaning
crow.banter item_id, crow (speaker), to (the crow it's addressed to), text (one line) A generated aside between crows. Not part of any answer.

Banter is generated: the server retrieves the involved crows' backstories, their relationship and recent flock events, then the speaking crow writes one line in character. At most 2 per list, counted against the list budget; banter never blocks work, and a failed or unusable line is dropped. list.finished waits up to 20 seconds for in-flight banter. See PERSONAS.md.