← All Diagrams
Group Chat — Multi-Agent Collaboration
Multiple AI agents (Claude, Codex, Gemini — or custom agents) work together on the same project directory.
Each agent is a long-lived subprocess that communicates via MCP tools served by a dedicated MCP server on :7892.
The GroupChatOrchestrator manages spawning, monitoring, and restarting agents.
Messages flow through an in-memory GroupChatStore with per-agent asyncio.Queue inboxes.
User (Web UI / API)
POST /api/group-chats/{id}/messages · GET /stream (SSE)
Group Chat API
web/api/group_chat.py
GROUP CHAT ORCHESTRATOR — web/group_chat.py
Agent Lifecycle
_spawn_agent() / _kill_agent()
Build system prompt from template
Backend.get_group_launch_command()
create_subprocess_shell()
Start stdout reader + stderr drainer
Write MCP configs per agent
Per-agent restart locks
Watchdog
_watchdog() asyncio.Task
Check every 10 seconds
Process exit detection
Heartbeat timeout check
(poll_timeout + 120s margin)
Auto-restart dead agents
Broadcast AgentStatus events
GroupChatStore
web/group_chat_store.py
In-memory message log (list[GroupMessage])
Per-agent asyncio.Queue inbox
post() → persist to DB + push queues
get_new_messages() → long-poll
SSE broadcast to subscribers
Reactions + read receipts
Output Reader
_read_agent_output() per agent
Read JSONL from agent stdout
backend.parse_entries() → events
Broadcast TextDelta, ThinkingDelta
Broadcast ToolInvocation events
Update agent status (listening/responding)
Extract + persist session_id, usage
store.post(sender="user")
Group Chat MCP Server :7892
web/group_mcp_server.py · FastMCP · BearerAuth · 127.0.0.1
get_new_messages (long-poll) · post_message · reply_to_thread · react_to_message
delegates to store
Claude Agent
claude -p --append-system-prompt --stream-json
Long-lived subprocess · Session resume via --resume
Lead Software Engineer
max_turns=200 · --dangerously-skip-permissions
Codex Agent
codex exec --json + AGENTS.md (system prompt)
Long-lived subprocess · danger-full-access sandbox
Senior Engineer (Implementation)
skip-git-repo-check · approval_policy="never"
Gemini Agent
gemini -p --yolo + .gemini/system-group-*.md
Long-lived subprocess · 1M context window
Senior Engineer (Analysis)
stdbuf -oL for line-buffered output
get_new_messages()
post_message()
reply_to_thread()
SHARED PROJECT DIRECTORY (same CWD)
All agents read/write the same codebase · File changes visible to all · Git operations shared · MCP configs written here
SSE → Browser
group_message · agent_status · text_delta · reaction
store.broadcast_raw()
Agent State Machine
idle
listening
responding
dead
spawn
text/tool output
get_new_messages()
exit/timeout
watchdog auto-restart (with context history + session resume)
Initial state
Waiting for messages
Producing output
Process exited
Agent Loop Pattern
Each agent runs an infinite MCP tool loop:
Call get_new_messages(conversation_id, agent_type) — blocks up to 600s
Read messages from all participants
If Human sent a message → MUST respond
If @mentioned → MUST respond
Post reply via post_message() or reply_to_thread()
Optionally react_to_message() (thumbs_up, fire, check, etc.)
Call get_new_messages() again — never stop the loop
Message Routing
User message → pushed to ALL agent queues (push_to_sender=True)
Agent message → pushed to all OTHER agent queues (not sender)
@mention parsed but all agents see all messages
Thread replies: thread_id="thread-{parent_sequence}"
Reactions: emoji set {thumbs_up, thumbs_down, fire, check, x, thinking}
Read receipts: tracked per-agent in group_read_receipts
SSE broadcast: GroupMessagePosted, MessageReaction, MessageReadReceipt
Watchdog & Recovery
Health check every 10 seconds in asyncio.Task
Heartbeat timeout = poll_timeout + 120s (generous margin)
Process exit detected via process.returncode is not None
Dead agents auto-restarted with last 20 messages as context
Session IDs persisted to group_agent_sessions for resume
Per-agent asyncio.Lock prevents concurrent restart races
Active group chats auto-resume on server startup (is_active=1)
Dynamic Agent Configuration
agent_definitions table: global reusable agent profiles
CrewAI-style identity: name, role, goal, backstory
group_conversation_agents: per-conversation lineup (2-10)
System prompt template with placeholders: {agent_name}, {agent_strengths}, etc.
Each backend injects system prompt differently:
Claude: --append-system-prompt
Codex: AGENTS.md file
Gemini: .gemini/system-group-*.md
Model override per-agent via AgentConfig.model
Output Reader Pipeline
Each agent has a dedicated _read_agent_output() asyncio.Task:
Read line from process.stdout with timeout
Extract session_id from init event (persist to DB)
backend.parse_entries() → ParsedEntry list
Update heartbeat timestamp on any output
Track agent status: get_new_messages → "listening", text/tool → "responding"
Broadcast TextDelta, ThinkingDelta, ToolInvocation events via store
Persist tool_calls to DB for history
Extract usage from "result" events → update group_agent_sessions
Files Involved
web/group_chat.py — GroupChatOrchestrator, AgentConfig, spawn/kill/restart
web/group_chat_store.py — GroupChatStore, message routing, SSE broadcast
web/group_mcp_server.py — FastMCP tools for agent communication
web/group_mcp_auth.py — BearerAuthMiddleware for :7892
web/api/group_chat.py — REST API routes for group chat
web/agent_definitions.py — CRUD for agent_definitions + per-conversation config
web/domain_events.py — GroupMessagePosted, AgentStatus, reactions, receipts
web/database.py — Schema, default prompt template, builtin agents
mcp_config.py — Write .mcp.json/.gemini/settings.json/.codex/config.toml