Architecture Overview
Status: Accepted Reference epics: INK-833, INK-837 ADRs: ADR-016, ADR-018, ADR-020
What Inklings is
Section titled “What Inklings is”Inklings is a local-first workbench for authoring a world — a worldbuilder’s term for the bounded body of content a person is building. The worldbuilder may be a fiction author building a setting, a researcher building a knowledge base, a product team building a design system, or anyone else whose work is the coherent, long-lived, authored accumulation of knowledge about a bounded subject.
The architecture is organized around three commitments that the rest of this document elaborates:
- A workspace is a world. Each workspace contains the author’s content plus the structural records that make the content a world rather than a bag of notes — provenance, derivation, deviation, lifecycle. A single workspace has a single World Agent that inhabits that world.
- Every write crosses the submit boundary. The workspace’s structural records are maintained by routing every write — whether the author typed it, the agent produced it, a scheduled task emitted it, or an import pipeline delivered it — through a single domain construct that captures origin, lifecycle, and derivation. This is an invariant of the domain model, not a convention. See submit-boundary and ADR-017.
- The World Agent runs in the Python sidecar on LangGraph. Agent reasoning, tool calls, scheduling, and checkpointing live in a single sidecar process built directly on LangGraph. The Rust side owns the domain, storage, and tools; the sidecar calls Rust tools over MCP; the submit boundary runs on the Rust side. See ADR-016.
For the world-model primitives — World Agent, zones, provenance, derivation, deviation, retroactive revision, the submit boundary, sandbox execution — read systems/world. That directory is canonical. This page describes how the code is organized to implement those primitives.
Design philosophy
Section titled “Design philosophy”Inklings is a polyglot modular monolith following Clean Architecture with compile-time layer enforcement via separate Rust crates. Dependencies flow inward: framework and infrastructure depend on application and domain, never the reverse.
Four foundational decisions shape the architecture:
- Rust owns the domain. All business logic, validation, and data integrity rules live in Rust. The TypeScript frontend is a “dumb pipe” that handles only UI/UX behavior. The Python sidecar holds the agent runtime but calls into Rust for every workspace-visible operation.
- SQLite per workspace. Each workspace is a single SQLite database file (
inklings.db) that holds content, structural records, and agent state together. Local-first by default, with optional cloud sync via Supabase. The separateagents.dbof earlier builds has been folded intoinklings.db; see Database Schema. - CRDT-backed content. Block content uses Loro CRDTs for persistent undo/redo and multi-device sync. The CRDT binary is the source of truth; materialized text is a derived projection for search indexing.
- Submit is the domain boundary. Workspace writes are only expressible in the domain through a single construct that carries origin, lifecycle, and (where applicable) derivation, deviation, and retroactive-revision impact. Every caller — Rust tool, Python tool via MCP, import pipeline, scheduled task — constructs the same world-write value on the Rust side. See Domain Rules for the invariants.
Clean Architecture layers
Section titled “Clean Architecture layers”The Rust agent harness (agent-core, agent-harness) has been removed. Agent execution — planning, tool dispatch, subagent orchestration, checkpointing, streaming — runs in the Python sidecar on LangGraph. The sidecar calls Rust through the MCP server; boundary-crossing tools declare their shape at registration and the adapter constructs the domain’s world write on their behalf. See ADR-016.
Domain layer (crates/domain/)
Section titled “Domain layer (crates/domain/)”Pure business entities with zero external dependencies. Every struct, enum, and validation rule here is framework-agnostic and infrastructure-agnostic.
Key contents:
- World primitives:
Origin,Lifecycle,DerivationLink,DeviationRecord,RevalidationFlag,WorldWrite - Entity types:
Page,Block,Tag,Workspace,Attachment,Bookmark,EventLogEntry,Participant,Channel,Conversation - Value objects:
WorkspaceName(filesystem-safe validation),Layout(CSS Grid template validation),IconConfig - Enums:
PageType,BlockContentType,Capability,EntityType,EventType - Domain errors and validated identifier newtypes
See the Entity Catalog for the auto-generated inventory and Domain Rules for the invariants.
Application layer (crates/application/)
Section titled “Application layer (crates/application/)”Use cases and service trait abstractions. Each use case is a single-purpose struct following the file-per-use-case pattern:
crates/application/src/page/├── mod.rs # Re-exports├── services.rs # PageRepository trait + errors├── get.rs # GetPageUseCase├── create.rs # CreatePageUseCase└── update.rs # UpdatePageUseCaseUse cases that write to the workspace take a WorldWrite value, not loose arguments. The world-write is constructed by the caller (command, MCP adapter, import pipeline, scheduled task) and validated at the domain layer before the use case executes. This is the mechanism that makes the submit boundary a domain invariant rather than a layer convention.
The application layer depends only on domain. Infrastructure is injected via trait objects (e.g., Box<dyn PageRepository>), enforcing the dependency inversion principle at compile time.
Infrastructure layer (crates/infrastructure/)
Section titled “Infrastructure layer (crates/infrastructure/)”Concrete implementations of application-layer traits:
| Crate | Purpose |
|---|---|
sqlite | SQLite storage — WAL mode, 1 writer + 4 reader pool, world primitives tables |
mcp-server | Rust MCP server; hosts all in-process tools (content, search, memory, sandbox) and carries submit-boundary registration metadata |
onnx | ONNX Runtime embedding (snowflake-arctic-embed-m-v2.0) |
supabase | Cloud services — auth, sync, analytics, realtime, storage |
llm-ingest | Provider SDKs for LLM-facing ingest work that runs inside Rust; the sidecar owns conversational LLM calls directly |
task-runner | Background task execution; dispatches scheduled work into the sidecar by thread_id |
import | External markdown import (Obsidian, Notion, AnyType, folders) — constructs world writes with origin: Imported, lifecycle: Draft |
sandbox | Wasmtime-sandboxed CPython executor, exposed as an MCP tool. Distinct from the agent runtime. See sandbox-execution and ADR-021. |
image | Image processing (resize, WebP conversion) |
json | JSON file-based settings repository |
Framework layer
Section titled “Framework layer”The framework layer encompasses the Tauri desktop shell, the React frontend, the HTTP bridge, and the Python sidecar — all of them outermost adapters translating between some external surface (user, test harness, agent loop) and the application core.
Commands (crates/commands/): Transport-agnostic validation, error sanitization, and shared types. Both Tauri and the HTTP bridge use the same command definitions. This crate strips internal error details before they reach the frontend, ensuring user-facing error messages are always safe and meaningful.
Tauri desktop app (apps/desktop/src-tauri/): Thin adapter layer that wires infrastructure implementations to application use cases via dependency injection (state.rs). Tauri commands are one-per-domain-area thin shim modules that delegate to commands/use cases.
React frontend (apps/desktop/src-react/): Single-page app with Zustand state management. Contains zero business logic — all data operations go through Tauri invoke() IPC calls. TipTap editor with Loro CRDT integration.
HTTP bridge (apps/http-bridge/): Axum REST server that replaces Tauri IPC for QA testing. Same command layer, different transport.
Python sidecar (apps/python-sidecar/): The World Agent runtime, built directly on LangGraph. Hosts the planning loop, subagent dispatch, tool-calling, streaming, checkpointing, and scheduling resumption. Calls Rust tools over MCP; carries no domain logic of its own. State is persisted in inklings.db via the LangGraph SQLite checkpointer. See ADR-016 and systems/agent/process-model.
Crate naming conventions
Section titled “Crate naming conventions”Crate names reflect their Clean Architecture layer and responsibility:
inklings-domain— The single domain crate (no prefix needed within)inklings-application— Use cases and service traitsinklings-commands— Shared validation and error sanitization (framework layer)inklings-sqlite,inklings-onnx, etc. — Infrastructure crates named by technologyinklings-mcp-server— Rust MCP server; hosts all in-process toolsinklings-sandbox— Wasmtime-sandboxed CPython executor
Removed names: inklings-agent-core and inklings-agent-harness no longer exist; the sidecar replaces them. See ADR-016 for the full rationale.
Type generation pipeline
Section titled “Type generation pipeline”Rust structs are projected to TypeScript via Specta:
Rust structs (with #[derive(Type)]) → Specta code generation → packages/contracts/generated/bindings.ts → TypeScript consumers import generated typesRun pnpm generate:types after modifying any Rust type that crosses the IPC boundary. Specta preserves snake_case field names (Rust storage_path → TypeScript storage_path, not storagePath).
The world primitives (Origin, Lifecycle, DerivationLink, DeviationRecord, RevalidationFlag) are exposed to the frontend so candidate-content UX, deviation inboxes, and re-validation queues can render without round-tripping descriptions.
System map
Section titled “System map”The application is organized into four functional groups of systems. Canonical world-model concepts live under systems/world/; the other groups describe how the code is organized to implement them.
World systems
Section titled “World systems”The canonical surface for world-model primitives. Every other system in the codebase cross-links up to these pages for primitive definitions.
| System | Description |
|---|---|
| World (index) | Orientation — what the world model is and how to read this directory |
| World Agent | Resident-inhabitant framing, six properties, author authority |
| World Zones | Three-zone topology (Known, Unknown, Unknowable) |
| Provenance | Origin, lifecycle, weight, standing |
| Derivation Links | Derivation relation and how it feeds standing |
| Deviation Records | Record-not-entity, four conflict types, triage |
| Retroactive Revision | Flag-not-rewrite queue, three options |
| Submit Boundary | Domain invariant + adapter mechanism |
| Sandbox Execution | Wasmtime CPython executor as capability, not runtime |
| Worked Example | Fiction-author magic-system walkthrough across the primitives |
Content systems
Section titled “Content systems”Core content management — pages, blocks, and the structures that connect them.
| System | Description |
|---|---|
| Page System | Core content CRUD, hierarchy, blocks |
| Block Content System | Block content types (Markdown, Image) |
| Attachment System | File attachments with SHA-256 dedup |
| Wiki-Link System | Wiki-link parsing, reference index, ghost detection |
| Tag System | Tag management with groups, FTS5, frontmatter bridge |
| Property System | Inline property references and autocomplete |
| Layout System | CSS Grid template areas with per-cell CRDT |
| Loro CRDT System | CRDT integration, binary passthrough, TipTap sync |
| Workspace System | Workspace initialization, switching, lifecycle |
| Type System | Type definitions, properties, container rules |
| Import System | External markdown and Obsidian/Notion/AnyType vault import; lands content as lifecycle: Draft through the submit boundary |
Agent systems
Section titled “Agent systems”How the World Agent is implemented. These pages describe participation; for the primitives the agent participates in, read systems/world.
| System | Description |
|---|---|
| Agent Core System | Sidecar runtime, LangGraph subgraph dispatch |
| Agent Memory System | Four-tier memory, MCP-exposed memory tools |
| Conversation System | Conversation and scratchpad lifecycle |
| LLM System | Provider SDKs called from sidecar nodes |
| MCP System | Rust MCP server, submit-boundary registration metadata |
| Process Model | Sidecar-as-single-Python-host; IPC surface |
| Skill System | Skill Composer as subagent subgraph; no compilation pipeline |
| Scheduling System | Category-based scope grants; checkpoint resumption on trigger |
| Prompt Injection Boundary | Runtime trust boundary (distinct from the submit boundary) |
Platform systems
Section titled “Platform systems”Infrastructure that supports all content and agent systems.
| System | Description |
|---|---|
| Analytics System | Pseudonymous offline-first analytics event queue |
| Auth System | Authentication via Supabase Auth |
| Bookmark System | Named anchors into the event log timeline |
| Embedding System | ONNX Runtime embedding pipeline |
| Event Log System | Continuous event log with named bookmarks; World Agent autonomous-task entries |
| Frontend System | React frontend architecture and IPC patterns |
| Model Downloader | Auto-download embedding model from HuggingFace |
| Permission System | Capability-based access control, with the World Agent as a participant |
| Python Sidecar Security | Sandbox hardening and trust boundary |
| Search System | FTS5 + semantic search with intent classification |
| Sync System | Multi-device sync via Supabase |
| Task Runner System | Background task execution; IPC triggers sidecar against thread_id |
| User Settings System | Application preferences |
| Write-Effect Coordinator | Side-effect dispatch for writes |
| Command Palette | Keyboard-driven command search |
| First Launch Experience | Onboarding tour |
Storage architecture
Section titled “Storage architecture”Per-workspace database
Section titled “Per-workspace database”Each workspace is a single SQLite database (inklings.db) using WAL mode with a connection pool (1 writer, 4 readers). It holds content, structural records, and agent state together. Key tables:
- Content —
pages,blocks(withcontent_loroBLOB source of truth),frontmatter,tags,page_tags,tag_groups,references,attachments,attachment_references - World primitives —
origin,lifecycle,derivation_link,deviation_record,revalidation_flag. Origin and lifecycle attach to every page and block write; the other three are first-class tables keyed into workspace content. - Type system —
types,properties,type_property_refs,page_type_assignments,container_rules,collection_views,layouts - Event history —
event_log,bookmarks,event_log_summaries - Search —
page_embeddings,pages_fts - Agent runtime —
channels,conversations,scratchpads,memory_entries,langgraph_checkpoints,langgraph_writes
The previously separate agents.db has been folded in. See Database Schema for the full schema reference and the migration that consolidates storage.
Settings
Section titled “Settings”Application settings stored as JSON (settings.json). Schema-versioned with migration support. The agents.db consolidation does not touch settings; application-wide preferences continue to live outside workspace databases.
Development environment
Section titled “Development environment”The app uses environment-aware paths to isolate dev/test data from production:
| Environment | Workspaces | Settings |
|---|---|---|
| Dev | {project}/.data/workspaces/ | {project}/.data/settings.json |
| Production | ~/Inklings/Workspaces/ | ~/Library/Application Support/Inklings/settings.json |
Debug builds (pnpm desktop:dev) use dev mode; release builds (pnpm desktop:build) use production mode.
Key architectural principles
Section titled “Key architectural principles”| Principle | Implementation |
|---|---|
| Dependencies flow inward | Separate crates enforce at compile time — infrastructure cannot import domain directly |
| Submit is a domain invariant | The only way to modify workspace content is to construct a WorldWrite; the domain rejects malformed ones |
| Agent runtime is LangGraph on the sidecar | One Python host, narrow IPC surface, checkpointer on workspace SQLite |
| Vertical slicing | Features implemented end-to-end through all layers, not layer-by-layer |
| CRDT binary passthrough | Never re-serialize a CRDT doc from materialized text — pass the binary through untouched |
| Frontend as dumb pipe | React contains no business logic — only UI/UX behavior |
| Identity vs auth vs analytics | Three distinct concerns with separate storage and lifecycle |
| Infrastructure naming discipline | Vendor-specific names (e.g., supabase_*) only in infrastructure layer |
Related
Section titled “Related”- Domain Rules — Business invariants enforced in Rust, including submit-boundary, origin immutability, lifecycle transitions, derivation-link constraints
- Entity Catalog — Auto-generated domain entity inventory
- Database Schema — Complete schema reference, including world primitives and LangGraph checkpointer tables
- Identifier Strategy — Three-tier identifier model: UUID, slug, and ref_code
- Error Handling — Error patterns across layers
- systems/world — Canonical world-model primitives
Was this page helpful?
Thanks for your feedback!