Files
the-custodian/workplans/CUST-WP-0015-agent-inbox.md
tegwick ac34c062df feat(CUST-WP-0015): implement agent inbox for inter-agent coordination
Adds a message-passing layer to state-hub so Claude instances can
coordinate across sessions without polling shared progress events.

- Migration f3a4b5c6d7e8: agent_messages table with thread support
- FastAPI router: POST/GET /messages/, thread view, mark-read, archive, reply
- 4 MCP tools: send_message, get_messages, mark_message_read, reply_to_message
- Observable dashboard: /inbox page with unread/read/archived sections + KPI
- CLAUDE.md updates: global, custodian, marki-docx, activity-core, template
- TOOLS.md: Agent Inbox tools section documented

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 02:55:45 +01:00

200 lines
6.1 KiB
Markdown

---
id: CUST-WP-0015
type: workplan
title: Agent Inbox — Inter-Agent Coordination via State-Hub
domain: custodian
repo: the-custodian
status: done
state_hub_workstream_id: 382c2e8a-28db-4db9-8c89-8bc2fea5159a
created: 2026-03-16
updated: 2026-03-16
---
# CUST-WP-0015 — Agent Inbox
## Problem
Two Claude instances (state-hub Claude on WSL2, worker Claudes on coulombcore
and other repos) share the state-hub as a blackboard but have no way to
address messages directly to each other. Progress events are broadcast with
no recipient; dispatch is pull-only. There is no lightweight protocol for a
worker to say "I need a decision from you" or for the state-hub Claude to say
"review this before proceeding".
## Goal
Add an agent inbox to the state-hub: a simple message-passing layer that lets
any Claude session send a structured message to a named agent, and any
receiving Claude to poll its inbox and reply. No real-time push required —
agents poll on their natural iteration cadence (ralph loop, session start).
## Design
```
Worker Claude State-hub State-hub Claude
─────────────────────────────────────────────────────────────────────────────
send_message(
to="state-hub",
subject="MRKD-WP-0001 T03 done — ready for review",
body="..."
) ──────────────────────────► agent_messages table
get_messages("state-hub")
◄──────────────────────
reply_to_message(id,
"Looks good, proceed")
──────────────────────►
get_messages("worker-marki-docx")
◄──────────────────────────────
```
### Agent naming convention
- `state-hub` — the custodian / state-hub Claude running locally
- `worker-<repo-slug>` — a Claude session working inside a specific repo
(e.g. `worker-marki-docx`, `worker-coulombcore`)
- `broadcast` — special recipient: all agents receive it
### Message lifecycle
`unread``read` → (optional) `archived`
Thread support: replies carry the original message's `id` as `thread_id`,
grouping a conversation.
## Schema
```sql
CREATE TABLE agent_messages (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
from_agent VARCHAR(100) NOT NULL,
to_agent VARCHAR(100) NOT NULL, -- or 'broadcast'
subject VARCHAR(500) NOT NULL,
body TEXT NOT NULL,
thread_id UUID REFERENCES agent_messages(id) ON DELETE SET NULL,
read_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT now()
);
CREATE INDEX ON agent_messages (to_agent, read_at);
CREATE INDEX ON agent_messages (thread_id);
```
---
## Task: Migration + model
```task
id: CUST-WP-0015-T01
status: done
priority: high
state_hub_task_id: "47035731-0d9b-42c0-ae87-29275046b1d8"
```
- Alembic migration: create `agent_messages` table (schema above)
- SQLAlchemy model `api/models/agent_message.py`
- Pydantic schemas `api/schemas/agent_message.py`:
`MessageCreate`, `MessageRead`, `MessageReply`
- Register model in `api/models/__init__.py`
---
## Task: API router
```task
id: CUST-WP-0015-T02
status: done
priority: high
state_hub_task_id: "17d61bc4-9793-48a4-929d-7c8ce0f03ba0"
```
`api/routers/messages.py`, prefix `/messages`:
- `POST /` — send message → `MessageRead` (201)
- `GET /?to_agent=<agent>&unread_only=false&limit=50` — inbox
- `GET /thread/{thread_id}` — full thread ordered by created_at
- `PATCH /{id}/read` — mark read (sets read_at=now)
- `PATCH /{id}/archive` — soft-delete (read_at stays, add archived_at)
Register in `api/main.py`.
Acceptance: `curl -X POST /messages/ -d '{"from_agent":"worker-marki-docx","to_agent":"state-hub","subject":"test","body":"hello"}'` returns 201.
---
## Task: MCP tools
```task
id: CUST-WP-0015-T03
status: done
priority: high
state_hub_task_id: "e847b88b-2549-4f02-94c8-e855f5ac6dde"
```
Add to `mcp_server/server.py`:
```python
send_message(to_agent, subject, body, from_agent="state-hub", thread_id=None)
get_messages(to_agent, unread_only=True, limit=20)
mark_message_read(message_id)
reply_to_message(message_id, body, from_agent="state-hub")
```
`reply_to_message` resolves the original message's thread root, posts a new
message with `thread_id` set, and marks the original as read in one call.
Update `mcp_server/TOOLS.md` with the new tools.
---
## Task: Dashboard inbox page
```task
id: CUST-WP-0015-T04
status: done
priority: medium
state_hub_task_id: "5af2a794-923a-4aa7-a00a-a7f4862e7b2c"
```
`dashboard/src/inbox.md`:
- Unread messages table: from, to, subject, age, thread indicator
- Mark-read button per row (POST to API)
- Thread view: clicking subject expands the reply chain inline
- KPI: unread count per agent (small summary bar at top)
Add to nav in `observablehq.config.js` after Repo Sync.
---
## Task: Session protocol update
```task
id: CUST-WP-0015-T05
status: done
priority: medium
state_hub_task_id: "5a80fc23-fc35-4dd5-833f-9280dddf6819"
```
Update CLAUDE.md files so agents know to check their inbox:
- `~/.claude/CLAUDE.md` (global): add `get_messages("state-hub")` to session start
- `state-hub/CLAUDE.md`: same, inside session protocol Step 1
- `marki-docx/CLAUDE.md`: add `get_messages("worker-marki-docx")` to session start
- `activity-core/CLAUDE.md`: add `get_messages("worker-coulombcore")` to session start
- Template (`state-hub/scripts/project_claude_md.template`): add inbox check
---
## Milestones
| # | Milestone | Tasks |
|---|-----------|-------|
| M1 | Messages in DB | T01 |
| M2 | Full API live | T02 |
| M3 | MCP tools available | T03 |
| M4 | Dashboard inbox | T04 |
| M5 | All agents check inbox on session start | T05 |
## Dependencies
- State-hub v0.5 dynamic domains — live ✓
- Repo sync / dispatch (CUST-WP-0014) — live ✓