Siryous CRM — Architecture
This document describes the architecture of the production system, then notes what the public demo substitutes. Names, data, and examples here are fictional.
Design goals
- Local-first, single source of truth. One SQLite file on one machine. No cloud service, no accounts, no sync protocol — and therefore no staleness: every client reads and writes the same rows.
- AI drafts, human confirms. Model output never mutates the database directly. Every AI feature terminates in a review screen; the confirm action is an ordinary form post handled by an ordinary server action.
- Least-privilege AI. Each AI feature runs with the minimum read-only tool grant it needs (web search, or Zoom/Gmail/Calendar readers). No granted tool can send, write, or delete anything external.
- Boring web tech. Server components render from the database on every request; forms are plain HTML posting to server actions. No client state libraries, no ORM, no API layer between the UI and the data.
System overview
┌─────────────┐ ┌────────────────────┐ ┌────────────────┐
│ Web app │ │ Claude Desktop │ │ Claude Code │
│ (Next.js) │ │ via MCP server │ │ sessions in │
│ port 3000 │ │ (16 CRM tools) │ │ the repo │
└──────┬──────┘ └─────────┬──────────┘ └───────┬────────┘
│ │ │
▼ ▼ ▼
┌───────────────────────────┐
│ data/crm.db (SQLite) │
│ WAL mode · single writer │
│ source of truth │
└───────────────────────────┘
▲
│ spawn: claude -p (headless, JSON out)
┌─────────────┴──────────────────────────────┐
│ Built-in AI features (web app) │
│ ✨ Intake → tools: web search/fetch │
│ ✎ Log → tools: Zoom read OR Gmail read│
│ 🗓 Prep → tools: Calendar+Gmail+web │
└────────────────────────────────────────────┘
Components
Web app (Next.js App Router)
- Rendering: React Server Components; every page is
force-dynamicand queries SQLite synchronously at request time (better-sqlite3 is synchronous, which suits per-request server rendering well). - Writes: server actions (
lib/actions.ts) — complete/reopen action items, log interactions (which bumplast_touch), edit profile sections in place, move pipeline stages, create people/companies/deals. - Read layer:
lib/queries.ts— typed query functions (roster ordering, follow-up scoring, dashboard aggregation, cross-entity search, map data). - Schema: people, companies, interactions, action_items, opportunities,
connections, outreach, plus AI working tables (intake_drafts, log_drafts,
briefs). Idempotent
CREATE TABLE IF NOT EXISTSplus tolerant column migrations at startup.
MCP server (Claude Desktop path)
A small stdio MCP server registered with Claude Desktop exposes ~16 tools over the same database: search, full profile reads, agenda ("what's overdue?"), pipeline listing and updates, interaction logging, profile building/updating with arbitrary markdown sections, action-item management, and deal management. This makes conversational workflows ("log yesterday's call with the Atlas COO and mark the readout item done") first-class without opening the web app.
Built-in AI features
Each AI page spawns the locally installed Claude Code CLI in headless mode
(claude -p --output-format json), using the user's existing Claude
subscription — no API key. The prompt embeds the relevant CRM context and the
CLI is passed an explicit --allowedTools list:
| Feature | Context injected | Read-only tools | Output |
|---|---|---|---|
| Intake | Full roster (for dedup + connection-finding) | Web search/fetch | JSON: person, company, profile sections (markdown), connections, suggested actions, confidence flags |
| Log | Roster + open action items | Zoom readers or Gmail readers (per source) | JSON: dated entries per matched contact, next-action updates, resolved item ids, unmatched participants |
| Prep | Full dossier: profile, log, deals, open items | Calendar + Gmail readers + web | Markdown brief with fixed sections and inline source attribution |
Results are stored in draft tables with pending/ready/error status and
rendered on review screens where every field is editable. Confirming runs the
same code paths as manual entry. Two safety properties hold everywhere:
tool grants are read-only, and external content (emails, transcripts, web
pages) is treated as data to summarize, never as instructions.
Backups & export
The web app and the MCP server each ensure a daily online-backup snapshot of the database (newest 30 kept) — a backup happens on any day the data can change, because only those two processes change it. A separate script can regenerate a human-readable markdown archive (profiles, index, pipeline) on demand.
Data model highlights
- Status pipeline shared by people and companies: New → Exploring → Active → On hold → Client → Won / Lost / Dormant, each with a free-text status note.
- Urgency buckets for action items (overdue / log-gap / this-week / awaiting / high-potential / keep-warm / venture / data-gap) — the dashboard is largely a rendering of these buckets.
- Cadence scoring: each person can carry
cadence_days; the Keep Warm view computesdays_since_last_touch − cadenceand sorts by how overdue a touch is. Logging an interaction resets the clock vialast_touch. - Connections are person-to-person labeled edges ("made intro", "owed intro", "colleague") rendered by the force-directed map.
- Profile sections are markdown columns (relationship_intel,
business_context, engagement_strategy, notes, extra_sections) — the house
style is bullet lists like
- **How we met / origin:** …, editable in place.
What the public demo substitutes
| Concern | Production | This demo |
|---|---|---|
| Storage | SQLite (better-sqlite3), WAL | In-memory store (lib/store.ts) seeded from lib/seed.ts; resets when the serverless instance recycles |
| Queries | SQL in lib/queries.ts |
Same function signatures in plain TypeScript |
| Writes | SQL server actions | Same server actions mutating the in-memory store |
| AI runs | Headless Claude CLI, 1–4 min, live tools | Scripted simulations (lib/simulated.ts), ~3 s, clearly labeled |
| MCP server | Registered with Claude Desktop | Described but not included |
| Data | Real relationships | Entirely fictional dataset (16 people, 9 companies, 10 deals) with dates generated relative to "now" |
Everything else — pages, components, review screens, server-action flows — is the production code.