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, streamed JSON out)
┌─────────────┴───────────────────────────────────────┐
│ Built-in AI features (web app) │
│ ◎ Coach → tools: none (CRM data only) │
│ ✨ Intake → tools: web search/fetch │
│ ✎ Log → tools: Zoom read OR Drive read (Meet) │
│ 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, coach_sessions, coach_messages — the drafts carry a
progresscolumn for the live feed). IdempotentCREATE 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 |
|---|---|---|---|
| Coach | Week snapshot + full book of business + last session's commitments + career-design folder | None | Markdown advice with fixed sections; chat turns and session close return JSON (reply/summary + CRM updates) |
| 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, Drive readers (Meet transcripts / Gemini notes), 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; downloadable as an editable Word doc (markdown → docx) |
Runs use the CLI's streaming output (--output-format stream-json): every tool
call and thinking step is translated into a human-readable progress event and
mirrored into the draft row's progress column, which the page polls (via
/api/progress) to render a live activity feed; finished reviews keep a
collapsed recap of the same events. Results are stored in draft tables with
analyzing/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.
Coach is the deliberate exception to draft-then-confirm: chat turns may
complete open action items ("I sent it") or record explicit commitments as new
action items (tagged source='coach') — conservatively extracted, applied in a
transaction, and surfaced immediately as a 📌 receipt message in the
transcript. Closing a session generates a summary + commitments checklist
(coach_sessions, coach_messages tables) that is injected into the next
session's context for week-over-week accountability.
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, streamed | Scripted simulations (lib/simulated.ts), ~15 s, clearly labeled |
| Live activity feed | Real streamed events mirrored into progress, polled via /api/progress |
Pre-scripted timelines replayed on a clock through the identical feed component and polling route |
| Coaching sessions | Claude with the full CRM + career-design context (lib/coachai.ts) |
Scripted advice/replies/summaries (lib/coachsim.ts) that read the live demo store — completions and commitments genuinely update it |
| Word-doc briefs | lib/mdToDocx.ts (markdown → docx) |
Identical code path — this one is real |
| 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.