Files
so-many-crows/CLAUDE.md
T

17 KiB

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: same day, the roadmap pivoted toward the chatroom spec (section 8) — the pivot came from CHATROOM_SPEC.md, GROKBOT_NOTES.md and JEV_STRATEGIES.md.

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.

That pipeline is what runs today, but the direction has changed (2026-10-27): the target is a conversation-driven chatroom where crows claim work in a nest, two new roles appear (assistant crow, Chief of Staff crow), and Jev audits expectations and nudges stalled work. Read CHATROOM_SPEC.md (the design, with [decided]/[proposed] marks), GROKBOT_NOTES.md (its evidence base) and JEV_STRATEGIES.md (what the classifier can do). The pipeline isn't deleted — it stays the working app until chatroom work replaces it, and nests, the trust ledger, guardrails, SSE, personas and the flight log all carry over.

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/<type>). 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 limits (corrected 2026-10-27: the account is paid, $50 in credits bought, is_free_tier: false): 1,000 free-model requests per day, 20 per minute (https://openrouter.ai/docs/api-reference/limits). Check live with GET /api/v1/key (never print the label or key). Pinned model: CROWS_MODEL=google/gemma-4-26b-a4b-it:free (switched from gemma-4-31b-it:free after upstream 429s); keep it until Smokey says otherwise. 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 31 tests; run with -race
EVENTS.md The server and UI contract: every event, payload and endpoint. Change it first, then the code.
CHATROOM_SPEC.md The pivot target: the chatroom design (message kinds, roles, expectation ledger, build plan).
GROKBOT_NOTES.md Black-box observations of Grok Bot that the spec is built from.
JEV_STRATEGIES.md What Jev can do (question types, bands, calibration) and strategies for using it.
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 31 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. It still matters (section 8, step 6), but the chatroom work leads now.

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)

The roadmap pivoted (2026-10-27): the chatroom spec is now the direction. Do one step at a time. Show the result. Ask before starting the next. Contracts first still applies: change EVENTS.md before UI or server code for any chatroom message kind or event.

  1. Resolve the spec's open blockers before code (Smokey decides, not the agent): message-kind tagging (Jev can tag it — see JEV_STRATEGIES.md strategy 1), the nudge loop guard (nudges count against the nest budget; a miss must not restart work forever), first-claim-wins arbitration when two crows claim at once, and Sentry/Elder's fate (spec open question 4).
  2. Chatroom build plan step 1 (CHATROOM_SPEC.md section 12): one nest, owner plus two crows, event-driven; say, request, claim, deliver with @-addressing; mock brain first, live models after it works.
  3. The assistant crow as the owner's default chat, relaying into the nest.
  4. The expectation ledger plus nudges, two checks first (promise kept, single owner), Jev questions per JEV_STRATEGIES.md. The "log-only experiment" there (kind + who-responds, no acting) can run beside current pipeline traffic to collect calibration data.
  5. The COS crow spinning up crows on request. Then opt-in debate mode, then visible budgets.
  6. Smokey's first hands-on session with the current pipeline stays worth supporting — if he runs start.command, watch the Terminal and ./crows log and fix what he hits (1,000-a-day free-model limit).

Paused by the pivot (resume only if Smokey asks): provider comparison beyond pinning a model (the pinned CROWS_MODEL stays); CLI banter printing; review queue for "regular" trust; threads; Rive; web-search capability; login; GitHub; GCP. GitHub note for when it happens: Smokey creates an empty repo jacisjake/so-many-crows, commit email must be 150986568+jacisjake@users.noreply.github.com.

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.