Architecture¶
Rill separates domain contracts, runtime decisions, external effects, and UI
state. Package.swift defines the module graph; AppBootstrap assembles the
concrete implementations.
Speech recognition and text input are the product capabilities. Workflows are a reusable composition layer for those capabilities; they do not own microphone, input-method composition, or model resource lifetimes. Future input-method entry points should reuse vocabulary, transformation and delivery contracts while retaining their own input lifecycle.
Run cards join content-free receipts with optional text history and run-scoped
diagnostics. Recording length comes from captured audio; selected process steps
and output actions retain monotonic elapsed milliseconds. A step's TOML
record_duration controls measurement in both receipts and text history. Missing
measurements remain absent, including older receipts and unexecuted branches.
Module dependencies¶
Arrows point from a consumer to its dependencies.
flowchart TD
App[RillApp] --> UI[RillUI]
App --> Runtime[RillRuntime]
App --> Persistence[RillPersistence]
App --> Providers[RillProviders]
App --> Platform[RillPlatform]
App --> Core[RillCore]
UI --> Runtime
UI --> Core
Runtime --> Core
Persistence --> Core
Providers --> Contracts[RillSpeechContracts]
Providers --> Core
Platform --> Core
Contracts --> Core
Worker[RillSpeechWorker] --> MLX[RillMLXRuntime]
Worker --> Contracts
Worker --> Core
MLX --> Contracts
MLX --> Core
| Module | Responsibility |
|---|---|
| Core | Values, validation, privacy contracts, and ports such as GlobalInputSource, SettingsStore, and RecordGraphPersistenceStore. |
| Runtime | Session execution, authorization, Record graph commands, and resource lifetimes against Core ports. |
| Platform | macOS input, clipboard, focus, files, credentials, and other system adapters. |
| Providers | Recognition clients, text transformation, and external output implementations; no macOS adapter dependency. |
| SpeechContracts | Worker wire values, streaming contracts, and pinned local model manifests shared by host and worker. |
| Persistence | SQLite connection, schema migration, encryption, and transactional repositories. |
| UI | Observable presentation state, feature models, and SwiftUI/AppKit views. |
| App | Composition, application lifecycle, and window/controller integration. |
| MLXRuntime / SpeechWorker | Local ASR, TTS, and record-embedding execution in separate supervised helper processes. |
Runtime receives system effects through injected ports or closures. For example,
RecordingSessionManager owns the validity of a
RecordingCueToken, while App supplies the haptic effect. The adapter calls
performIfValid at the synchronous effect boundary, after reaching the main
actor, so cancelling a recording suppresses a cue still waiting to be played.
State and lifetime ownership¶
| State | Owner | Boundary |
|---|---|---|
| Records, memberships, routes, leases, and persistence revision | RecordStore |
Commands commit before publishing catalog updates or collection events. |
| SQLite connection and transactions | SQLitePersistenceStore |
Settings, history, and catalog extensions share one actor and connection. A transaction never suspends between statements. |
| Active recording and its cleanup | RecordingSessionManager |
Cancellation invalidates cue tokens and retains pending work until it settles. |
| Authorized workflow run | SessionCoordinator |
Frozen workflow/context and resolved provider plan remain attached to one run. |
| UI settings reads | AppModelSettingsReadTaskOwner |
Replaced reads remain owned until drained; shutdown rejects new reads. |
| UI persistence tasks | PersistenceWriteCoordinator |
Replacement writes serialize per setting key; all accepted tasks remain tracked until completion. |
| Unsaved settings and retry policy | SettingsPersistenceModel |
Latest-write completion updates visible state; failures retain the exact value to retry. AppSettingsCodec owns stored-value decoding and migration. |
| Voice presentation | VoiceRunModel |
Progress is accepted only from the currently presented run and lane. |
| History browsing | RunHistoryModel |
Read sessions, page locators, deep links, and privacy-scoped queries have one owner. |
| Vocabulary | VocabularyLibraryModel |
Collection mutations and legacy-rule projection update the runtime source together; persisted default bindings retain their scope and uses. |
| Terminal run body and receipt | WorkflowRunReceiptRecorder |
Runtime freezes both values and commits them in one generation and one transaction; UI only reloads or displays an explicit session-only result. |
| Timed-out external work | BoundedOperation |
A cancelled caller does not free the resource slot until the underlying operation returns. Shutdown seals and drains accepted work. |
| Workflow files and enabled state | XDGWorkflowFileStore and WorkflowLibraryModel |
External editors own text editing. File writes check the last loaded source before replacement; file observation reloads validated definitions. |
Settings writes and reads have different shutdown contracts. Reads can be
cancelled and sealed. Accepted writes must finish, including older writes whose
storage implementation ignores cancellation. PersistenceWriteCoordinator
therefore waits for a replaced write before starting the next value for that
key, ignores stale completion results, and drains tasks added during a flush.
Unrelated keys can progress independently. Store errors become feature presentation
state; the task owner does not know about localization, credentials,
or workflow availability.
SQLite repository extensions divide queries by domain while preserving the connection's transaction boundary. Splitting them into independent actors would require a new transaction contract, particularly for history clear barriers and Record migration. File size alone is not a reason to introduce that separation.
AuthorizedLiveAudioSession shares the capture admission boundary between held
recording and workflow-controlled recording: it validates the run and audio
lifetime, monitors revocation, rechecks immediately before capture, and starts
context preparation only after capture begins. Gesture and window state remain
with their respective controllers.
EventBus bounds each subscriber buffer and diagnostic tail. Consecutive
presentation updates coalesce; lifecycle boundaries apply backpressure. Terminal
run, history, and final presentation updates retain admission even when their
producer is cancelled. Shutdown drains producers while consumers are alive,
then observes the delivery barrier before closing the UI consumer.
The speech model pool owns model loading. UI selection and explicit preparation use the injected trusted catalog; legacy custom model keys are migration inputs, not a second active configuration or automatic preparation path.
Workflow representation¶
Workflow TOML under the XDG configuration directory is the durable source of
truth. WorkflowDocument is the parsed value; external text editors own editing.
The application manages activation and templates through WorkflowFileStore.
The codec owns TOML
syntax, Core owns plan validation, and WorkflowPlanCompiler resolves runtime
providers and vocabulary. Validated process steps become an immutable enum-based
execution plan, so the interpreter does not repeatedly interpret optional DTO fields.
Output configuration is validated and frozen by action position, including repeated
action kinds with different destinations. WorkflowTextExecutor executes the
compiled steps; SessionCoordinator owns admission and output delivery, and
WorkflowRunDiagnostics records execution progress. Manifest checks use the same
semantic compiler as execution.
Legacy workflow migration is resumable by workflow ID. Existing TOML files are preserved, missing files are created with an explicit missing-file expectation, and the legacy library remains active until every definition is accounted for. The protected legacy settings retain a recovery copy after successful migration. Partially created files retain their source expectations on reload; deleting or replacing a workflow checks for external edits and saves the accepted old source as a private recovery version.
WorkflowPlan.acceptingTextInput() is the shared projection for Record replay,
explicit clipboard-text runs, and audio-to-text projection. It removes the root
recognition/resolution stages and speech route while preserving step identities,
branches, vocabulary, and output policy. It leaves malformed nested speech
steps available for validation to reject. Projection produces a new value;
existing workflow and run snapshots remain unchanged.
Explicit text runs retain outputs and use the normal authorized execution path. The compiler can validate a text projection while retaining the original declaration for run receipts.
See Record architecture for graph invariants and workflow TOML for the file format.
Verification¶
Run just ci for the repository hooks, release build and packaging checks, and
complete test suite. scripts/test.sh runs domain tests with four workers and
native platform, UI, and application tests serially. CI executes the same suite
through preflight.sh. Build modes and cache boundaries are defined in the
contribution guide.
check_module_boundaries.py checks SwiftPM dependencies and compiler-reported
imports. Optional just test-render and just test-stress capture rendering and
large-catalog evidence separately from normal gates.
Boundary regressions use controllable stores and suspended operations to verify ordering, cancellation, and shutdown behavior. Physical Fn input, haptics, microphone use, paste, and accessibility still require the device checks in the release QA checklist.