Architecture — Current State (v0.1.1, 2026-05-21)
Living snapshot. Update at every minor version cut. Last refresh: end of EXECUTION_MASTER_PLAN Phases 1-10.
Three-layer model
L3 INTERFACE CLI · Cockpit (React + PWA) · GitHub PRs
↑
L2 SERVICES agents · adapters · schedulers · WebSocket push
↑
L1 SUBSTRATE markdown + git (one repo per company)
The substrate is the load-bearing decision. Every other layer is swappable, but only because the substrate is just files.
Repository topology
Two distinct repos at runtime:
- The
Lycatosource repo (this workspace) — code that produces thelycatoCLI,@lycato/cockpitweb bundle, adapters, agents, the public@lycato/sdk. - The company repo — a separate folder per company, scaffolded by
lycato init. HoldsWHO/WHAT/WHEN,decisions/,journal/,money/,people/,inbox/,memory/,investors/,support/,content/,.founder/,Lycato/cache/.
The CLI and cockpit always operate against a company repo, never the source repo.
Source repo layout
packages/
schemas/ @lycato/schemas Zod schemas
core/ @lycato/core parse/write, git, money, paths, search,
attention, memory, provenance, reconcile,
policy, keys, index-cache, approvals,
runs, migrations, modules, llm/
sdk/ @lycato/sdk PUBLIC. Adapter/Agent/Module types +
defineX helpers + LlmClient re-export
templates/ @lycato/templates renderKernel + renderDemoSeed
adapters/
mercury, github, plain, visible, stripe-atlas, stripe
agents/
daily-digest, finance-close, investor-update, content-queue,
kpi-snapshot, decision-draft, notifier
apps/
cli/ @lycato/cli oclif CLI (`lycato` binary); topic dirs
cockpit/ @lycato/cockpit React 19 + Vite + Tailwind + PWA;
Fastify api/ with WebSocket
hosted/ (no package.json — not a workspace package)
web/ deployed hosted browser app + site shells
test/ vitest + Firebase-emulator suites
19 workspace packages. apps/hosted is not one of them: it is plain
browser JS copied into the docs-site output by scripts/build-docs.mjs.
Workspace via pnpm-workspace.yaml. ESM throughout (NodeNext,
verbatimModuleSyntax, exactOptionalPropertyTypes).
The installable npm boundary is the coordinated CLI runtime closure documented in
DISTRIBUTION.md; the CLI is not released independently of its Cockpit,
core, schema, template, adapter, and worker packages.
Core module surface (post-Phase-10)
| Module | Purpose |
|---|---|
parse / write |
gray-matter + Zod for typed markdown |
repo / paths |
repo root resolution + canonical paths |
git |
simple-git wrapper with prior-branch restore |
money / reconcile |
ledger parsing + Stripe↔Mercury reconcile |
search / index-cache |
on-disk index for cmd-K |
attention / memory / provenance |
founder-facing read helpers |
policy / approvals |
risk scoring + editable flag + approval markers |
action-digest |
create → approve → consume gate for scheduled agents that write company truth with no branch and no diff; backs lycato operator approvals / approve / deny |
agent-schedule / notify / backup |
cron slot matching + missed-minute catch-up · notification triggers and channels · portable lycato-backup-v1 archives |
keys |
BYO key registry in .founder/keys.env |
runs |
JSONL run-ledger in .founder/runs/YYYY-MM.jsonl |
migrations |
forward-only schema migrations + CURRENT_REPO_VERSION |
modules |
.founder/modules.yml + built-in saas/services/d2c |
telemetry |
opt-in local event log + recordRun / recordUsage helpers |
llm/{anthropic,openai,gemini,factory} |
provider-agnostic LLM client |
Company repo topology
WHO.md WHAT.md WHEN.md founder + product + obligations
decisions/ accepted/proposed decisions
journal/daily/ daily-digest output (agent-authored)
inbox/ unprocessed signals (+ POST /api/inbox writes here)
people/ pre-memory typed people
memory/people|companies|projects|topics/ file-per-entity memory
money/ledger/<yyyy-mm>.md normalised transactions
money/reconcile-<yyyy-mm>.md reconcile output
investors/<yyyy-mm>.md investor-update agent output
support/tickets/<id>.md Plain adapter output
content/queue/<date>-<channel>-<slug>.md content-queue agent output
cap-table/ equity pool + grants
.founder/config.yml company metadata
.founder/adapters/<name>.yml per-adapter config
.founder/keys.env BYO API keys (gitignored, chmod 600)
.founder/policy.yml risk-aware approval policy + editable flag
.founder/reviews/<branch>.yml approval markers
.founder/runs/<yyyy-mm>.jsonl run-ledger (gitignored by default)
.founder/modules.yml enabled modules registry
.co-md-version schema version stamp
Lycato/cache/index.json derived search index (gitignored)
Data-flow diagram (text)
External SaaS (Mercury, Stripe, GitHub, Plain, Visible, Stripe Atlas)
│ https (BYO key)
▼
adapter.client ─── injectable fetch ─── transform ─── pull
│ writes (idempotent by external id)
│ appendRun() → .founder/runs/YYYY-MM.jsonl
▼
Company repo (markdown files)
│ reads
▼
agent.run ─── LlmClient (Anthropic | OpenAI | Gemini) ─── writes draft
│ appendRun() → .founder/runs/YYYY-MM.jsonl
│ writes to a "proposal" path or branch
▼
chokidar watcher → /api/ws → cockpit useLive(kinds, refresh)
│ cockpit Review page expands inline diff (LCS)
│ optional inline edit (policy.editable) → POST /api/file commits
▼
Git diff
│ Founder approves via:
│ a) Cockpit "Approve" button → .founder/reviews/<branch>.yml
│ b) `lycato review approve <branch>` → .founder/reviews/<branch>.yml
│ then manual `git merge`
▼
Company truth (main branch)
CLI surface (v0.1.1)
lycato init [--demo]
lycato doctor # now reports schema version
lycato migrate [--dry-run] [--to <ver>] [--json] # NEW Phase 3
lycato search <query>
lycato morning [--auto-sync]
lycato obligations [-d <substr>]
lycato sync [--dry-run] [--only <step>] [--json]
lycato logs [--limit] [--kind] [--name] [--since] [--json] # NEW Phase 2
lycato audit [--provenance] [--json]
lycato reconcile [--month <n>] [--year <n>] [--dry-run]
lycato reindex
lycato close [--month <n>] [--year <n>] [--provider <p>]
lycato serve [--interval <sec>] [--skip-cockpit] [--skip-sync] # NEW Phase 9
lycato cockpit
lycato module list # NEW Phase 8
lycato module add <name> # NEW Phase 8
lycato module remove <name> # NEW Phase 8
lycato money summary | runway
lycato people add | list
lycato decide | decide list
lycato equity grant | pool | cap-table
lycato mem <key> | mem ls | mem find | mem write
lycato keys | keys set <name> <value>
lycato inbox | inbox view | inbox triage
lycato operator investor-update [--month] [--year] [--provider]
lycato operator content-queue [--provider]
lycato import notion | gmail-mbox | linear
lycato review [--base <branch>]
lycato review approve <branch> [--approver] [--note]
Cockpit API (v0.1.1)
| Method | Path | Returns |
|---|---|---|
| GET | /api/health |
{ ok, repoRoot, version } |
| GET | /api/attention |
AttentionSummary |
| GET | /api/summary |
balance + monthly net |
| GET | /api/decisions |
last 10 decisions |
| GET | /api/people |
all people |
| GET | /api/inbox |
sorted inbox |
| POST | /api/inbox |
NEW capture: { title, body?, source? } → inbox file |
| GET | /api/runs |
run-ledger entries (rewritten Phase 2) |
| GET | /api/review |
proposal branches with risk + approval state |
| POST | /api/review/:branch/approve |
record approval marker |
| GET | /api/review/:branch/diff |
NEW per-file diff (Phase 6) |
| GET | /api/adapters |
adapter YAML configs |
| GET | /api/memory |
entity list |
| GET | /api/memory/:kind/:id |
entity record |
| GET | /api/file?path= |
repo file (traversal-guarded, 256KB cap) |
| POST | /api/file |
NEW edit + commit on proposal branch (Phase 7) |
| GET | /api/search?q= |
NEW classified hits (Phase 4) |
| POST | /api/reindex |
NEW rebuild on-disk cache (Phase 4) |
| WS | /api/ws |
NEW chokidar-driven { kind, path, at } (Phase 5) |
Feature flag cockpit.live_updates: false in .founder/config.yml
disables the watcher + WS route.
Cockpit frontend additions
components/CmdK.tsx— cmd/ctrl+K modal with debounced search.components/DiffViewer.tsx— in-browser LCS line diff + per-file Edit mode that callsPOST /api/file.hooks/useLive.ts— singleton WS with exponential backoff.hooks/useApi.ts— extended withrefreshfor live invalidation.public/manifest.webmanifest+public/sw.js— installable PWA with network-first/api/*+ cache-first/assets/*.
Schema versions
CURRENT_REPO_VERSION = "0.1.1"(was 0.1.0 at v0.1.0 cut).- Registered migrations:
0001_baseline_v0_1_0(backfill version file),0002_runs_gitignore(append.founder/runs/to.gitignore). lycato doctorflags schema drift and recommendslycato migrate.
Test posture
- 265+ tests pass and growing (204/204 at the v0.1.1 cut). The
continuous-improvement loop adds coverage each iteration — see
docs/superpowers/plans/2026-06-22-co-md-improvement-loop.mdfor the live count. - v0.1.1 coverage: LLM client per-provider (9), run-ledger (8), migrations (9), modules (6), SDK types (3), cockpit search (4), cockpit WebSocket (2), cockpit diff (1), cockpit file write (4), cockpit inbox POST (3).
- E2E:
cli/test/e2e.test.tsstill passes (init → mercury pull → daily digest → morning). - Cockpit now has browser-side render tests (jsdom +
@testing-library/react, per-file@vitest-environment jsdom):AsyncState,Sidebar,ErrorBoundary- the Work / Company / Settings / Memory / Review pages (mocked
api).
- the Work / Company / Settings / Memory / Review pages (mocked
Build / deploy posture
pnpm buildrunstscper workspace package; cockpit also runsvite build. Post-v0.1.1 the bundle is code-split viamanualChunks(charts / react-vendor / vendor / app) with the recharts chartReact.lazy-loaded — first-paint critical JS ~86KB gzip.pnpm typecheckclean.biome check .clean.- CI: ubuntu/macos/windows × Node 22.
- Self-hosted: source-built
lycato serveruns the cockpit, sync loop, and scheduler on one host. No Docker/Compose/Caddy artifacts ship. Seedocs/deployment/SELF_HOSTING.mdfor the exact supported path.
Bottlenecks & risks today (refreshed)
Resolved since v0.1.0:
Single-LLM coupling→ Phase 1 (ADR-0015).No realtime updates→ Phase 5 (ADR-0019).Schema versioning is implicit→ Phase 3 (ADR-0017).No metrics / observability→ Phase 2 (ADR-0016) —lycato logsreads the JSONL ledger.
Still open:
Cockpit chunk is one ~612KB JS bundle.Resolved post-v0.1.1:vitemanualChunkssplits charts / react-vendor / vendor / app, and the recharts money chart isReact.lazy-loaded — first-paint critical JS dropped to ~86KB gzip.- Markdown-as-DB scales linearly. Index cache at v0.1.1 is JSON rebuilt on demand. Past ~10k entities → SQLite + watcher (deferred until founder pain emerges).
- No multi-tenant story in the CLI.
lycato serveis single-tenant. The deployed hosted slice (apps/hosted/web/) is separately multi-tenant — one uid-keyed tenant per signed-in founder, isolated byfirestore.rules— but it shares no code withlycato serveand there is no server component behind it. The container-per-tenant design indocs/hosted/MULTI_TENANT.mdis not built. - Trust boundary is a convention for adapters, not a guard.
Adapters could write to high-risk paths without a
proposal/branch. Policy is only enforced at review-time and at the cockpit/api/filewrite boundary. SDK consumers should follow the convention; we don't fence them in yet. - Cockpit is unauthenticated. Single-tenant assumption.
Hardening designed in
docs/hosted/AUTH.md; runbook atdocs/deployment/SELF_HOSTING.mdcalls out the reverse-proxy-or-Tailscale stop-gap. - Run-ledger telemetry is local-only. No upstream aggregation
yet — by design, see
docs/security/TELEMETRY_OPT_IN.md.
Forward maturity (next 3 minor versions)
- 0.2.x (shipping in v0.1.1 patch increments): Phases 1-7 already in main; iterate on cockpit perf + bundle splitting.
- 0.3.x: SDK ecosystem (3rd-party adapters), additional modules (services-pmm, d2c-shopify variants), encrypted memory paths via age/git-crypt.
- 0.4.x: hosted product. KMS-backed secrets, passkey auth,
Stripe billing, multi-tenant, managed backups. See
docs/hosted/. - 0.5.x: full Phase 10 hosted slice — Postmark inbound email, Whisper voice transcription, web push notifications.
(See strategy/ROADMAP.md for the planning document; this is the
architecture lens.)