# Inbox CLI ## Purpose `inbox` is the durable coordination bus for agent-to-agent communication. It is not the scheduler. It stores threads, messages, leases, and artifacts so workers and leaders can coordinate through reliable state instead of ad hoc multi-turn chat. In normal operation: - workers use `inbox` directly - leaders use `inbox` mainly for inspection or manual override - leader-side task planning and dispatch should happen through `orch` ## Responsibilities `inbox` is responsible for: - creating and updating communication threads - sending directed messages between agents - allowing a worker to claim one thread at a time through leases - recording progress, blocked questions, results, and failures - attaching artifact references such as files, logs, or patches - listing, watching, waiting, and showing thread history ## Non-Responsibilities `inbox` should not decide: - how a large goal is decomposed into tasks - whether a task is ready based on dependencies - which worker should receive a task by policy - when a failed task should be retried Those decisions belong to `orch`. ## Core Objects - `thread`: the durable container for one work conversation - `message`: one event inside a thread - `lease`: an exclusive worker claim for a thread - `artifact`: a path or file reference attached to a message - `event`: a monotonic record used to wake blocking waiters - `read cursor`: the last thread message an agent has explicitly consumed ## Required Fields ### Thread Fields - `thread_id` - `run_id` - `task_id` - `subject` - `created_by` - `assigned_to` - `status` - `priority` - `created_at` - `updated_at` ### Message Fields - `message_id` - `thread_id` - `from_agent` - `to_agent` - `kind` - `summary` - `body` - `payload_json` - `created_at` ## Message Kinds - `task` - `progress` - `question` - `answer` - `result` - `control` - `event` ## Thread Status Values - `pending` - `claimed` - `in_progress` - `blocked` - `done` - `failed` - `cancelled` ## Worker Protocol The normal worker flow is: 1. `fetch` candidate threads 2. `claim` one thread 3. `update --status in_progress` 4. continue with `update` messages as needed 5. if blocked, set `blocked` and ask a precise question 6. wait for a reply with `inbox wait-reply` 7. finish with `done` or `fail` Rules: - `fetch` does not grant ownership - only `claim` grants ownership - only one active lease may exist per thread - blocked messages must say exactly what is missing - terminal messages should include result or failure summary - a blocked worker should wait on a reply event rather than sleeping blindly ## CLI Surface The binary name is `inbox`. ### Global Flags - `--db PATH` - `--json` - `--agent NAME` ### `inbox init` Initialize the communication schema and SQLite pragmas. Example: ```bash inbox init --db .agents/coord.db ``` ### `inbox send` Create a new thread or append a message to an existing one. Suggested flags: - `--from AGENT` - `--to AGENT` - `--subject TEXT` - `--thread THREAD_ID` - `--run RUN_ID` - `--task TASK_ID` - `--kind task|question|answer|progress|result|control|event` - `--summary TEXT` - `--body TEXT` - `--body-file PATH` - `--payload-json STRING` - `--priority low|normal|high` - `--artifact PATH` - `--artifact-kind KIND` - `--artifact-metadata-json STRING` ### `inbox fetch` List candidate threads for an agent without claiming them. Suggested flags: - `--agent AGENT` - `--status pending,blocked` - `--limit N` - `--unread` `--unread` should use a per-agent thread read cursor, not a latest-message heuristic alone. ### `inbox claim` Acquire a lease on a thread. Suggested flags: - `--agent AGENT` - `--thread THREAD_ID` - `--lease-seconds N` ### `inbox renew` Extend an existing lease. Suggested flags: - `--agent AGENT` - `--thread THREAD_ID` - `--lease-seconds N` ### `inbox update` Append a progress or blocked update. Suggested flags: - `--agent AGENT` - `--thread THREAD_ID` - `--status in_progress|blocked` - `--summary TEXT` - `--body TEXT` - `--body-file PATH` - `--payload-json STRING` - `--artifact PATH` - `--artifact-kind KIND` - `--artifact-metadata-json STRING` ### `inbox reply` Reply inside an existing thread. Suggested flags: - `--from AGENT` - `--to AGENT` - `--thread THREAD_ID` - `--kind answer|question|progress|control` - `--summary TEXT` - `--body TEXT` - `--body-file PATH` - `--payload-json STRING` - `--artifact PATH` - `--artifact-kind KIND` - `--artifact-metadata-json STRING` ### `inbox done` Mark a thread complete and attach the final result. Suggested flags: - `--agent AGENT` - `--thread THREAD_ID` - `--summary TEXT` - `--body TEXT` - `--body-file PATH` - `--payload-json STRING` - `--artifact PATH` - `--artifact-kind KIND` - `--artifact-metadata-json STRING` ### `inbox fail` Mark a thread failed. Suggested flags: - `--agent AGENT` - `--thread THREAD_ID` - `--summary TEXT` - `--body TEXT` - `--payload-json STRING` - `--artifact PATH` - `--artifact-kind KIND` - `--artifact-metadata-json STRING` ### `inbox cancel` Cancel a thread. Suggested flags: - `--agent AGENT` - `--thread THREAD_ID` - `--reason TEXT` - `--artifact PATH` - `--artifact-kind KIND` - `--artifact-metadata-json STRING` ### `inbox list` List threads with filters. Suggested flags: - `--agent AGENT` - `--status pending,claimed,in_progress,blocked,done,failed,cancelled` - `--created-by AGENT` - `--assigned-to AGENT` - `--limit N` ### `inbox show` Show one thread with full message history. Suggested flags: - `--thread THREAD_ID` - `--json` - `--mark-read` `show` should include per-message artifact references when present. ### `inbox watch` Block until new matching activity appears. Suggested flags: - `--agent AGENT` - `--status pending,blocked,done,failed` - `--timeout-seconds N` ### `inbox wait-reply` Block until a new reply-like message appears for one thread. This is the normal wait primitive for a blocked worker. Suggested flags: - `--thread THREAD_ID` - `--after-message MESSAGE_ID` - `--after-event EVENT_ID` - `--kinds answer|control|result` - `--timeout-seconds N` Behavior: - waits until a later matching message exists in the thread - returns the new message and associated event cursor - avoids blind `sleep` loops in worker logic ## JSON Contract Every command should support `--json`. Suggested success shape: ```json { "ok": true, "command": "claim", "thread": { "thread_id": "thr_123", "task_id": "T4", "status": "claimed", "assigned_to": "backend-worker" } } ``` Suggested error shape: ```json { "ok": false, "error": { "code": "lease_conflict", "message": "thread already claimed by another worker" } } ``` Suggested `wait-reply` wake shape: ```json { "ok": true, "command": "wait-reply", "woke": true, "next_event_id": 127, "message": { "message_id": "msg_901", "thread_id": "thr_123", "kind": "answer", "summary": "Use email/password for MVP", "body": "Use a simple credential flow for the first iteration." } } ``` ## Exit Codes - `0`: success - `10`: no matching work - `20`: conflict such as lease contention - `30`: invalid input or invalid state transition - `40`: not found - `50`: storage or internal error ## SQLite Schema Draft ```sql CREATE TABLE IF NOT EXISTS threads ( thread_id TEXT PRIMARY KEY, run_id TEXT NOT NULL, task_id TEXT NOT NULL, subject TEXT NOT NULL, created_by TEXT NOT NULL, assigned_to TEXT NOT NULL, status TEXT NOT NULL, priority TEXT NOT NULL DEFAULT 'normal', latest_message_id TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS messages ( message_id TEXT PRIMARY KEY, thread_id TEXT NOT NULL, from_agent TEXT NOT NULL, to_agent TEXT NOT NULL, kind TEXT NOT NULL, summary TEXT NOT NULL, body TEXT NOT NULL DEFAULT '', payload_json TEXT NOT NULL DEFAULT '{}', created_at TEXT NOT NULL, FOREIGN KEY(thread_id) REFERENCES threads(thread_id) ); CREATE TABLE IF NOT EXISTS leases ( thread_id TEXT PRIMARY KEY, agent_id TEXT NOT NULL, lease_token TEXT NOT NULL, claimed_at TEXT NOT NULL, expires_at TEXT NOT NULL, released_at TEXT ); CREATE TABLE IF NOT EXISTS artifacts ( artifact_id TEXT PRIMARY KEY, message_id TEXT NOT NULL, path TEXT NOT NULL, kind TEXT NOT NULL, metadata_json TEXT NOT NULL DEFAULT '{}', created_at TEXT NOT NULL, FOREIGN KEY(message_id) REFERENCES messages(message_id) ); CREATE TABLE IF NOT EXISTS thread_reads ( thread_id TEXT NOT NULL, agent_id TEXT NOT NULL, last_read_message_id TEXT NOT NULL, last_read_at TEXT NOT NULL, PRIMARY KEY(thread_id, agent_id), FOREIGN KEY(thread_id) REFERENCES threads(thread_id), FOREIGN KEY(last_read_message_id) REFERENCES messages(message_id) ); CREATE TABLE IF NOT EXISTS events ( event_id INTEGER PRIMARY KEY AUTOINCREMENT, run_id TEXT NOT NULL, task_id TEXT NOT NULL, thread_id TEXT NOT NULL, source TEXT NOT NULL, event_type TEXT NOT NULL, message_id TEXT, summary TEXT NOT NULL DEFAULT '', payload_json TEXT NOT NULL DEFAULT '{}', created_at TEXT NOT NULL ); CREATE INDEX IF NOT EXISTS idx_threads_status_assigned ON threads(status, assigned_to, updated_at); CREATE INDEX IF NOT EXISTS idx_messages_thread_created ON messages(thread_id, created_at); CREATE INDEX IF NOT EXISTS idx_events_thread_event ON events(thread_id, event_id); CREATE INDEX IF NOT EXISTS idx_thread_reads_agent ON thread_reads(agent_id, last_read_at); ``` ## Concurrency Notes - use `PRAGMA journal_mode=WAL` - keep write transactions short - make `claim` atomic - never mutate state during `fetch` - implement `watch` and `wait-reply` as blocking queries over message or event cursors, not as user-managed `sleep` ## Embedded Skill Draft The following block is a draft `SKILL.md` for the `inbox` skill. ````markdown ```markdown --- name: inbox description: Use this skill when an agent needs durable communication through the local inbox CLI. It is for fetching work, claiming a thread, sending progress updates, raising blocked questions, waiting for replies, replying inside a thread, returning results, and watching inbox activity. Do not use it for task decomposition or scheduling decisions; use the orchestrator skill for that. --- # Inbox Use this skill when you need to communicate through the `inbox` CLI and its SQLite-backed thread store. ## When To Use - a worker needs to fetch and claim work - a worker needs to report progress - a worker is blocked and must ask for clarification - a leader needs to inspect or manually reply inside a thread - an agent needs durable, machine-readable coordination instead of ad hoc chat ## Rules - Prefer `--json` when another agent will consume the output. - Never treat `fetch` as ownership; only `claim` grants ownership. - Do not start work without a valid lease. - When blocked, say exactly what is missing. - If blocked, prefer `inbox wait-reply` instead of manual sleep loops. - Use `result` for final output and `fail` for terminal failure. - Keep scheduling decisions out of this layer. ## Typical Commands ```bash inbox fetch --agent backend-worker --status pending --json inbox claim --agent backend-worker --thread thr_123 --lease-seconds 900 --json inbox update --agent backend-worker --thread thr_123 --status in_progress --summary "Implementing post CRUD routes" --json inbox update --agent backend-worker --thread thr_123 --status blocked --summary "Need auth decision" --payload-json '{"question":"Should admin auth use email/password in MVP?"}' --json inbox wait-reply --thread thr_123 --after-event 51 --timeout-seconds 1800 --json inbox reply --from leader --to backend-worker --thread thr_123 --kind answer --summary "Use email/password for MVP" --body "Use a simple credential flow for the first iteration." --json inbox done --agent backend-worker --thread thr_123 --summary "Post CRUD implemented" --body-file result.md --json ``` ``` ````