# So Many Crows: handoff for the next agent Read this whole file before changing anything. It is the single source of truth for where the project stands, why it is built this way, and what comes next. Last updated 2026-10-27 by the agent that built v1 through personas. ## 1. What this is So Many Crows is a multi-agent app for one owner, Smokey (Jacob Madsen). You hand a "flock" of AI agents ("crows") a todo list in a chat-room web UI. A **Sentry** crow triages each item, worker crows do the work, an **Elder** crow merges multi-crow results, and **the human approves or rejects** until crows earn trust. It belongs to So Many Crows LLC, a Utah LLC Smokey is registering that builds marketing communications (marcomm) tools. Working copy: `~/Projects/so-many-crows` on Smokey's Mac (Apple silicon, macOS). There is **no Git remote yet** (see section 8). Do not confuse it with `~/Projects/crows`, an unrelated older folder: never touch it. ## 2. How to work with Smokey These are his stated preferences. Follow them every time. - **Direct and brief.** No flattery, no padding. Dry wit is welcome. - **One question at a time.** Never stack questions. - **Don't guess.** If unsure, look it up or ask. Back factual claims with sources (links). - **Incremental, RITE-style** (rapid iterative testing and evaluation): smallest working step, show it, adjust. **Don't add complexity until he asks.** - **AP style for acronyms:** spell out on first use with the acronym in parentheses, then the acronym alone. - **Guardrails first.** No capability (web, files, APIs, new providers) is enabled until its guardrails are defined. This is the project's founding rule. - **Never print, log or paste secrets.** When inspecting `.env`, show names and lengths only. - He reviews visually. When you change the UI, render and look at it (both desktop width and about 400 pixels wide) before reporting. ## 3. Decisions made, and why ### Product model - **Crows answer alone or work a list together as a "murder."** The Sentry classifies each item as `solo` (one crow), `split` (2 to 4 subtasks in parallel, never re-split) or `murder` (3 crows answer independently, then the Elder reconciles). - **Autonomy is earned, like befriending wild crows.** Trust ladder per crow *and* per task type: 0 stranger (human approves triage and output), 1 regular (output auto-accepted, marked unreviewed), 2 trusted (runs alone). Promotion after 10 clean approvals in a row (the flock as a whole needs 20); one rejection drops a level. A crow's effective level blends its own record with the flock's, so new crows start on the flock's reputation. The Sentry earns triage trust separately (task types `triage/`). The least-trusted crow on a job sets the approval bar. - **Abuse gets a mild, fading prank.** Flooding (more than 5 lists a minute per requester) earns a cooldown (30 seconds, doubling per strike, max 1 hour), a prank message ("a crow left a gift on your windshield"), and strikes fade one per clean day. Never destructive, never public. - **Nests** are rooms where a team of crows works together. Smokey creates a nest and picks its core crows; when a job needs more crows than the core, the Sentry **borrows** from the rest of the flock (labeled "borrowed"). The default nest `todo` uses the whole flock. - **Personas.** Ten crows with backstory, a prompt policy (how they approach work) and an output policy (what answers look like). Hugin (Thought) and Munin (Memory) are grounded in Norse sources: Grímnismál stanza 20 and Snorri's Gylfaginning 38. The Elder sits "where Odin sat," hearing reports. Personality affects voice and approach only, never facts or guardrails. Full sheet: `PERSONAS.md`, generated from `personas.go`. - **Banter is generated, not scripted** (Smokey's explicit choice): a retrieval-then-inference loop. On a moment (a rejection, a level-up, rival crows on one job), the server retrieves the involved crows' backstories, their relationship and recent flock events from a short shared memory, then the speaking crow's model writes one line in character. Capped at 2 per list, counted against the list's token budget, never blocks work, and lines are validated (one line, at most 220 characters, no links) or dropped. ### Models and providers - **The main LLM is switchable** via `CROWS_PROVIDER`: `openrouter` (default model `openrouter/free`, a router that picks a different free model per call) or `anthropic` (default `claude-sonnet-5-5`). Every flight logs which model actually answered. - **Jev (TypeSafe) is a classifier, run in tandem with the LLM** for the Sentry's triage, called **directly from TypeSafe** (`https://api.typesafe.ai/v1/systemone`), not via OpenRouter. Jev answers mode and task type with calibrated confidence. Its call stands when both confidences are at least 0.7 and the mode is not `split`. Otherwise the LLM Sentry decides: when Jev is unsure, when it calls a split (Jev can't write subtasks), or when Jev errors. Jev never blocks a list. - **Smokey's comparison plan:** a free model first, then Claude over OpenCode Zen, then Claude direct from Anthropic. Status: OpenRouter free works (live-tested). Zen is **not built**: Zen's own docs say its free models only work inside the OpenCode app, so only paid Zen models can be used from this app. Claude direct is **built but never live-tested** (no Anthropic key has been provided). - **OpenRouter free tier limits** (key is on the free tier): 50 free-model requests per day, 20 per minute. Each todo item costs about 2 to 6 requests (Sentry, crows, Elder, banter). Jev triage saves one request per confidently-triaged item. ### Stack - **Go backend, standard library only**, Go 1.24 (`go.mod`, module `somanycrows`). Smokey's team prefers Go or Rust, hosted on Firebase or Google Cloud (GCP) later. - **Web front end: plain TypeScript with Vite**, no framework. Smokey dislikes Metro and Expo; web first, native later via Rive's native runtimes if needed. - **Server-Sent Events (SSE)** stream every event to the browser. - **Rive** is the planned animation layer, using **data binding** (view models), since Rive's docs mark state machine inputs as deprecated. The `.riv` file does not exist yet; an SVG stand-in implements the same interface. ### Art direction (hard-won; follow exactly) - **Pen and ink on cream paper, with stipple shading.** Reference: a Ryan LeCount flash sheet Smokey shared. - **Never redraw Smokey's crow** (`art/crow.svg`: disc, beak, head with eye cutout, jaw). He rejected a redraw. Keep its silhouette; animate the jaw for "caw." - **No outlines on boxes** (cards, text box, buttons, toasts, dialogs). Shape comes from fill plus a **stippled drop shadow**. No dashed lines anywhere. Stippled drop shadows are approved. - **Stipple must be subtle and irregular:** a generated tile of fine random dots (fixed seed) layered at 64 and 47 pixels so it never visibly repeats. Never a regular dot grid (reads as halftone). - **Crows never change size** in any animation: motion only (hop, tilt, spin, bob). - **Sparkles (four-point star plus broken cross) mean celebration only** (level-up). Waiting is a plain "?". Dazed crows fade in place, with no circling stars (Smokey: "looks like they're drunk"). - Type: IM Fell English SC (headings) and EB Garamond (text). Colors: paper `#f4eedf`, card `#f9f5ea`, ink `#1c1915`, muted `#5f574b`, red `#9b2f22`. Disc colors (Smokey's art): mint for crows, yellow for the Sentry, gray for the Elder. - **Layout is a chat platform** (like Slack or Teams): sidebar with Nests (like channels) and The flock (one list tile per crow showing its latest update, time, and a red "N waiting" count, newest on top); a channel feed where the Sentry and crows post; inline approve and reject buttons; verdicts shown as reactions; composer at the bottom. On phones: two tabs (the current nest, and Flock), with no crow strip across the top. - **Spacing is generous** ("spread it out a little"). - Behavior is Duolingo-like (expressive characters, streaks and level-ups); look is pen and ink. ### Safety and security - Server listens on **127.0.0.1 only** by default. There is no login; requester names are self-asserted (a pranked user can rename to dodge a cooldown). - Approvals are **fail-safe**: anything but an explicit yes is a no; unanswered approvals time out as rejected after 10 minutes. - Server text is only ever inserted into the page as text, never as HTML. - `.env` (mode 600) holds keys, is git-ignored, and is read line by line by `start.command`, never executed. ## 4. Repository map | Path | What it is | |---|---| | `main.go` | Command line: `run`, `serve`, `trust`, `log`, `personas` | | `server.go`, `hub.go`, `approve.go` | HTTP API, SSE hub with per-list replay, web and stdin approvers | | `pipeline.go` | The shared run loop for command line and server; emits events; banter moments | | `flock.go` | Crows: picking (core-first, then borrow), triage, execute, reconcile, guarded `ask` | | `brain.go` | Model clients: `AnthropicBrain`, `ChatBrain` (OpenAI-style, used for OpenRouter), `MockBrain` | | `jev.go` | Jev classifier client and the guarded `classify` call | | `personas.go`, `banter.go` | Personas and policies; retrieval-then-generation banter | | `ledger.go`, `guardrails.go`, `nests.go`, `flightlog.go` | Trust ledger and requester strikes; limits; nests; JSON Lines flight log | | `*_test.go` | 26 tests; run with `-race` | | `EVENTS.md` | **The server and UI contract**: every event, payload and endpoint. Change it first, then the code. | | `RIVE_CONTRACT.md` | Spec for the animator's `crows.riv` (data binding, one `Crow` artboard for v1) | | `PERSONAS.md` | Generated persona sheet. Regenerate with `./crows personas > PERSONAS.md` after editing `personas.go` | | `ui/` | Vite + TypeScript source (`src/main.ts`, `crow.ts`, `api.ts`, `style.css`). Builds into `web/` (git-ignored) | | `art/crow.svg` | Smokey's crow art. Do not alter. | | `start.command` | Double-click launcher: reads `.env`, opens the browser, runs `./crows serve` | | `dist/` | Git-ignored build drops (`crows-darwin-arm64`, `crows-linux-arm64`, `update.tgz`) | | `.crows/` | Git-ignored runtime state: `ledger.json`, `nests.json`, `flight.jsonl` | ## 5. How to build, test and run ``` go version # need Go 1.24+; if missing: brew install go go vet ./... && go test -race ./... go build -o crows . # Mac binary (start.command expects ./crows) cd ui && npm install && npm run build && cd .. # builds the UI into web/ ./start.command # or double-click it; opens http://127.0.0.1:8080 ./start.command --mock # offline mock crows: no keys, no cost ./crows run todo.txt # command-line run (stdin approvals; banter not printed yet) ./crows log -n 30 # recent flight log; each flight shows model, tokens, ms ./crows trust # trust ledger ``` UI development: run the server, then `cd ui && npm run dev` (Vite proxies `/api` to port 8080). Delete `.crows/` to reset state (it holds trust and nests). `.env` keys (names only; never print values): `OPENROUTER_API_KEY` (present), `TYPESAFE_API_KEY` (present), optional `ANTHROPIC_API_KEY`, `CROWS_PROVIDER`, `CROWS_MODEL`, `CROWS_JEV_MODEL` (default `jev-latest`), `CROWS_JEV_THRESHOLD` (default 0.7). Inspect safely with: `sed -n 's/^\([A-Z_]*\)=.*/\1/p' .env`. Guardrail defaults (`guardrails.go`): 5 crows in flight, murder of 3, 20 items per list, 2,000 characters per item, 4 subtasks, 20,000 tokens per item, 100,000 per list, 60-second crow timeout, promote after 10, 5 requests per minute, cooldown 30 seconds doubling to 1 hour, 2 banter calls per list. ## 6. Verified state (2026-10-27) - All 26 tests pass with `-race`. - Live test with real keys: Jev triaged "Should we launch a CLI or a web app first" as murder (91%) and analysis (100%) in 195 ms; Hugin, Munin and Corvina answered on three different free models; the Elder merged in character; after a rejection, Soot generated: *"Hugin, another grand scheme foiled? Perhaps the CLI is the tiny bird worth chasing."* Total 6,257 tokens. - UI end-to-end tested headless at desktop and phone widths: nests, borrowing, approvals, reactions, prank cooldown, banter asides. No console errors. - **Not yet done:** Smokey has not yet used the app himself through `start.command` with real crows. That is the next thing to support. ## 7. Known gaps and gotchas - **The command line doesn't print banter.** The web UI and flight log show it; `crows run` ignores the `crow.banter` event. - **Claude-direct path is untested live.** It is unit-tested only. - **Free-model quality is uneven.** In testing, one free model swapped crow and raven examples in a table. That is what the approval gate is for. - **No login and self-asserted requester names** (see section 3). - **"Regular" trust is half-built:** output is marked unreviewed, but there is no command or view to review it later. - **Killing the server:** never use `pkill -f "crows serve"` from a shell whose own command line contains that text; it kills the shell. Find the process ID first (`pgrep -x crows`) or use Ctrl-C in its window. - **Deletes may be blocked** in some sandboxed shells. Git objects are read-only, so re-extracting an archive over `.git` prints harmless "Permission denied" lines. - `~/Projects/so-many-crows-mac.tgz` is an old installer archive. Safe for Smokey to delete; don't delete it yourself without asking. ## 8. What happens next (in order) Do one at a time. Show the result. Ask before starting the next. 1. **Support Smokey's first hands-on session.** He starts `start.command` and uses the app with real crows. Watch the Terminal window and `./crows log` for errors, and fix what he hits. Keep an eye on the 50-a-day free limit. 2. **Print banter on the command line.** Small: handle `crow.banter` in the command line's printing emitter (`main.go`). 3. **Provider comparison.** Smokey wants to compare (a) a free model, (b) Claude over OpenCode Zen, (c) Claude direct from Anthropic. Steps: pin one model per run with `CROWS_MODEL` for fair comparisons (the free router randomizes); add an OpenCode Zen provider to `NewBrain` (base `https://opencode.ai/inference/`, Claude models via `anthropic/v1/messages`, open models via `openai/v1/chat/completions`; confirm the auth header in Zen's docs first, since they didn't state it for these endpoints); get keys from Smokey; then add a small `crows stats` summary of the flight log (per model: calls, average tokens, average milliseconds, approval and rejection rates). Ask Smokey which model to pin before running. 4. **Review queue for "regular" trust:** a way to review outputs that were auto-accepted while unreviewed. 5. **Rive.** An animator builds `crows.riv` per `RIVE_CONTRACT.md`; then add a Rive-backed `CrowView` in `ui/src/crow.ts` (the interface is already shaped for it). Smokey's crow art is the character. 6. **Threads per item** (Slack-style): asked of Smokey, not answered yet. Ask before building. 7. **Persona strengths in crow picking** (for example, Munin for retrieval). Not requested; propose only. 8. **First real capability (likely web search):** write its guardrail spec first (scope, limits, what needs approval), get Smokey's approval, then build. 9. **Login and real requester identity** before the server ever leaves localhost. 10. **GitHub:** Smokey deferred it. When he's ready: he creates an empty repo `jacisjake/so-many-crows`, then push. Commit email must be `150986568+jacisjake@users.noreply.github.com` (pushes with his real email are rejected). 11. **Hosting on Google Cloud** later; not started. ## 9. Conventions - **Contracts first:** change `EVENTS.md` (or `RIVE_CONTRACT.md`) before code on either side of it. - **Every model call goes through `Flock.ask` or `Flock.classify`** so budgets, the 5-crow cap, timeouts and the flight log always apply. Never call a brain directly. - **Tests:** add tests for every new path, including failure paths. Run `gofmt -l .`, `go vet ./...`, `go test -race ./...` before committing. - **Commits:** small, descriptive messages. Never commit `.env`, `.crows/`, `web/`, `dist/`, binaries or screenshots. - **UI:** server data goes into the page with `textContent`, never `innerHTML`. Test at about 400 pixels wide and at desktop width, and look at screenshots before reporting.