generated from coulomb/repo-seed
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>
This commit is contained in:
@@ -107,6 +107,25 @@ Domains are now first-class DB entities. Use `list_domains()` to discover availa
|
||||
|
||||
---
|
||||
|
||||
## Agent Inbox Tools
|
||||
|
||||
Inter-agent coordination via shared message board. Check inbox at session start;
|
||||
send messages to coordinate across Claude instances.
|
||||
|
||||
Agent names: use the repo slug (e.g. `"marki-docx"`, `"railiance"`) or `"hub"` for the custodian agent.
|
||||
Use `"broadcast"` as `to_agent` to send to all agents.
|
||||
|
||||
| Tool | Key Args | When to use |
|
||||
|------|----------|-------------|
|
||||
| `get_messages(to_agent?, from_agent?, unread_only?, limit?)` | `to_agent`: your agent name; `unread_only`: True recommended at session start | Check for pending coordination messages. |
|
||||
| `send_message(from_agent, to_agent, subject, body, thread_id?)` | all except `thread_id` required | Send a coordination message to another agent (or broadcast). |
|
||||
| `mark_message_read(message_id)` | `message_id`: UUID | Mark a message as read after acting on it. |
|
||||
| `reply_to_message(message_id, from_agent, body)` | all required | Reply in-thread; marks original as read. |
|
||||
|
||||
Dashboard: `http://localhost:3000/inbox`
|
||||
|
||||
---
|
||||
|
||||
## Domain Slugs
|
||||
|
||||
Run `list_domains()` to get the live list. Default 6: `custodian` · `railiance` · `markitect` · `coulomb_social` · `personhood` · `foerster_capabilities`
|
||||
|
||||
@@ -1434,6 +1434,74 @@ def get_repo_dispatch(repo_slug: str) -> str:
|
||||
return json.dumps(_get(f"/repos/{repo_slug}/dispatch"), indent=2)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Agent Inbox (inter-agent message passing)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@mcp.tool()
|
||||
def send_message(from_agent: str, to_agent: str, subject: str, body: str, thread_id: str | None = None) -> str:
|
||||
"""Send a message from one agent to another (or 'broadcast' for all).
|
||||
|
||||
Use this to coordinate with other Claude instances — e.g. a worker agent
|
||||
reporting status back to the orchestrator, or the hub agent dispatching
|
||||
instructions to a domain agent.
|
||||
|
||||
Args:
|
||||
from_agent: Sender identifier (e.g. 'hub', 'marki-docx', 'railiance')
|
||||
to_agent: Recipient identifier or 'broadcast' for all agents
|
||||
subject: Short subject line (max 500 chars)
|
||||
body: Full message body (markdown supported)
|
||||
thread_id: UUID of the root message to create a thread (optional)
|
||||
"""
|
||||
payload: dict = {"from_agent": from_agent, "to_agent": to_agent, "subject": subject, "body": body}
|
||||
if thread_id:
|
||||
payload["thread_id"] = thread_id
|
||||
msg = _post("/messages/", payload)
|
||||
return json.dumps(msg, indent=2)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def get_messages(to_agent: str | None = None, from_agent: str | None = None, unread_only: bool = False, limit: int = 20) -> str:
|
||||
"""List messages in the agent inbox.
|
||||
|
||||
Call this at session start to check for pending coordination messages.
|
||||
|
||||
Args:
|
||||
to_agent: Filter by recipient (your agent name, or omit for all)
|
||||
from_agent: Filter by sender (optional)
|
||||
unread_only: Return only unread messages (default: False)
|
||||
limit: Maximum number of messages to return (default: 20)
|
||||
"""
|
||||
params: dict = {"limit": limit, "unread_only": unread_only}
|
||||
if to_agent:
|
||||
params["to_agent"] = to_agent
|
||||
if from_agent:
|
||||
params["from_agent"] = from_agent
|
||||
return json.dumps(_get("/messages/", params), indent=2)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def mark_message_read(message_id: str) -> str:
|
||||
"""Mark an inbox message as read.
|
||||
|
||||
Args:
|
||||
message_id: UUID of the message to mark as read
|
||||
"""
|
||||
return json.dumps(_patch(f"/messages/{message_id}/read", {}), indent=2)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def reply_to_message(message_id: str, from_agent: str, body: str) -> str:
|
||||
"""Reply to a message. Marks the original as read and creates a reply in the same thread.
|
||||
|
||||
Args:
|
||||
message_id: UUID of the message to reply to
|
||||
from_agent: Your agent identifier
|
||||
body: Reply body (markdown supported)
|
||||
"""
|
||||
return json.dumps(_post(f"/messages/{message_id}/reply", {"from_agent": from_agent, "body": body}), indent=2)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Entry point
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user