Skip to content
Documentation GitHub
Architecture

Architecture Overview

Inklings is a polyglot modular monolith following Clean Architecture with compile-time layer enforcement via separate Rust crates. The core principle: dependencies flow inward — framework and infrastructure layers depend on application and domain, never the reverse.

Three foundational decisions shape the architecture:

  1. 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.
  2. SQLite per workspace — Each workspace is a single SQLite database file (inklings.db). Local-first by default, with optional cloud sync via Supabase.
  3. 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.

Pure business entities with zero external dependencies. Every struct, enum, and validation rule here is framework-agnostic and infrastructure-agnostic.

Key contents:

  • Entity types: Page, Block, Tag, Workspace, Attachment, Bookmark, EventLogEntry
  • 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 complete auto-generated inventory.

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

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:

CratePurpose
sqliteSQLite storage — WAL mode, 1 writer + 4 reader pool
onnxONNX Runtime embedding (snowflake-arctic-embed-m-v2.0)
supabaseCloud services — auth, sync, analytics, realtime, storage
llmMulti-provider LLM abstraction via Rig crate
agent-coreCustom agent loop, process model, tool abstraction
agent-harnessAgent harness with skill execution, prompt engine, MCP bridge
task-runnerBackground task execution (event-driven + scheduled)
importExternal markdown import (Obsidian, folders)
imageImage processing (resize, WebP conversion)
jsonJSON file-based settings repository

In this architecture, the Framework Layer encompasses both the Tauri desktop shell and the React frontend, as both serve as the outermost adapter translating between user interaction and the application core. The React frontend follows a “dumb pipe” pattern — it contains no business logic and delegates all data handling to the Tauri backend via invoke() calls.

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 ~32 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.

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 traits
  • inklings-commands — Shared validation and error sanitization (framework layer)
  • inklings-sqlite, inklings-onnx, etc. — Infrastructure crates named by technology
  • inklings-agent-core, inklings-agent-harness — Agent subsystem crates

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 types

Run 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 application is organized into three functional groups of systems:

Core content management — pages, blocks, and the structures that connect them.

SystemDescription
Page SystemCore content CRUD, hierarchy, blocks
Block Content SystemBlock content types (Markdown, Image)
Attachment SystemFile attachments with SHA-256 dedup
Wiki-Link SystemWiki-link parsing, reference index, ghost detection
Tag SystemTag management with groups, FTS5, frontmatter bridge
Property SystemInline property references and autocomplete
Layout SystemCSS Grid template areas with per-cell CRDT
Loro CRDT SystemCRDT integration, binary passthrough, TipTap sync
Workspace SystemWorkspace initialization, switching, lifecycle
Type SystemType definitions, properties, container rules
Import SystemExternal markdown and Obsidian vault import

AI agent integration — the harness, LLM providers, and integration surface.

SystemDescription
Agent Core SystemAgent process model, session management
Agent Memory SystemDual-database memory with scope isolation
LLM SystemMulti-provider LLM abstraction
MCP SystemIn-process MCP server for agent integration
Process Model4-type agent process model + Context Pipeline
Skill SystemMulti-artifact skill packages and template engine
Scheduling SystemBackground task scheduling
Prompt Injection BoundaryTrust boundary enforcement

Infrastructure that supports all content and agent systems.

SystemDescription
Auth SystemAuthentication via Supabase Auth
Embedding SystemONNX Runtime embedding pipeline
Event Log SystemContinuous event log with named bookmarks
Frontend SystemReact frontend architecture and IPC patterns
Model DownloaderAuto-download embedding model from HuggingFace
Permission SystemCapability-based access control
Search SystemFTS5 + semantic search with intent classification
Sync SystemMulti-device sync via Supabase
Task Runner SystemBackground task execution
User Settings SystemApplication preferences
Write-Effect CoordinatorSide-effect dispatch for writes
Command PaletteKeyboard-driven command search
First Launch ExperienceOnboarding tour

Each workspace is a single SQLite database (inklings.db) using WAL mode with a connection pool (1 writer, 4 readers). Key tables:

  • pages — Page metadata, hierarchy (parent_slug), layout assignment
  • blocks — Block content: content (TEXT for FTS), content_loro (BLOB, CRDT source of truth)
  • tags, tag_groups, page_tags — Tag taxonomy
  • references — Wiki-link index for backlink tracking
  • attachments, attachment_references — File attachments with SHA-256 dedup
  • event_log — Structural change history
  • bookmarks — Named history anchors
  • embeddings — Semantic search vectors (768-dim, snowflake-arctic-embed-m-v2.0)

See Database Schema for the complete schema reference.

Agent memory uses two separate agents.db instances:

  • Account-level ({storage_dir}/agents.db) — User preferences and cross-workspace patterns
  • Workspace-level ({workspace_dir}/agents.db) — Workspace-specific knowledge

Scope isolation is enforced by database separation, not row-level filtering.

Application settings stored as JSON (settings.json). Schema-versioned with migration support.

The app uses environment-aware paths to isolate dev/test data from production:

EnvironmentWorkspacesSettings
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.

PrincipleImplementation
Dependencies flow inwardSeparate crates enforce at compile time — infrastructure cannot import domain directly
Vertical slicingFeatures implemented end-to-end through all layers, not layer-by-layer
CRDT binary passthroughNever re-serialize a CRDT doc from materialized text — pass the binary through untouched
Frontend as dumb pipeReact contains no business logic — only UI/UX behavior
Identity vs Auth vs AnalyticsThree distinct concerns with separate storage and lifecycle
Infrastructure naming disciplineVendor-specific names (e.g., supabase_*) only in infrastructure layer

Was this page helpful?