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 →
Entity Tracking
LLM-based entity extraction identifies people, projects, places, organizations, and topics from conversations. Entities are linked to memories via mention records, enabling entity-aware memory retrieval. Fast substring detection at retrieval time avoids extra LLM calls.
Open Loop Tracking
Post-turn implication analysis detects unresolved items (tasks, follow-ups, commitments) and tracks them as open loops. The system also infers implicit facts and stores them as low-confidence memories.
Session Briefing
When a new conversation starts, Edward generates a continuity summary of what happened between sessions — new memories stored, scheduled events fired, orchestrator tasks completed, triage activity, and consolidation results. Pure DB queries, no LLM call needed.
Proactive Intelligence
Autonomous background services that operate on their own schedules (all disabled by default):
- Morning briefing — daily summary with calendar, open loops, and activity
- Meeting prep — calendar-based preparation briefs with entity context
- Open loop nudges — daily reminders for stale unresolved items
- Pattern detection — weekly analysis of conversation themes and entity trends
Read the full proactive intelligence 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
- Proactive services — morning briefing, meeting prep, nudges, patterns
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 |