02 — System Architecture¶
Cites:
research/reference/02_lucasastorian_impl.md,research/reference/04_atomicmemory_compiler.md
One-screen view¶
~/.alexandria/
├── config.toml
├── state.db (SQLite)
├── secrets/ (encrypted creds)
└── workspaces/
├── global/ (user general knowledge)
│ ├── SKILL.md
│ ├── raw/
│ └── wiki/
└── <project>/
├── SKILL.md
├── raw/
└── wiki/
▲
│
┌──────────────┴────────────────┐
│ │
┌─────────┴─────────┐ ┌─────────┴─────────┐
│ alexandria CLI │ │ alexandria daemon │ (optional)
│ │ │ │
│ init, project, │ │ - sync scheduler │
│ source, sync, │ │ - subscriptions │
│ ingest, query, │ │ - local web UI │
│ lint, export │ │ - MCP endpoint │
└─────────┬─────────┘ └─────────┬─────────┘
│ │
└─────────────┬─────────────────┘
▼
┌────────────────────┐
│ Guardian Agent │ (Claude via MCP,
│ (LLM client) │ or CLI `alexandria chat`)
└────────────────────┘
Three entry points, one data directory:
alexandriaCLI — the primary user surface. All operations (init, add source, sync, ingest, query, lint, export) are subcommands.alexandria mcp serve --workspace <slug>— a stdio MCP server. The primary integration path for agents: Claude Code, Cursor, Codex CLI, Windsurf, and Claude Desktop launch it as a subprocess and speak MCP over stdin/stdout. No daemon required. One process per session, per workspace. See08_mcp_integration.mdfor the full tool surface and client configs.alexandria daemon— a long-running local process. Optional. Runs scheduled syncs, polls subscriptions, serves a local web UI athttp://localhost:<port>, and exposes MCP over HTTP+SSE (for Claude.ai web and any client that prefers network transport). Not started by default;alexandria daemon startenables it.
Components¶
| Component | Tech | Responsibility |
|---|---|---|
| CLI | Python + typer |
Init, workspace/project management, source config, sync triggers, ingest/query/lint shortcuts, export |
| Core library | Python 3.12+, pydantic, sqlalchemy core (or asyncpg for sqlite via aiosqlite) |
Filesystem layout, SQLite schema, source adapter runtime, agent glue |
| Daemon | uvicorn + fastapi + apscheduler |
Scheduled syncs, subscription polling, web UI, MCP server |
| Web UI | Server-rendered HTML (Jinja) or a small HTMX interface | Dashboard, source status, wiki viewer, subscription inbox. No write surface — agent-only. |
| MCP server | fastmcp |
Tools the agent calls: guide, search, read, write, delete, sources, subscriptions, history. Runs as stdio subprocess or HTTP via the daemon — same code, two transports. |
| State store | SQLite (~/.alexandria/state.db) |
Source adapters, sync runs, subscription queues, search indexes, provenance, structured log |
| Object storage | Filesystem under raw/ |
PDFs, images, originals. Large blobs live next to their metadata. |
One process per entry point (CLI invocation or daemon). Both share the same SQLite file; WAL mode keeps concurrent reads + occasional writes safe.
Why SQLite + filesystem instead of Postgres¶
- Personal tool. Zero ops. No services to run. No containers to start.
pip install alexandria && alexandria initand you're done. - Files are the primary artifact. The user can
git init ~/.alexandria/workspaces/customer-x, open it in Obsidian, back it up with Dropbox. SQLite stays consistent because it's a derivative — rebuildable from the filesystem. - Search works. SQLite FTS5 covers keyword search up to several thousand pages per workspace. Combined with
grep(regex) andlist(glob) primitives, it gives the agent everything it needs to navigate without an embedding pipeline. - No tenancy to enforce. No RLS, no
user_idcolumns, no scoping middleware. The only boundary is workspace — which is literally a directory path.
Why a daemon is optional¶
Everything the daemon does, the CLI can also do on demand (alexandria sync, alexandria subscriptions poll, alexandria query). The daemon exists to enable:
- Scheduled syncs (cron every N minutes).
- Newsletter/Twitter polling.
- Live web UI.
- MCP endpoint for Claude/Cursor.
Users who don't want a background process can skip it and run everything from the CLI. The daemon is the same Python code, just invoked by apscheduler on a timer and served over HTTP.
Request flow examples¶
alexandria init¶
alexandria init [--path ~/.alexandria]
↓
Creates ~/.alexandria/{config.toml, state.db, secrets/, workspaces/global/}
Creates workspaces/global/{SKILL.md, raw/, wiki/}
Seeds wiki/overview.md, wiki/index.md, wiki/log.md with starter content
Runs SQLite migrations
alexandria project create customer-acme¶
Creates workspaces/customer-acme/{SKILL.md, raw/, wiki/}
Inserts workspaces row in SQLite
Prompts user for description
Seeds structural files
alexandria source add --workspace customer-acme --type notion¶
Prompts for Notion integration token, database IDs
Stores encrypted config in secrets/ and source_adapters row in SQLite
Does NOT sync yet — prints next step: `alexandria sync customer-acme`
alexandria sync customer-acme¶
Finds all source_adapters for the workspace
Runs each adapter's list → fetch cycle
Writes new/updated files to workspaces/customer-acme/raw/<adapter>/...
Records source_run row in SQLite with counts
Updates FTS5 index on new content
Prints summary: "Notion: 12 new, 3 updated. GitHub: 2 new."
User asks the agent to ingest¶
The agent is running in Claude.ai (or alexandria chat) and connected to the MCP endpoint (alexandria daemon start made it available). User says "ingest the new Notion pages."
Agent → guide(workspace="customer-acme") — reads current state
Agent → search(path="/raw/notion/*") — finds pending items
Agent → read(...) for each
Agent → write(create/append/str_replace) for wiki pages
Agent → write(append) to log.md
Every call lands on the local daemon, which writes directly to the workspace files and updates SQLite.
Deployment shape¶
There is no deployment. It's a local Python package:
pip install alexandria # or: pipx install alexandria
alexandria init # creates ~/.alexandria/
alexandria project create ... # optional
alexandria source add ...
alexandria daemon start # optional: web UI + MCP + scheduler
For power users: alexandria export --workspace X --format obsidian-zip produces a portable artifact.
Language and stack choices¶
- Python 3.12+ — user's required language.
typer— CLI with good typing and help text.fastapi+uvicorn— daemon HTTP surface.fastmcp— MCP server inside the daemon.apscheduler— in-process scheduler for syncs and subscriptions.aiosqlite+sqlite-utils— SQLite access. FTS5 for search.anthropic/openai/google-genai/ provider SDKs (optional) — only needed for daemon-owned scheduled synthesis and CLI batch operations. Interactive work happens in the MCP client which brings its own provider. See11_inference_endpoint.md.pydantic— config + data shapes.cryptography— encrypted secrets storage (symmetric key derived from OS keyring).
Dev tooling: mypy --strict, pytest, pytest-asyncio.
Concurrency model¶
The daemon is a single asyncio process. Concurrent source syncs run as asyncio tasks with per-adapter locks. SQLite writes are serialized through a write lock; reads are concurrent. The CLI and daemon may run at the same time because both use WAL mode — conflicts are rare and detected via busy_timeout.
Long-running sync jobs are moved off the main loop into a worker thread pool when they hit blocking I/O (git clone, large PDF extraction). LLM calls stay on the main loop; they're I/O bound.
What this model does NOT do¶
- No account system.
- No cloud backend.
- No shared wikis across machines (unless the user syncs
~/.alexandria/with Syncthing or git themselves — explicitly supported but not managed by us). - No cross-workspace queries at MVP.
- No server-side agent runs scheduled by us. (The daemon can schedule background lint, but it still talks to a local LLM — no remote compute.)
See 03_workspaces_and_scopes.md for the workspace model, 05_source_integrations.md for adapters, 06_data_model.md for the on-disk layout + SQLite schema.