# 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
```
```
````
