Web Chat Flow (Single Agent)
A user sends a message through the Web UI. The FastAPI server persists it, writes MCP configs for the conversation, spawns a CLI subprocess via the AgentExecutor, reads JSONL from stdout, converts lines to domain events, streams them as SSE to the browser, and persists messages/tool calls/usage to SQLite.
Browser Next.js SPA chat-detail.tsx Conversations API FastAPI route handler api/conversations.py Agent Executor Subprocess manager agent_executor.py Backend ClaudeBackend etc. backends/*/backend.py CLI Process claude -p stdout PIPE POST run_agent() get_cmd() spawn 1. POST /messages 2. INSERT user msg → DB 3. Write MCP configs _write_mcp_configs_for_conversation() 4. Build shell command backend.get_launch_command() 5. create_subprocess_shell 6. JSONL stdout lines 7. parse_entries() → events 8. Persist events + yield SSE 9. INSERT assistant msgs INSERT tool_calls + UPDATE 10. SSE → Live UI render 11. UPDATE conversation stats tokens, cost, session_id SSE Events: started · text_delta · thinking · tool_use_start · tool_result · completed · error

Components Involved

  • web/api/conversations.py — send_message() route
  • web/agent_executor.py — run_agent() async generator
  • backends/*/backend.py — command building + JSONL parsing
  • web/domain_events.py — event dataclasses + SSE serialization
  • web/database.py — get_db() for persistence
  • mcp_config.py — write MCP configs before agent launch

Session Resumption

On the first message, the agent starts fresh. On subsequent messages, the stored backend_session_id is passed to get_resume_command() which adds --resume {id} to the CLI command, preserving the full conversation context.

If a resume fails (hung subprocess), a per-read timeout of 300s kills it and reports an error.

MCP Server Discovery

Before launching the agent subprocess, _write_mcp_configs_for_conversation() queries the DB for enabled MCP servers (global + per-conversation overrides) and writes .mcp.json, .gemini/settings.json, and .codex/config.toml in the project directory. The CLI automatically discovers these on startup.

Cancellation

POST /api/conversations/{id}/cancel calls cancel_agent() which sends SIGTERM to the process group. The event generator detects agent.cancelled=True, emits an error event, and the SSE stream closes. Any still-running tool_calls are marked as cancelled in the DB.