Architecture map

~4 min read

Architecture map

A 1500-foot view of how Lettuce is put together.

Modules

┌───────────────────────────────────────────────┐
│                    Lettuce (app + CLI)         │
│  CLI parsing • UI views • run loops             │
├───────────────────────────────────────────────┤
│                  LettuceServer                  │
│  Hummingbird 2 HTTP • MCP JSON-RPC • REST •     │
│  Auth • VaultService • EditCoordinator          │
├───────────────────────────────────────────────┤
│                   LettuceKit                    │
│  Renderer • LinkIndex • TagIndex •              │
│  DocsVault • PublishConfig •                    │
│  GitDeployer • SiteBuilder                      │
├───────────────────────────────────────────────┤
│   swift-markdown │ Yams │ Hummingbird │ sqlite3 │
└───────────────────────────────────────────────┘

Dependencies are strictly downward — Lettuce depends on LettuceServer and LettuceKit; LettuceServer depends on LettuceKit; LettuceKit depends only on external packages.

LettuceKit (the engine)

Markdown/MarkdownRenderer.swift — Markdown → HTML via swift-markdown, with custom visitors for:

Vault/LinkIndex.swift[from-path → [to-path, link-text, link-type]]. Built in parallel from a vault scan. ~100ms cold for 6000 notes.

Vault/TagIndex.swift[tag → [paths]] with nested-tag traversal. Built alongside link index.

Vault/VaultWatcher.swift — FSEvents-based incremental updates that drive re-indexing.

DocsVault.swift — see Building from source for the SwiftPM resource model. Loads Docs/ from the bundle, materialises to Application Support, version-stamps.

Publish/PublishConfig.swift, Publish/SiteBuilder.swift — static site generation.

Publish/GitDeployer.swiftlettuce deploy plumbing.

Markdown/HTMLSanitizer.swift — defends against user-content XSS. Whitelisted tags only; live disallowed tags are escaped.

LettuceServer (the agent surface)

LettuceHTTPServer.swift / Router.swift — Hummingbird 2 app. Routes for:

There is no SSE / streaming endpoint — every MCP call is a single request/response over POST.

MCP.swift — tool registry. Each tool is dispatched through a Swift switch on the method name (no runtime registry, no reflection).

Auth.swift — derives caller identity from the request:

Proposals.swift (in LettuceKit) — owns .lettuce/proposals/, one JSON file per proposal. A proposeOnly agent’s write tools land here instead of mutating the vault; the user accepts (re-running the write through the normal commit path, after a staleness check) or rejects (deleting the file) in the GUI. Operations: list, load, save, delete, staleness.

EditCoordinator.swift (actor) — tracks advisory soft-locks (files the user is actively editing get a 409 on agent writes) and records recent agent edits.

VaultService.swift (actor) — single owner of vault state. Every file read/write/move goes through here. Provides atomic file ops, full re-index, and path-safety (rejects .. and absolute paths outside the vault).

SchedulePoller.swift — on lettuce serve, fires due schedules every 60s (the headless equivalent of the app’s timer).

Lettuce (the app)

The SwiftUI/AppKit app. Layers:

CLI subcommands live in Sources/Lettuce/Main.swift — a simple switch on the first argument dispatches to runServe, runPublish, runDocs, runRender, etc. Each subcommand reads from LettuceKit and / or starts LettuceServer.

Concurrency model

There are no DispatchQueues. There is no manual lock primitive (besides advisory soft-locks tracked in EditCoordinator).

Storage

Data Where Format
Notes <vault>/*.md Markdown + YAML frontmatter
Sidebar order <vault>/.lettuce/order.json JSON
Triggers <vault>/.lettuce/triggers.yml YAML
Schedules <vault>/.lettuce/schedules.yml YAML
Lint state <vault>/.lettuce/lint-state.json JSON
Embeddings <vault>/.lettuce/vectors.json, <vault>/.lettuce/passages.json JSON
Proposals <vault>/.lettuce/proposals/*.json JSON
Agent-edit timeline <vault>/.lettuce/agent-edits.json JSON
Agent tokens macOS Keychain system
Provider keys macOS Keychain system
App preferences UserDefaults system
Docs vault ~/Library/Application Support/Lettuce/Documentation/ Markdown

There’s no on-disk SQLite database for vault metadata. sqlite3 is used by SearchIndex.swift for an in-memory FTS5 full-text index that’s rebuilt on vault open and live-reload. All persisted state is plain files.