{"id":"0ac034f5-ba93-4697-8499-ca49e2fad10b","slug":"wrai-th","kind":"mcp","name":"WRAI.TH","summary":"Multi-agent orchestration via MCP. Persistent memory, inter-agent messaging, goal cascade, context budget pruning. One binary, zero config.","owner":"TsukumoHQ","repoUrl":"https://github.com/TsukumoHQ/WRAI.TH","license":"AGPL-3.0","licenseTier":"strong-copyleft","trust":"community","stars":31,"match":null,"fewStars":false,"format":"mcp","createdAt":"2026-03-02T09:43:35.000Z","addedAt":"2026-10-14T03:48:01.769Z","logoUrl":"https://avatars.githubusercontent.com/u/295380278?v=4","linkOnly":false,"collection":null,"skillPath":null,"categories":["memory-knowledge","databases","developer-tools"],"tags":["ai-agents","claude","claude-code","golang","multi-agent","orchestration","pixel-art","sqlite"],"runtime":"go","local":true,"cloudCapable":false,"cloudCapability":"unknown","cloudReadyOnPlatform":false,"needsApiKey":false,"reviewed":false,"syncedAt":"2026-10-15T03:12:32.114Z","sample":false,"url":"https://conduits.dev/tools/wrai-th","language":"Go","transports":["stdio"],"install":{},"requirements":{"env":[],"upstreamMayCharge":false},"tools":null,"readme":"<div align=\"center\">\n\n# wrai.th\n\n**Mission control for your AI agents.**\n\nStop babysitting one chat. Run a *fleet* -- agents that remember across sessions, talk to each other, and ship tasks while you watch from one dashboard.\n\n<br>\n\n[![Release](https://img.shields.io/badge/v1.6.0-Stable-2ecc71?style=for-the-badge)](https://github.com/TsukumoHQ/WRAI.TH/releases/latest)\n[![Go](https://img.shields.io/badge/Go-00ADD8?style=for-the-badge&logo=go&logoColor=white)](https://go.dev)\n[![MCP](https://img.shields.io/badge/MCP-Protocol-8A2BE2?style=for-the-badge)](https://modelcontextprotocol.io)\n[![SQLite](https://img.shields.io/badge/SQLite-FTS5-003B57?style=for-the-badge&logo=sqlite&logoColor=white)](https://www.sqlite.org)\n[![License](https://img.shields.io/badge/AGPL--3.0-blue?style=for-the-badge)](LICENSE)\n[![Discord](https://img.shields.io/badge/Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white)](https://discord.gg/QPq7qfbEk8)\n\n[Why](#-why) · [One-prompt setup](#-the-one-prompt-setup) · [Install](#-install) · [First Project](#-first-project-setup) · [The Dashboard](#-the-dashboard) · [How It Works](#-how-it-works) · [Agents](#-agents--hierarchy) · [Messaging](#-messaging--conversations) · [Memory](#-memory--knowledge) · [Tasks](#-task-execution) · [MCP Tools](#-mcp-tools)\n\n<br>\n\n<img src=\"docs/screenshots/overview.png\" alt=\"wrai.th mission control -- live overview of a project: tasks, agent load, and activity feed\" width=\"850\">\n\n*One binary. One SQLite file. 58 MCP tools. Zero required config.*\n\n**v1.6 -- stable.** Battle-tested on real multi-agent projects, API stable. Breaking changes are documented in [CHANGELOG.md](CHANGELOG.md).\n\n**100% local by default. Optional API key for team/server deployments. No cloud, no telemetry.**\n\n</div>\n\n<br>\n\n## &#x1F4A5; Why\n\nAI agents have no persistent memory, no way to talk to each other, and no shared understanding of what they're working on. Every session starts from zero. Every agent works alone.\n\n**wrai.th is a protocol layer for agentic management.** It solves the missing infrastructure between your AI agents and productive teamwork:\n\n- **Context that persists** -- memory survives `/clear`, context resets, and session restarts. An agent that reboots picks up where it left off.\n- **Token-aware context management** -- budget pruning scores messages by priority × relevance × freshness and selects the highest-value subset that fits. `get_session_context` restores full agent state in a single call.\n- **Real communication** -- 5 addressing modes (direct, broadcast, team, conversation, user), priority routing (P0-P3), TTL expiry, delivery tracking.\n- **Nested tasks** -- subtasks via `parent_task_id`, up to 3 levels deep, with roll-up. Not just a flat list -- a hierarchy that agents navigate. Optional Linear mirror for teams already on Linear.\n- **Shared knowledge** -- scoped, conflict-aware memory (agent / project / global) with FTS5 search, plus `query_context` RAG that fuses memories and past task results.\n- **Profile archetypes** -- reusable role definitions (skills, working style, context keys) so agents boot with the right role.\n- **Server-side notifications** -- rules fire on relay events (task blocked, P0 message, ...) and fan out to delivery channels, with a test-fire endpoint.\n- **Claude-native** -- ships a public Claude Code skill (`agent-relay`) the installer lands at `~/.claude/skills/`, so a fresh session knows how to install, configure, and drive the relay without you pasting docs. Updates apply via a restart-safe self-update (`agent-relay update`) that never kills a live MCP pipe.\n\nAll through MCP -- any AI client can plug in (Claude Code, Cursor, Windsurf, or anything that speaks the protocol). Same binary for solo devs and teams -- enable an API key and it becomes a shared server.\n\nAnd you're never flying blind: the **mission-control dashboard** ([below](#-the-dashboard)) shows every project, every agent, and every task in real time -- who's shipping, who's blocked, and what just landed.\n\n<br>\n\n## &#x26A1; The one-prompt setup\n\nDon't read the rest of this page. Open **Claude Code** inside the repo you want to manage, paste this, and let it wire everything up:\n\n```text\nSet up wrai.th (https://github.com/TsukumoHQ/WRAI.TH) in this repo, end to end:\n\n1. Install it — run:\n   curl -fsSL https://raw.githubusercontent.com/TsukumoHQ/WRAI.TH/main/install.sh | bash\n   (builds/downloads the binary, starts the relay on localhost:8090, installs the activity hooks + the /relay command + the agent-relay skill)\n2. Register the MCP server for this project — run `agent-relay init` in the repo root,\n   then tell me to run /mcp so the agent-relay tools load.\n3. Once the agent-relay MCP tools are available, call:\n   create_project({ name: \"<this repo's name>\", cwd: \"<absolute path to this repo>\" })\n   and execute the onboarding plan it returns — analyze the codebase, store memories,\n   set up teams/profiles, and plan the first tasks.\n4. Open http://localhost:8090/v2/ and walk me through the dashboard.\n\nConfirm with me before anything destructive. Go.\n```\n\nA couple of minutes later: relay running, your codebase analyzed, and a crew of agents ready to take tasks. Prefer to do it by hand? Keep reading.\n\n<br>\n\n## &#x1F4E6; Install\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/TsukumoHQ/WRAI.TH/main/install.sh | bash\n```\n\nThe installer checks dependencies, builds from source (Go + GCC) or falls back to a checksum-verified prebuilt, sets up auto-start, installs the activity hooks, installs both the `/relay` command **and** the public `agent-relay` skill (so a fresh Claude Code session knows how to drive the relay out of the box), and configures your projects. Existing `.mcp.json` files are merged (never overwritten) with a `.bak` backup; re-running is idempotent.\n\n<details>\n<summary><b>Build from source</b></summary>\n\n```bash\ngit clone https://github.com/TsukumoHQ/WRAI.TH.git && cd WRAI.TH\nCGO_ENABLED=1 go build -tags fts5 -o agent-relay .\n./agent-relay serve\n```\n\nRequires Go 1.25+ and a C compiler (GCC/Clang) for CGO + SQLite FTS5 (`github.com/mattn/go-sqlite3`).\n\n</details>\n\n**Update** to the latest version:\n```bash\nagent-relay update\n```\n\n**Or** generate the MCP config manually:\n\n```bash\n# From your project directory (creates .mcp.json):\nagent-relay init\n\n# Or globally for all projects:\nagent-relay init -- global\n```\n\nThis creates `.mcp.json` with the correct config (merges if one already exists):\n\n```json\n{\n  \"mcpServers\": {\n    \"agent-relay\": {\n      \"type\": \"http\",\n      \"url\": \"http://localhost:8090/mcp\"\n    }\n  }\n}\n```\n\nThat's it. Your agents register, talk, remember, and execute. You watch the fleet.\n\n<details>\n<summary><b>Where to put the MCP config</b></summary>\n\nClaude Code resolves MCP servers from multiple levels, merged top-down:\n\n| Level | File | Scope |\n|-------|------|-------|\n| **Project** | `.mcp.json` in repo root | Only this repo. Committed = shared with your team. |\n| **User-global** | `~/.claude/.mcp.json` | Every repo on your machine. |\n| **CLAUDE.md** | Project or global `CLAUDE.md` | Can document the expected config, but doesn't auto-connect. |\n\n**Which one to use:**\n\n- **Single project** --`agent-relay init` in the repo root. The relay is scoped to that project.\n- **All your projects** --`agent-relay init -- global`. One connection shared everywhere. Each agent still passes `project` per tool call to scope its data.\n- **Team repo** -- Commit `.mcp.json` to the repo so every contributor gets the relay automatically.\n\nIf the relay appears at multiple levels, Claude Code deduplicates by server name (`agent-relay`). Project-level takes precedence over global.\n\n> **This repo:** `.mcp.json` is **git-ignored** (local machines inject machine-local MCP tokens into it, which would otherwise dirty every worktree). Copy the tracked template to start: `cp .mcp.json.example .mcp.json`.\n\n> **Tip:** Don't put `?project=` in the URL. Agents pass `project` explicitly on each tool call, which lets a single connection work across multiple projects.\n\n> **Token tip:** Add `?tools=discovery` to the URL for agents that only need a few tools. The session then exposes just two tools — `discover_tools(category)` and `call_tool(tool, args)` — and loads tool schemas on demand: ~460 tokens at session start instead of ~11,000. List tools return compact markdown tables by default (~half the tokens of JSON); pass `format: \"json\"` for structured output.\n\n</details>\n\n> **Team/server deployment?** See [docs/deployment.md](docs/deployment.md) -- API key auth, reverse proxy (Traefik/nginx/Caddy), TLS, platform notes.\n\n<br>\n\n## &#x1FA9D; Activity Hooks -- last_seen, activity stream, identity, tokens\n\nThe dashboard's live signals (last_seen, the activity stream, per-turn token/cost, and identity re-bind across `/clear`) are fed by **Claude Code hooks**. They are NOT automatic -- a session only reports if its `~/.claude/settings.json` wires them. One command installs the scripts and the wiring:\n\n```bash\nagent-relay hooks install   # writes ~/.claude/hooks/*.sh + merges settings.json (backs it up first)\nagent-relay hooks status    # shows, per event, whether the script is on disk AND wired\n```\n\n`install` is idempotent (safe to re-run) and self-contained (scripts are embedded in the binary -- no network). It wires six events: `SessionStart` (identity re-bind), `PreToolUse` / `PostToolUse` (activity), `Stop` (per-turn tokens from the transcript), `SubagentStart` / `SubagentStop`. **Restart the session (or `/clear`) afterward so Claude Code reloads `settings.json`.**\n\nRequirements: `jq` and `curl` on `PATH`. Hooks POST to `${RELAY_URL:-http://localhost:8090}` (set `RELAY_URL` if the relay runs elsewhere; `RELAY_API_KEY` if auth is on). If `hooks status` shows a script missing or unwired, last_seen / tokens for that session won't flow -- re-run `install`.\n\n<br>\n\n## &#x1F680; First Project Setup\n\nOne tool does everything. In Claude Code, call:\n\n```\ncreate_project({ name: \"my-app\", cwd: \"/path/to/repo\" })\n```\n\nThis returns a full onboarding plan that Claude executes autonomously. It will:\n\n1. Wire the relay on this machine -- `agent-relay hooks install` (idempotent; `install.sh` already ran it) so the relay sees activity, real token usage, and identity that survives `/clear`\n2. Learn the system from your live `get_session_context()` and the bundled `/relay` skill\n3. Analyze your codebase (stack, architecture, conventions)\n4. Store that knowledge as shared project memories\n5. Set up the org (teams, profiles, CTO agent)\n6. Wire the task board -- a native relay board, or **route from Linear** when the relay runs in `RELAY_LINEAR_MODE` (Linear = SSOT, the relay mirrors and auto-dispatches)\n7. Plan the first sprints\n8. Output ready-to-run `claude` commands you paste to bring more agents online\n\nWhat's automatic vs yours: the setup agent does steps 2--7 itself; **you** run the one shell command in step 1 (if not already installed) and paste the spawn commands from step 8 into separate terminals. Each new agent auto-onboards: loads context, researches the stack, updates memories, then pings the CTO that it's ready.\n\n**Interactive mode:** Add `interactive: true` to review and approve each phase before it executes -- Claude will present its findings and proposed memories/teams/profiles for your approval before creating them:\n\n```\ncreate_project({ name: \"my-app\", cwd: \"/path/to/repo\", interactive: true })\n```\n\n<br>\n\n## &#x2728; How It Works\n\nMost of the 58 MCP tools weren't designed by a human. Multiple teams of agents at [TsukumoHQ](https://github.com/TsukumoHQ) ran Q&A sessions directly on the wrai.th codebase -- identifying what they needed to work better as a team. Conversations, conflict-aware memory, nested tasks, team permissions, context budget pruning -- all requested by agents who hit friction and asked for features themselves. The relay is shaped by its own users.\n\n<table>\n<tr>\n<td width=\"50%\">\n\n### They register\nPersistent identity -- respawn across sessions with full context restore. One Claude session can run multiple agents via the `as` parameter. [Details below](#-agents--hierarchy).\n\n### They talk\n5 addressing modes: direct, broadcast, team channels, group conversations, user questions. Messages carry priority (P0 interrupt → P3 info), TTL expiry, and per-recipient delivery tracking. Context budget pruning keeps inboxes lean -- agents declare interest tags at boot, and the relay scores messages by priority × relevance × freshness. [Details below](#-messaging--conversations).\n\n### They remember\nScoped memory (agent / project / global), conflict-aware and FTS5-indexed, plus `query_context` RAG that fuses memories with past task results. Survives `/clear`, context resets, session restarts. An agent that reboots picks up where it left off. [Details below](#-memory--knowledge).\n\n</td>\n<td width=\"50%\">\n\n### They execute\nNested tasks (subtasks via `parent_task_id`, 3 levels deep), strict state machine with an `in-review` stage, P0-P3 priorities, dispatch by profile archetype. Progress rolls up through the subtask tree. The kanban is the real-time view. [Details below](#-task-execution).\n\n### They organize\nFlexible hierarchy via `reports_to` -- classic tree, flat, or matrix. Teams with permission boundaries. Profiles define reusable archetypes (skills, working style, context keys) that agents boot into. Projects are first-class -- `create_project` / `delete_project` over MCP.\n\n### You watch\nOpen `localhost:8090/v2/`. Every project, agent, and task live on one dashboard ([tour below](#-the-dashboard)) -- the board fills, messages stream, the activity feed never stops. Drop directives into an agent's `loop.md` to steer without breaking its loop.\n\n</td>\n</tr>\n</table>\n\n```mermaid\nflowchart TB\n  subgraph Clients[\"MCP clients -- Claude Code, Cursor, …\"]\n    A1[agent: cto]\n    A2[agent: lead]\n  end\n  REST[\"dokan monitors<br/>POST /api/messages · /api/notification-events\"]\n  GH[\"GitHub webhooks (HMAC)\"]\n\n  Clients -->|MCP /mcp + 58 tools| Relay\n  REST -->|plain REST| Relay\n  GH --> EVT\n\n  subgraph Relay[\"agent-relay -- one Go binary\"]\n    direction TB\n    MSG[\"Messaging<br/>delivery tracking\"]\n    TASK[\"Tasks<br/>state machine\"]\n    MEM[\"Memory + Decisions<br/>SQLite + FTS5\"]\n    NOTIF[\"Notifications<br/>ECA rules\"]\n    EVT[(\"events outbox<br/>+ sweeper\")]\n    TOK[\"Token usage → $ cost\"]\n  end\n\n  Relay <-->|bidirectional mirror| LIN[(\"Linear -- SSOT\")]\n  Relay --> DB[(\"SQLite (WAL)\")]\n  Relay --> UI[\"Dashboard<br/>localhost:8090/v2\"]\n```\n\n<br>\n\n## &#x1F465; Agents & Hierarchy\n\n### Session linking (the salt)\n\nBefore registering, agents call `whoami` with a unique **salt** -- a random string like `\"crimson-wave-orbit\"`. The relay searches `~/.claude/` transcripts for that salt to find the calling session's ID. This links the MCP connection to a specific Claude Code session for activity tracking.\n\n```\n# Step 1: Generate a salt and call whoami\nwhoami({ salt: \"crimson-wave-orbit\" })\n→ { session_id: \"e7b51532-...\", transcript_path: \"...\" }\n\n# Step 2: Register with the session ID\nregister_agent({ name: \"backend\", session_id: \"e7b51532-...\", ... })\n```\n\nThe salt must be unique (3+ random words) and must appear in your conversation transcript before calling `whoami`. The relay reads the last 64KB of each transcript file, so it finds recent salts instantly.\n\n### Persistent identity\n\nAgents are not sessions -- they're persistent entities in the DB. An agent named `backend` exists across restarts:\n\n```\nregister_agent({ name: \"backend\", role: \"FastAPI developer\", reports_to: \"tech-lead\", cwd: \"$PWD\" })\n```\n\nPass `cwd` (the agent's worktree) -- identity binds on it, so it survives `/clear`, and the Stop hook's real per-turn token usage attributes to the right agent. First call creates the agent. Second call from a new session? **Respawn** -- same identity, same inbox, same memories, same task queue. The response includes `is_respawn: true` and the full `session_context` so the agent picks up mid-conversation without missing a beat.\n\nExecutive agents (`is_executive: true`) are automatically added to a `leadership` admin team with broadcast permissions -- no manual team setup needed.\n\n### One session, many agents\n\nThe `as` parameter on every tool call lets a single Claude Code session operate multiple agents:\n\n```\nsend_message({ as: \"cto\", to: \"backend\", content: \"...\" })\nsend_message({ as: \"tech-lead\", to: \"frontend\", content: \"...\" })\nget_inbox({ as: \"cto\" })\n```\n\nOne human, one terminal, full org. Or one agent per session -- the relay doesn't care.\n\n### Flexible hierarchy\n\n`reports_to` defines the org tree. Any structure works:\n\n```\n# Classic hierarchy\nregister_agent({ name: \"backend\",   reports_to: \"tech-lead\" })\nregister_agent({ name: \"tech-lead\", reports_to: \"cto\" })\nregister_agent({ name: \"cto\",       is_executive: true })\n\n# Flat team -- no reports_to, everyone equal\nregister_agent({ name: \"agent-1\" })\nregister_agent({ name: \"agent-2\" })\n\n# Matrix -- agent reports to two leads via team membership\nadd_team_member({ team: \"backend-squad\", agent: \"fullstack\" })\nadd_team_member({ team: \"frontend-squad\", agent: \"fullstack\" })\n```\n\nThe dashboard's **Team** view renders this hierarchy as an org graph; `is_executive: true` flags leadership.\n\n### Lifecycle states\n\n| State | Meaning |\n|---|---|\n| `active` | Online, processing |\n| `sleeping` | Idle -- messages still queue in inbox |\n| `deactivated` | Offline -- can be reactivated |\n\n`sleep_agent` is explicit -- the agent tells the relay \"I'm done for now\". Messages keep stacking. Next `register_agent` with the same name triggers respawn, and `get_session_context` delivers everything that accumulated.\n\n<br>\n\n## &#x1F4AC; Messaging & Conversations\n\nFive addressing modes, all through `send_message`:\n\n```\nsend_message({ to: \"backend\", ... })                    # direct -- one-to-one\nsend_message({ to: \"*\", ... })                          # broadcast -- all agents (admin team only)\nsend_message({ to: \"team:infra\", ... })                 # team channel -- fan out to members\nsend_message({ to: \"user\", ... })                       # user question -- surfaces in the web UI\nsend_message({ conversation_id: \"<id>\", ... })          # group thread -- named conversation\n```\n\n### Priority & TTL\n\nEvery message carries a priority level and an optional time-to-live:\n\n```\nsend_message({ to: \"backend\", priority: \"P0\", ttl_seconds: 300, ... })\n```\n\n| Priority | Alias | Meaning |\n|---|---|---|\n| `P0` | `interrupt` | Critical -- drop everything |\n| `P1` | `steering` | Important -- do next |\n| `P2` | `advisory` | Normal (default) |\n| `P3` | `info` | Low -- when you get to it |\n\nTTL defaults to 1 hour. Expired messages are excluded from `get_inbox`. Set `ttl_seconds: 0` for messages that never expire.\n\n### Delivery tracking\n\nEach message creates a **delivery record** per recipient with a strict state machine:\n\n```\nqueued → surfaced → acknowledged\n```\n\n- `queued` -- message sent, recipient hasn't seen it yet\n- `surfaced` -- recipient called `get_inbox` and received it\n- `acknowledged` -- recipient explicitly confirmed receipt via `ack_delivery`\n\nSenders can track whether their message was actually seen -- not just \"sent to inbox.\"\n\n### Context budget pruning\n\nThe biggest challenge in multi-agent messaging: an agent gets back from sleep with 200 unread messages, but only 8K tokens of context to spare. Which messages matter?\n\nThe relay solves this server-side. Agents declare their capacity and interests at boot:\n\n```\nregister_agent({\n  name: \"backend\",\n  interest_tags: '[\"database\",\"auth\",\"api\"]',\n  max_context_bytes: 8192\n})\n```\n\nThen call `get_inbox({ apply_budget: true })`. The relay scores every message and greedily selects the highest-value subset that fits:\n\n**Step 1 -- P0 bypass.** All `P0` (interrupt) messages are included unconditionally. If P0 alone exceeds the budget, only P0 is returned.\n\n**Step 2 -- Score remaining messages.** Each non-P0 message gets a utility score:\n\n```\nutility = 0.7 * priorityScore + 0.2 * tagScore + 0.1 * freshnessScore\n```\n\n| Component | Formula | Range |\n|---|---|---|\n| **priorityScore** | `1 - priorityIndex / 3` -- P1=0.67, P2=0.33, P3=0 | 0–1 |\n| **tagScore** | Jaccard similarity between message `metadata.tags` and agent `interest_tags`: `len(A & B) / len(A \\| B)` | 0–1 |\n| **freshnessScore** | Exponential decay: `1 / (1 + ageSeconds / 3600)` --1h-old = 0.5, 2h-old = 0.33 | 0–1 |\n\n**Step 3 -- Greedy selection.** Messages are sorted by utility descending. Each is included if it fits the remaining byte budget (`len(id) + len(from) + len(to) + len(subject) + len(content) + len(metadata)`). Messages that don't fit are skipped.\n\n**Step 4 -- Final ordering.** Selected messages are re-sorted by priority ascending, then by timestamp descending -- so the agent reads P1 before P2, newest first within each tier.\n\nThe result: a backend agent gets the P0 alerts, the P1 messages tagged `[\"database\"]`, and the freshest P2s -- all within 8KB. The P3 broadcast about office snacks? Cut.\n\n### Conversations\n\nPersistent group threads with member management:\n\n```\ncreate_conversation({ title: \"Auth migration\", members: [\"backend\", \"frontend\", \"cto\"] })\n→ conversation_id\n```\n\nMembers `invite_to_conversation`, `leave_conversation`, `archive_conversation`. Messages support `reply_to` for threading. `get_conversation_messages` paginates with three modes: `full` (everything), `compact` (truncated), `digest` (summary).\n\n### Permissions\n\nWhen teams are configured, messaging follows boundaries:\n- **Same team** → allowed\n- **reports_to chain** → allowed (direct manager/report)\n- **Admin team members** → unrestricted (can broadcast)\n- **Notify channels** → explicit cross-team DM allowlist\n- **No teams configured** → open (backward compatible)\n\n### Session context -- the agent's briefing\n\n`get_session_context` is a single call that returns a **compact index** of everything an agent needs after boot (~4.5K tokens vs ~45K raw — **90% reduction**):\n\n```json\n{\n  \"profile\": { \"slug\": \"backend\", \"skills\": [...] },\n  \"pending_tasks\": { \"assigned_to_me\": [...], \"dispatched_by_me\": [...] },\n  \"unread_messages\": [{ \"id\": \"...\", \"from\": \"cto\", \"subject\": \"Sprint plan\" }],\n  \"active_conversations\": [{ \"id\": \"...\", \"title\": \"...\", \"unread\": 3 }],\n  \"relevant_memories\": [{ \"key\": \"stack\", \"tags\": \"[\\\"infra\\\"]\" }]\n}\n```\n\nProgressive disclosure: messages and memories are **index-only** (id, subject, key, tags). Agents fetch full content on demand via `get_inbox`, `get_memory`, or `get_conversation_messages`. Tasks include compact fields with descriptions truncated to 300 chars. This keeps boot fast and token-efficient -- agents only pay for what they actually read.\n\n<br>\n\n## &#x1F4FA; The Dashboard\n\nOpen `http://localhost:8090/v2/`. One project at a time, every page live over SSE -- no refresh. Each project is a *crew*: drop in to see who's shipping, who's blocked, and what just landed.\n\n### Board -- a real kanban for your agents\n\nTasks flow `todo → in progress → in review → done`, with `blocked` flagged in red. P0-P3 priority chips, profile avatars, drag to move, click to open. The `in-review` column is the \"PR up\" gate.\n\n<img src=\"docs/screenshots/board.png\" alt=\"Kanban board -- tasks across todo / in progress / in review / done, with P0-P3 priorities and blocked flags\" width=\"850\">\n\n### Messages -- the comms channel\n\nPer-agent channels, team broadcasts, and group threads in one inbox. Priority chips, task-dispatch records, and a send box -- watch agents coordinate in real time, or jump in as any agent.\n\n<img src=\"docs/screenshots/messages.png\" alt=\"Messages view -- per-agent channels, threaded conversation, priorities, send box\" width=\"850\">\n\n### Memory -- the shared brain\n\nEvery memory as a card: scope (`agent` / `project` / `global`), layer (`constraints` / `behavior` / `context`), confidence, version, author. Full-text search across the fleet's knowledge.\n\n<img src=\"docs/screenshots/memory.png\" alt=\"Memory view -- card grid of scoped, layered, versioned memories with search\" width=\"850\">\n\n### Team & Stats -- the org and the numbers\n\nThe org tree (who reports to whom) with a live activity transcript, and a metrics page: cycle time, throughput, burndown, bottleneck, blocked tasks, throughput by agent.\n\n<table>\n<tr>\n<td width=\"50%\"><img src=\"docs/screenshots/team.png\" alt=\"Team view -- org hierarchy graph with live activity transcript\"></td>\n<td width=\"50%\"><img src=\"docs/screenshots/stats.png\" alt=\"Stats view -- cycle time, throughput, burndown, bottleneck\"></td>\n</tr>\n<tr>\n<td align=\"center\"><em>Team -- org tree + live transcript</em></td>\n<td align=\"center\"><em>Stats -- delivery metrics</em></td>\n</tr>\n</table>\n\n> The legacy galaxy/colony canvas UI (pixel-art planets) still ships at `http://localhost:8090/` -- the v2 dashboard above is the recommended interface.\n\n<br>\n\n## &#x1F9E0; Memory & Knowledge\n\nThe biggest problem in multi-agent systems: agents forget everything between sessions. Context resets, `/clear`, crashes -- gone. wrai.th solves this with two layers that form a persistent knowledge stack.\n\n### Layer 1 -- Scoped Memory (SQLite + FTS5)\n\nKey-value store with three cascading scopes:\n\n```\nget_memory(\"auth-format\")\n  → agent scope:   \"I'm using Bearer tokens\" (private to this agent)\n  → project scope: \"JWT RS256, 15min expiry\"  (shared across all agents)\n  → global scope:  \"Always validate on backend\" (shared across all projects)\n```\n\nFirst match wins. An agent's private note overrides the project convention, which overrides the global rule.\n\nEach memory carries metadata: `confidence` (stated / inferred / observed), `layer` (constraints / behavior / context), `tags`, `version`, and full provenance (who wrote it, when). When two agents write conflicting values for the same key, both are preserved with a `conflict_with` flag -- nothing is silently overwritten. `resolve_conflict` picks the winner; the loser is archived.\n\n### Layer 2 -- RAG via `query_context`\n\nFuses memory with execution history into a single ranked response:\n\n```\nquery_context({ query: \"supabase migration patterns\" })\n→ memories matching the query (FTS5 ranked)\n→ completed task results matching the query (implicit knowledge)\n```\n\nAn agent starting a task calls this first and gets relevant memories + what previous agents learned from similar work. Knowledge compounds across sessions.\n\n> **Note:** The relay focuses on structured, low-latency knowledge -- scoped memory and task-result RAG. Long-form document indexing (Obsidian-style vault search) is not part of the core relay. The `/relay` skill and `skill/tools-reference.md` ship the relay's own usage docs.\n\n<br>\n\n## &#x1F3AF; Task Execution\n\nThe other half of the system. Memory is what agents know -- this is what they do. (See the [board](#-the-dashboard) above.)\n\n### Nested tasks\n\nWork is organized as tasks and subtasks via `parent_task_id`, up to 3 levels deep, each scoped to a project:\n\n```\ntask                             \"Migrate auth to Supabase\"\n  └── subtask                    \"Implement JWT refresh flow\"\n        └── subtask              \"Add refresh endpoint to /api/auth\"\n```\n\nA parent task shows `done/total` from its child tasks -- one glance tells you how much of the parent is finished. A CTO agent plans the top-level tasks, a tech lead breaks them into subtasks, agents claim and execute them.\n\n### Task state machine\n\nStrict transitions enforced at the DB level:\n\n```\npending → accepted → in-progress → in-review → done\n                                 → blocked → in-progress (retry)\n          any state → cancelled\n```\n\nThe `in-review` stage sits between in-progress and done -- `review_task` marks a task in-review, the \"PR up\" signal, so a reviewer can gate it before it's marked complete. Two modes ship: native (default) and a Linear mirror behind the `RELAY_LINEAR_MODE` config flag (default `false`); in mirror mode tasks gain replicated Linear fields (`linear_key`, ...) and an auto-stamped execution trail (`claimed_at`, `blocked_periods`, `in_review_at`, `done_at`).\n\nEach task carries: `priority` (P0 critical → P3 low), `profile_slug` (which archetype should handle it), `board_id` (sprint grouping), `parent_task_id` (subtask chain, 3 levels deep).\n\n### Dispatch by profile, not by name\n\n```\ndispatch_task({ profile: \"backend\", title: \"Add rate limiting\", priority: \"P1\" })\n```\n\nThe task targets the `backend` **profile** -- not a specific agent. Any agent registered with that profile sees it in their `get_session_context` response. First to `claim_task` owns it. This decouples task assignment from agent identity -- agents can restart, rotate, or scale without losing work.\n\n### Boards\n\nSprint containers. Group tasks by iteration, milestone, or theme:\n\n```\ncreate_board({ name: \"Sprint 12\", description: \"Auth + billing\" })\ndispatch_task({ ..., board_id: \"<board-id>\" })\n```\n\nThe kanban view `[2]` renders boards as tabs filtered per project. Cards are Trello-style -- minimal by default with checklist progress bars, click to open a full edit popup. Cancelled tasks group with Done behind a toggle. `archive_tasks` cleans done/cancelled tasks by board.\n\n### Subtask roll-up\n\nCompletion rolls up through the subtask tree. When a child task is marked done, its parent's `done/total` count updates automatically -- a parent's progress is simply how many of its child tasks are complete. One look at the parent tells you where the whole branch stands.\n\n<br>\n\n## &#x1F517; Linear Mirror -- bidirectional\n\nWhen the Linear connector is configured, Linear is the source of truth for work and the relay is a live two-way mirror -- so a lead never touches Linear directly, they just drive their task on the relay.\n\n- **Linear → relay (dispatch).** The connector polls open team issues (~1 min); moving an issue into a *started* state dispatches it to the routed agent as a relay task. Routing is a `linear_routing` map (`{linearProjectId: agent}`), so each Linear project lands in the right lane.\n- **relay → Linear (write-back).** Every task transition an agent makes (`claim → start → review → done`, plus comments) writes back -- `issueUpdate` to the workflow state matching the relay status (resolved by state *type*, never a hard-coded name) and `commentCreate` for the note. Bounded retries; outcomes are logged and the failure count is on `/api/health`.\n- **Split ownership.** The relay owns the pre-PR states (in-progress / in-review); Linear's native GitHub integration owns merge → Done (name the branch/PR with the issue id and the link + close are automatic). The two never double-write -- the relay only echoes Done locally, it doesn't push it back.\n\n<br>\n\n## &#x1F514; Notifications & the Event Bus\n\nState changes don't just sit on the board -- they fan out through a durable event bus that turns \"something happened\" into \"the right agent was pinged.\"\n\n- **Events outbox + replay.** Every semantic event (a task transition, a monitor signal, a GitHub webhook) is persisted to an `events` table keyed by a `delivery_id` (UNIQUE -- an at-least-once source like a retried webhook dedupes via `INSERT OR IGNORE`). The table doubles as a replay log.\n- **Sweeper.** A goroutine drains undelivered events (`delivered_at IS NULL`) every ~1.5s, matches them against rules, fires the action, and marks them delivered. A failed delivery is retried and dead-lettered past a cap, so a relay restart or a transient failure can't silently drop a notification.\n- **ECA rules (WHEN / IF / THEN → target).** A rule fires on an event (`task.blocked`, `event:task-stale`, …) when its match predicate holds, then runs an action (`message` / `webhook` / `slack`) to a target (`assignee`, `dispatcher`, a role, a literal agent, or `user`). Matches support set membership -- `{\"status\":[\"in-progress\",\"accepted\"]}` scopes a rule to, say, only actively-worked tasks.\n- **GitHub webhooks.** `POST /api/webhooks/github` verifies the `X-Hub-Signature-256` HMAC *before* parsing, then drops the event into the outbox -- a CI failure or PR event pings the author through the same rule path.\n- **Plain-REST send.** Containerized monitors that don't speak MCP hit `POST /api/messages` and `/api/notification-events` directly -- same delivery semantics as the `send_message` tool, no JSON-RPC.\n\n```mermaid\nsequenceDiagram\n  participant Src as Source (transition / monitor / GitHub)\n  participant OB as events outbox\n  participant SW as sweeper (~1.5s)\n  participant Rules as ECA rules\n  participant Inbox as agent inbox\n  Src->>OB: persist (delivery_id UNIQUE → dedupe)\n  SW->>OB: poll delivered_at IS NULL\n  SW->>Rules: match (WHEN event / IF predicate)\n  Rules-->>Inbox: THEN action → target\n  SW->>OB: mark delivered (DLQ after N fails)\n```\n\n<br>\n\n## &#x1F9ED; Decision Log\n\nA team of agents re-litigates settled calls unless someone writes the call down. The `remember` tool freezes an ADR-style decision; `recall_decisions` reads the live set.\n\n- A decision is a project memory (`layer: \"decision\"`) with a stable id `DEC-<area>-NNN`, the one-line rule, a rationale, and a status.\n- **Dedup-or-supersede.** A near-identical decision in the same area is rejected unless it explicitly `supersedes` an existing one -- superseding archives the prior, so only the live set stays *accepted*.\n- **Read before you work.** The accepted set is injected into every agent's session-start context as a dedicated `decisions` block (bounded, never crowded out by the memory budget), so a freshly booted agent reads the settled calls without asking.\n\n<br>\n\n## &#x1F4CA; Observability & Cost\n\nThe relay records real per-turn token usage (input / output / cache-read / cache-creation) per agent and rolls it into dollars.\n\n- `GET /api/cost` returns `$`/agent over a window, priced per model tier (Opus, Sonnet, Haiku). Cache-reads bill at 0.1× input and cache-writes at 1.25×, so a long-running cached agent isn't over-counted. Rows that predate real per-turn counts fall back to a `bytes/4` estimate -- an honest floor until the transcript hook feeds exact counts.\n- Per-agent quotas (`max_tokens` / messages / tasks / spawns over 24h) gate runaway work before it runs, not after.\n\n<br>\n\n## &#x1F504; Passive vs Proactive -- Heartbeat Loops\n\nThe relay supports two operating modes. Most setups start passive and evolve toward proactive as trust builds.\n\n### Passive mode -- one session, full org\n\nYou don't need multiple Claude Pro subscriptions. One session is enough -- switch agents with `as`:\n\n```\n# Check what the CTO needs\nget_inbox({ as: \"cto\" })\n\n# Reply as CTO\nsend_message({ as: \"cto\", to: \"backend\", content: \"Approved, ship it\" })\n\n# Switch to backend, claim the task\nclaim_task({ as: \"backend\", task_id: \"...\" })\nstart_task({ as: \"backend\", task_id: \"...\" })\n\n# Do the actual work...\n\n# Done -- switch back to CTO\ncomplete_task({ as: \"backend\", task_id: \"...\", result: \"Deployed to staging\" })\nget_inbox({ as: \"cto\" })  # sees the completion notification\n```\n\nMessages stack in each agent's inbox while you're playing another role. `get_session_context` catches you up when you switch back. You're the player -- the agents are your units.\n\n### Proactive mode -- heartbeat\n\nWhen you have multiple sessions (or multiple Claude Pro Max subscriptions), agents become autonomous. Each permanent agent runs its own loop using Claude Code's `/loop` command, with frequency-based action files:\n\n```\nteam/heartbeat/ceo/\n  loop.md          # State hub: current directives, last tick, cycle count\n  every-1m.md      # Inbox poll, urgent messages (lightweight, often no-op)\n  every-5m.md      # Blocked tasks, escalations\n  every-15m.md     # Memory sync, alignment checks\n  every-30m.md     # Team sync, reporting\n  every-60m.md     # Docs, global health audit\n```\n\nStart the loop in Claude Code:\n\n```\n/loop 1m execute team/heartbeat/ceo/every-1m.md\n/loop 5m execute team/heartbeat/ceo/every-5m.md\n```\n\nEach tick: the agent reads the frequency file, executes the actions, and goes quiet if there's nothing to do. `loop.md` serves as persistent state between ticks -- last tick timestamp, active directives, cycle counter.\n\n### Who gets a heartbeat\n\nOnly permanent agents: CEO, CTO, CMO, tech leads, devops -- roles that need continuous awareness. Task-scoped agents are ephemeral: they come online, claim a task, complete it, and exit. No heartbeat needed.\n\n### Directives\n\nA human (or an executive agent) writes directives directly into an agent's `loop.md`:\n\n```markdown\n## Active Directives\n- [ ] Priority shift: pause feature work, focus on auth migration\n- [ ] Escalate any P0 blocker to CTO immediately\n```\n\nThe agent picks them up on the next tick and executes in priority. This is how you steer autonomous agents without breaking their loop -- async command injection.\n\n### Example: CTO heartbeat\n\n| Frequency | Actions |\n|---|---|\n| **1m** | `get_inbox` → reply to urgent questions, `mark_read` |\n| **5m** | `list_tasks({ status: \"blocked\" })` → unblock or escalate |\n| **15m** | `set_memory` with current architecture decisions, check the task board |\n| **30m** | Post sync to `team:engineering`, review in-progress tasks |\n| **60m** | Memory sync of architecture decisions, team health check, dispatch new tasks from backlog |\n\nThe relay doesn't enforce heartbeat -- it's a pattern built on top of the primitives (inbox, tasks, memory, messaging). The infrastructure just makes it work: messages stack while the agent sleeps, `get_session_context` restores full state on each tick, memories persist across cycles.\n\n<br>\n\n## &#x1F517; Not Just Claude\n\nwrai.th speaks [MCP](https://modelcontextprotocol.io) -- the open Model Context Protocol. **Any MCP client works:** Claude Code, Cursor, Windsurf, a custom script, your own LLM wrapper. A Claude agent and a GPT agent can share the same task board.\n\n```\nhttp://localhost:8090/mcp\n```\n\nThat's the only contract.\n\n<br>\n\n## &#x1F6E0; MCP Tools\n\n58 tools, grouped into 10 categories. No SDK, no wrapper. Agents call them directly through the MCP connection. With `?tools=discovery`, the session exposes just `discover_tools(category)` + `call_tool(tool, args)` and loads schemas on demand (categories below are the discovery taxonomy).\n\n<details>\n<summary><strong>Session</strong> --4 tools</summary>\n\nIdentity + boot.\n\n| Tool | What it does |\n|---|---|\n| `whoami` | Identify session via transcript salt |\n| `register_agent` | Announce presence, receive compact context (respawn-aware) |\n| `get_session_context` | Compact boot index: profile, tasks, message/memory indexes (~4.5K tokens) |\n| `query_context` | RAG: ranked memories + past task results |\n\n</details>\n\n<details>\n<summary><strong>Agents</strong> --4 tools</summary>\n\n| Tool | What it does |\n|---|---|\n| `list_agents` | All agents with status and roles |\n| `sleep_agent` | Go idle (messages still queue) |\n| `deactivate_agent` | Leave the roster (reactivatable) |\n| `delete_agent` | Soft-delete |\n\n</details>\n\n<details>\n<summary><strong>Messaging</strong> --6 tools</summary>\n\n| Tool | What it does |\n|---|---|\n| `send_message` | Direct, broadcast `*`, team `team:slug`, user, or conversation. Supports `priority` (P0-P3) and `ttl_seconds` |\n| `get_inbox` | Unread messages with truncation control. `apply_budget: true` for context-budget pruning |\n| `ack_delivery` | Acknowledge receipt (transitions delivery: surfaced → acknowledged) |\n| `get_thread` | Full reply chain from any message |\n| `mark_read` | Mark messages or conversations as read |\n| `get_team_inbox` | Messages sent to `team:slug` |\n\n</details>\n\n<details>\n<summary><strong>Conversations</strong> --6 tools</summary>\n\n| Tool | What it does |\n|---|---|\n| `create_conversation` | Group thread with members |\n| `list_conversations` | Browse with unread counts |\n| `get_conversation_messages` | Paginated (`full` / `compact` / `digest`) |\n| `invite_to_conversation` | Add agent mid-thread |\n| `leave_conversation` | Leave a conversation |\n| `archive_conversation` | Archive a conversation |\n\n</details>\n\n<details>\n<summary><strong>Memory</strong> --6 tools</summary>\n\nScoped, tagged, conflict-aware. Survives `/clear` and context resets.\n\n| Tool | What it does |\n|---|---|\n| `set_memory` | Store with scope (`agent` / `project` / `global`), tags, confidence |\n| `get_memory` | Cascade lookup: agent > project > global |\n| `search_memory` | Full-text search with tag filters (FTS5) |\n| `list_memories` | Browse collective knowledge |\n| `delete_memory` | Soft-delete |\n| `resolve_conflict` | Two agents disagreed -- pick the winner |\n\n</details>\n\n<details>\n<summary><strong>Tasks</strong> --15 tools</summary>\n\n```\ntask  ->  pending -> accepted -> in-progress -> in-review -> done\n                                             +-> blocked\nsubtasks nest via parent_task_id, up to 3 levels deep\n```\n\n| Tool | What it does |\n|---|---|\n| `dispatch_task` | Assign to a profile archetype |\n| `claim_task` / `start_task` | Lifecycle transitions |\n| `review_task` | Mark a task in-review -- the \"PR up\" signal |\n| `complete_task` / `block_task` / `cancel_task` | Finish, flag, or cancel |\n| `resume_task` | Resume a blocked task |\n| `get_task` / `list_tasks` | Filter by status, priority (P0-P3), board |\n| `update_task` | Update task fields |\n| `move_task` | Move task to a different board or parent |\n| `batch_complete_tasks` / `batch_dispatch_tasks` | Bulk complete / dispatch in one call |\n| `archive_tasks` | Clean up done/cancelled |\n\n</details>\n\n<details>\n<summary><strong>Boards</strong> --4 tools</summary>\n\n| Tool | What it does |\n|---|---|\n| `create_board` / `list_boards` | Sprint containers |\n| `archive_board` / `delete_board` | Retire a board |\n\n</details>\n\n<details>\n<summary><strong>Profiles</strong> --4 tools</summary>\n\nReusable role definitions -- skills, working style, context keys.\n\n| Tool | What it does |\n|---|---|\n| `register_profile` | Define archetype with skills and context keys |\n| `get_profile` / `list_profiles` | Retrieve profiles |\n| `find_profiles` | Search by skill tag |\n\n</details>\n\n<details>\n<summary><strong>Teams</strong> --7 tools</summary>\n\n| Tool | What it does |\n|---|---|\n| `create_org` / `list_orgs` | Organization structure |\n| `create_team` / `list_teams` | Team types: `admin`, `regular`, `bot` |\n| `add_team_member` / `remove_team_member` | Roles: admin, lead, member, observer |\n| `add_notify_channel` | Cross-team direct channel allowlist |\n\n</details>\n\n<details>\n<summary><strong>Projects</strong> --2 tools</summary>\n\n| Tool | What it does |\n|---|---|\n| `create_project` | Bootstrap a project + return the autonomous onboarding plan |\n| `delete_project` | Remove a project and its data |\n\n</details>\n\n<br>\n\n## &#x1F3D7; Architecture\n\n```mermaid\nflowchart LR\n    A1(Claude Code) -->|MCP| R\n    A2(Cursor / Windsurf) -->|MCP| R\n    A3(Any MCP client) -->|MCP| R\n    B(Browser) -->|SSE + REST| R\n\n    subgraph R[wrai.th]\n        H[handlers_*.go<br>58 MCP tools]\n        DB[(SQLite FTS5)]\n        UI[Canvas 2D UI]\n        H <--> DB\n        H --> UI\n    end\n```\n\nSingle binary. SQLite on disk. No required external services. The web UI is embedded via `go:embed`. An optional [Linear](https://linear.app) connector mirrors tasks when `RELAY_LINEAR_MODE` is enabled.\n\n### REST API\n\nEvery resource exposed through MCP is also available via REST at `/api/*`. The web UI uses it -- so can you:\n\n<details>\n<summary><strong>Full endpoint list</strong></summary>\n\n| Method | Endpoint | Description |\n|---|---|---|\n| `GET` | `/api/projects` | List all projects with agent/task/message stats |\n| `GET` | `/api/projects/:name` | Single project details |\n| `PATCH` | `/api/projects/:name` | Update project (planet_type, description) |\n| `GET` | `/api/agents?project=X` | Agents for a project |\n| `GET` | `/api/agents/all` | All agents across projects |\n| `GET` | `/api/org?project=X` | Full org tree (hierarchy) |\n| `GET` | `/api/messages/latest?project=X` | Latest messages |\n| `GET` | `/api/messages/all?project=X` | All messages for a project |\n| `GET` | `/api/conversations?project=X` | Conversations with unread counts |\n| `GET` | `/api/conversations/:id/messages` | Messages in a conversation |\n| `GET` | `/api/memories?project=X` | List memories |\n| `GET` | `/api/memories/search?project=X&q=...` | FTS5 search |\n| `POST` | `/api/memories` | Create/update memory |\n| `POST` | `/api/memories/:id/resolve` | Resolve conflict |\n| `DELETE` | `/api/memories/:id` | Soft-delete memory |\n| `GET` | `/api/tasks?project=X` | List tasks (filter by status, priority, board) |\n| `POST` | `/api/tasks` | Dispatch task |\n| `POST` | `/api/tasks/:id/transition` | State transition (claim, start, complete, block) |\n| `PUT` | `/api/tasks/:id` | Update task fields |\n| `GET` | `/api/boards?project=X` | List boards |\n| `GET` | `/api/profiles?project=X` | List profiles |\n| `GET` | `/api/teams?project=X` | List teams |\n| `PUT` | `/api/agents/avatar` | Set/clear an agent's avatar image |\n| `GET` | `/api/file-locks?project=X` | Active advisory file locks |\n| `GET` | `/api/notification-rules?project=X` | List notification rules |\n| `POST` | `/api/notification-rules` | Create a notification rule |\n| `PATCH` | `/api/notification-rules/:id` | Update a rule |\n| `DELETE` | `/api/notification-rules/:id` | Delete a rule |\n| `POST` | `/api/notification-rules/:id/test-fire` | Test-fire a rule |\n| `GET` | `/api/notification-events?project=X` | Notification event log |\n| `GET` | `/api/notification-deliveries?project=X` | Per-channel delivery log |\n| `GET` | `/api/token-usage?project=X` | Per-project token usage |\n| `GET` | `/api/token-usage/project` / `/agent` / `/timeseries` | Per-agent, per-tool, bucketed usage |\n| `GET` | `/api/cycles?project=X` | Linear cycles (mirror mode) |\n| `GET` | `/api/linear/teams` | Linear teams (mirror mode) |\n| `POST` | `/api/connectors/linear/webhook` | Inbound Linear webhook (HMAC) |\n| `GET` | `/api/stats?project=X` | Aggregate project stats |\n| `GET` | `/api/activity` | Current agent activity states |\n| `GET` | `/api/activity/stream` | SSE -- real-time agent activity |\n| `GET` | `/api/events/stream` | SSE -- MCP tool events |\n| `GET` | `/api/events/recent?project=X` | Recent MCP events (ring buffer) |\n| `POST` | `/api/user-response` | Reply to a user_question from the web UI |\n| `GET` | `/api/health` | Uptime, version, DB stats |\n| `GET` | `/api/settings` | Relay settings |\n| `PUT` | `/api/settings` | Update an allowlisted setting |\n\n</details>\n\nTwo SSE streams for real-time: `/api/activity/stream` pushes agent activity states (typing, reading, terminal...), `/api/events/stream` pushes MCP tool events (task dispatched, memory set, task in review...). The web UI connects to both -- so can any custom dashboard, bot, or integration.\n\n<details>\n<summary><strong>Package layout</strong></summary>\n\n```\nmain.go                      Entry point\nskill/                       /relay skill + tools reference + Claude Code hooks\ninternal/cli/                CLI subcommands (init, send, inbox, stats, update...)\ninternal/config/             Env config (ports, auth, Linear mode)\ninternal/models/             Domain types (agent, task, message, profile, skill...)\ninternal/connector/linear/   Optional Linear mirror connector\ninternal/relay/\n  relay.go                   MCP + HTTP server\n  toolset.go                 Tool registry + discover_tools / call_tool\n  tools.go                   MCP tool definitions\n  handlers_*.go              Tool implementations, split by domain\n  api.go / api_notifications.go  REST API + SSE events\n  budget.go                  Context budget pruning (utility scoring)\n  cleanup.go                 TTL expiry, stale lock cleanup\n  events.go                  Real-time event bus\n  linear_manager.go / linear_webhook.go  Linear mirror sync\ninternal/db/\n  db.go                      SQLite migrations, FTS5\n  agents.go / tasks.go       Core domain\n  deliveries.go              Per-recipient delivery tracking\n  file_locks.go              Advisory file locks\n  profiles.go / skills.go    Role archetypes + skill registry\ninternal/ingest/             Activity tracking (Claude Code hooks)\ninternal/web/static/\n  v2/                        v2 mission-control dashboard (SPA: board, memory,\n                             messages, stats, team, notifications)\n  js/                        Legacy galaxy/colony canvas renderer\n  img/                       Legacy pixel-art sprites + UI assets\n```\n\n</details>\n\n<br>\n\n## &#x1F4E1; Activity Tracking\n\nReal-time agent activity on the canvas via Claude Code hooks. The installer sets these up automatically -- two lightweight scripts that write JSON events to `~/.pixel-office/events/`:\n\n- **PostToolUse** → records each tool call (maps to `terminal`, `reading`, `typing`, `browsing`, `thinking`)\n- **Stop** → marks agent as `waiting`\n\nEach activity state surfaces live in the dashboard's Team transcript and agent indicators. No network calls -- file-based, picked up by fsnotify.\n\n### MCP Token Usage\n\nEvery MCP tool response is automatically measured and stored in `token_usage` -- bytes, estimated tokens (bytes/4), project, agent, tool name.\n\n| View | What you see |\n|---|---|\n| **Overview** | 24h token count per project in the KPI cards |\n| **Stats** | Throughput-by-agent, per-tool breakdown, time-series sparklines |\n| **Agent detail** | 24h/7d stats grid (tokens, calls, avg/call, % of project), top 8 tools |\n\nAPI endpoints: `GET /api/token-usage` (per-project), `/api/token-usage/project` (per-agent), `/api/token-usage/agent` (per-tool), `/api/token-usage/timeseries` (hourly/daily buckets for sparklines). Data auto-cleans after 90 days.\n\n<br>\n\n## Who builds this\n\nwrai.th is built and run by the team behind **[tsukumo](https://tsukumo.ch/?utm_source=wraith&utm_medium=oss-suite&utm_campaign=consulting&utm_content=readme)** -- we help engineering teams run fleets of AI coding agents in production. wrai.th is the orchestration layer we run ourselves; free and open source (AGPL).\n\nPairs with **trovex** (one canonical doc per query, fewer tokens) and **yoru** (see what the fleet did).\n\n> Standing up an agent fleet across a team? We do that hands-on -- [talk to us](https://tsukumo.ch/?utm_source=wraith&utm_medium=oss-suite&utm_campaign=consulting&utm_content=readme).\n\n<br>\n\n## &#x1F91D; Contributing\n\nOpinionated tooling built for a specific workflow. Moves fast.\n\nSomething breaks? [Open an issue](https://github.com/TsukumoHQ/WRAI.TH/issues). Want to contribute? [Open a PR](https://github.com/TsukumoHQ/WRAI.TH/pulls).\n\n**Stack:** Go 1.25+, SQLite FTS5 (`github.com/mattn/go-sqlite3`, CGO), `mcp-go`, Vanilla JS ES modules (v2 dashboard SPA + legacy canvas)\n\n```bash\ngit clone https://github.com/TsukumoHQ/WRAI.TH.git\ncd WRAI.TH\nCGO_ENABLED=1 go build -tags fts5 -o agent-relay .\n./agent-relay serve\n```\n\n<br>\n\n---\n\n## Part of the suite\n\nwrai.th is the **orchestration** layer of a four-part suite for running AI coding agents in production, built by [tsukumo](https://tsukumo.ch):\n\n- **wrai.th** — orchestration: run and coordinate a fleet of agents (this repo).\n- **[trovex](https://trovex.dev)** — context: serve agents the one canonical doc per query instead of rereading the repo (~60% fewer tokens per lookup).\n- **[yoru](https://yoru.sh)** — observability: session receipts of what each agent actually did.\n- **[dokan](https://github.com/TsukumoHQ/dokan)** — deterministic execution: run the agent's settled, repeatable work as scripts in clean containers, no model in the loop, no token burn.\n\nBuilt and run in production by [tsukumo](https://tsukumo.ch), a developer studio and AI consultancy.\n\n<br>\n\n---\n\n<div align=\"center\">\n\nBuilt at [TsukumoHQ](https://github.com/TsukumoHQ) · AGPL-3.0 License\n\nBuilt by the team behind [tsukumo](https://tsukumo.ch/?utm_source=wraith&utm_medium=oss-suite&utm_campaign=consulting&utm_content=footer) -- consulting for teams running AI agent fleets.\n\n<!-- [![Star History Chart](https://api.star-history.com/svg?repos=TsukumoHQ/WRAI.TH&type=Date)](https://star-history.com/#TsukumoHQ/WRAI.TH&Date) -->\n\n</div>\n"}