Architecture
How Edward is structured under the hood.
System Overview
Frontend (Next.js :3000) → Backend (FastAPI :8000) → PostgreSQL (:5432)
↓
LangGraph Agent
↓
Claude API (Anthropic)The frontend is a Next.js app that communicates with the FastAPI backend via REST and SSE (Server-Sent Events) for streaming. The backend manages all state in PostgreSQL, including conversation checkpoints, memories, documents, and configuration.
LangGraph Flow
Every message goes through a four-node LangGraph pipeline:
preprocess → retrieve_memory → respond → extract_memory → END- preprocess — normalizes the input, sets up conversation metadata
- retrieve_memory — searches pgvector for relevant memories using hybrid 70% vector + 30% BM25 scoring
- respond — calls Claude with the full context and tool bindings, enters a tool loop (up to 5 iterations)
- extract_memory — uses Claude Haiku to identify any memorable information from the conversation and stores it
Tool Loop
Inside the respond node, Edward can call tools iteratively:
LLM response
↓
tool_calls? ──yes──> execute tools
│ ↓
no add ToolMessage
↓ ↓
stream response loop (max 5x)This allows Edward to chain actions — for example, searching the web, reading the page content, then summarizing it — all within a single conversation turn.
Memory System
- Embedding model: sentence-transformers (
all-MiniLM-L6-v2, 384 dimensions) - Search: Hybrid 70% vector similarity + 30% BM25 keyword matching
- Memory types:
fact,preference,context,instruction - Extraction: Claude Haiku identifies memorable info after each turn
- Deep Retrieval: For complex conversations, runs 4 parallel queries (original + 3 Haiku-rewritten) for richer context
Read the full memory system deep dive →
Background Systems
Edward runs several background loops that operate independently of user conversations:
Heartbeat
Monitors iMessage, Apple Calendar, and Apple Mail for incoming items. Multi-layer triage classifies urgency: Layer 1 (zero-cost rules) → Layer 2 (Haiku classification) → Layer 3 (execute action). Can ignore, remember, respond, or push-notify based on classification.
Read the full heartbeat deep dive →
Reflection
Post-turn enrichment that generates 3-5 Haiku queries to find related memories. Results are stored and loaded on the next turn for deeper context. Runs asynchronously — no latency impact.
Consolidation
Hourly background loop that clusters related memories via Haiku. Creates connections between related memories and flags quality/staleness issues. Disabled by default.
Scheduler
In-process asyncio loop that polls every 30 seconds for due events. Executes scheduled events via the same chat_with_memory() function used in conversation — meaning Edward has full tool access when processing scheduled actions.
Orchestrator
Spawns lightweight worker agents (mini-Edwards) that run within Edward's process. Workers have full tool access, memory retrieval, and state persistence. Worker conversations appear in the sidebar with a distinct source tag.
Read the full orchestrator deep dive →
Key Directories
meet-edward/
├── frontend/ # Next.js app
│ ├── app/ # Pages and routes
│ ├── components/ # React components
│ └── lib/ # API client, context, utilities
├── backend/
│ ├── main.py # FastAPI app + lifespan
│ ├── routers/ # API route handlers
│ ├── services/
│ │ ├── graph/ # LangGraph agent (nodes, state, tools)
│ │ ├── execution/ # Code execution sandboxes
│ │ ├── heartbeat/ # Message monitoring + triage
│ │ ├── memory_service.py
│ │ ├── document_service.py
│ │ ├── tool_registry.py
│ │ ├── skills_service.py
│ │ └── ...
│ └── start.sh # Backend startup script
├── site/ # Marketing site (meet-edward.com)
├── setup.sh # First-time installation
└── restart.sh # Service managementStartup Order
The backend initializes components in a specific order (defined in main.py lifespan). The tool registry must come after all tool sources are initialized:
- Database + LangGraph — tables and checkpoint store
- Skills — load enabled states
- MCP clients — WhatsApp, Apple Services subprocesses
- Custom MCP servers — user-added servers from DB
- Tool registry — must be after all tool sources
- Scheduler — polls for due events
- Heartbeat — iMessage listener + triage loop
- Consolidation — hourly memory clustering
- Evolution — check for pending deploys
- Orchestrator — recover crashed worker tasks
All have matching shutdown hooks in reverse order.
SSE Event Protocol
The chat endpoint (POST /api/chat) streams structured events for real-time UI updates:
| Event | Description |
|---|---|
thinking | LLM is processing (between tool calls) |
tool_start | Tool execution beginning |
code | Code/query/command to execute |
execution_output | stdout/stderr from code execution |
execution_result | Code execution completed |
tool_end | Tool execution finished |
content | Text content from LLM response |
done | Stream complete |