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

6.1 KiB

id, type, title, domain, repo, status, state_hub_workstream_id, created, updated
id type title domain repo status state_hub_workstream_id created updated
CUST-WP-0015 workplan Agent Inbox — Inter-Agent Coordination via State-Hub custodian the-custodian done 382c2e8a-28db-4db9-8c89-8bc2fea5159a 2026-03-16 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

unreadread → (optional) archived

Thread support: replies carry the original message's id as thread_id, grouping a conversation.

Schema

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

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

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

id: CUST-WP-0015-T03
status: done
priority: high
state_hub_task_id: "e847b88b-2549-4f02-94c8-e855f5ac6dde"

Add to mcp_server/server.py:

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

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

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 ✓