12 KiB
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
inboxdirectly - leaders use
inboxmainly 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 conversationmessage: one event inside a threadlease: an exclusive worker claim for a threadartifact: a path or file reference attached to a messageevent: a monotonic record used to wake blocking waitersread cursor: the last thread message an agent has explicitly consumed
Required Fields
Thread Fields
thread_idrun_idtask_idsubjectcreated_byassigned_tostatusprioritycreated_atupdated_at
Message Fields
message_idthread_idfrom_agentto_agentkindsummarybodypayload_jsoncreated_at
Message Kinds
taskprogressquestionanswerresultcontrolevent
Thread Status Values
pendingclaimedin_progressblockeddonefailedcancelled
Worker Protocol
The normal worker flow is:
fetchcandidate threadsclaimone threadupdate --status in_progress- continue with
updatemessages as needed - if blocked, set
blockedand ask a precise question - wait for a reply with
inbox wait-reply - finish with
doneorfail
Rules:
fetchdoes not grant ownership- only
claimgrants 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.
Built-in help should be sufficient for first use:
- root help should explain the worker role and the normal fetch -> claim -> update -> wait-reply -> done/fail loop
- command help should explain when to use the command, not just list flags
- high-frequency commands should include concrete examples that can be copied directly
listhelp should explain how it differs fromfetchshowhelp should explain that it is the thread-history inspection commandwatchhelp should explain how it differs fromwait-reply
Global Flags
--db PATH--json--agent NAME
inbox init
Initialize the communication schema and SQLite pragmas.
Example:
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
sleeploops in worker logic
JSON Contract
Every command should support --json.
Suggested success shape:
{
"ok": true,
"command": "claim",
"thread": {
"thread_id": "thr_123",
"task_id": "T4",
"status": "claimed",
"assigned_to": "backend-worker"
}
}
Suggested error shape:
{
"ok": false,
"error": {
"code": "lease_conflict",
"message": "thread already claimed by another worker"
}
}
Suggested wait-reply wake shape:
{
"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: success10: no matching work20: conflict such as lease contention30: invalid input or invalid state transition40: not found50: storage or internal error
SQLite Schema Draft
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
claimatomic - never mutate state during
fetch - implement
watchandwait-replyas blocking queries over message or event cursors, not as user-managedsleep
Embedded Skill Draft
The following block is a draft SKILL.md for the inbox skill.
```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 orch 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
```
```