# 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:

1. **The `Lycato` source repo** (this workspace) — code that produces the
   `lycato` CLI, `@lycato/cockpit` web bundle, adapters, agents, the public
   `@lycato/sdk`.
2. **The company repo** — a separate folder per company, scaffolded by
   `lycato init`. Holds `WHO/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`](./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 calls `POST /api/file`.
- `hooks/useLive.ts` — singleton WS with exponential backoff.
- `hooks/useApi.ts` — extended with `refresh` for 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 doctor` flags schema drift and recommends `lycato 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.md` for 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.ts` still 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`).

## Build / deploy posture

- `pnpm build` runs `tsc` per workspace package; cockpit also runs
  `vite build`. Post-v0.1.1 the bundle is code-split via `manualChunks`
  (charts / react-vendor / vendor / app) with the recharts chart
  `React.lazy`-loaded — first-paint critical JS ~86KB gzip.
- `pnpm typecheck` clean.
- `biome check .` clean.
- CI: ubuntu/macos/windows × Node 22.
- **Self-hosted**: source-built `lycato serve` runs the cockpit, sync loop,
  and scheduler on one host. No Docker/Compose/Caddy artifacts ship. See
  `docs/deployment/SELF_HOSTING.md` for 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 logs`
  reads the JSONL ledger.

Still open:

1. ~~**Cockpit chunk is one ~612KB JS bundle.**~~ Resolved post-v0.1.1:
   `vite` `manualChunks` splits charts / react-vendor / vendor / app, and
   the recharts money chart is `React.lazy`-loaded — first-paint critical
   JS dropped to ~86KB gzip.
2. **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).
3. **No multi-tenant story in the CLI.** `lycato serve` is single-tenant.
   The deployed hosted slice (`apps/hosted/web/`) is separately
   multi-tenant — one uid-keyed tenant per signed-in founder, isolated by
   `firestore.rules` — but it shares no code with `lycato serve` and there is
   no server component behind it. The container-per-tenant design in
   `docs/hosted/MULTI_TENANT.md` is not built.
4. **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/file` write boundary. SDK consumers should follow the
   convention; we don't fence them in yet.
5. **Cockpit is unauthenticated.** Single-tenant assumption.
   Hardening designed in `docs/hosted/AUTH.md`; runbook at
   `docs/deployment/SELF_HOSTING.md` calls out the
   reverse-proxy-or-Tailscale stop-gap.
6. **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.)
