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:
@@ -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