14 KiB
02 — Architecture
Component map
┌─────────────────────────────────────────────────────────────────────┐
│ WebView2 window │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ Svelte frontend (TypeScript) │ │
│ │ views: MeetingsList · TranscriptNotes · SummaryPanel · │ │
│ │ Settings · SpeakerReview · CalendarView │ │
│ │ stores: recording, meeting, transcript, hardware, settings │ │
│ └───────────────▲───────────────────────────┬───────────────────┘ │
│ Tauri events (push) Tauri commands (request/response) │
└──────────────────┼───────────────────────────┼──────────────────────┘
│ │
┌──────────────────┴───────────────────────────▼──────────────────────┐
│ Rust core (Tauri) │
│ │
│ commands.rs ──orchestrates──► AppState { service handles } │
│ │
│ ┌─────────┐ ┌──────────┐ ┌────────────┐ ┌──────────────┐ │
│ │ audio │ │ hardware │ │transcription│ │ diarization │ │
│ │ (WASAPI)│ │(detect) │ │(whisper/onnx)│ │(sherpa-onnx)│ │
│ └────┬────┘ └────┬─────┘ └─────┬──────┘ └──────┬───────┘ │
│ │ │ │ │ │
│ ┌────▼────────────▼──────────────▼────────────────▼───────┐ │
│ │ storage (SQLite + files) notes (markdown) │ │
│ └────┬───────────────────────────────────┬───────────────┘ │
│ │ │ │
│ ┌────▼─────┐ ┌──────────────┐ ┌──────────┐ ┌─────▼──────┐ │
│ │ llm │ │ sync │ │ mcp │ │ calendar │ │
│ │(Ollama / │ │(WebDAV/OAuth)│ │ (server) │ │ (.pst/Graph)│ │
│ │ cloud) │ └──────┬───────┘ └────▲─────┘ └────────────┘ │
│ └────┬─────┘ │ │ loopback (INBOUND) │
└────────┼────────────────┼───────────────┼──────────────────────────────┘
│ HTTP/HTTPS │ HTTPS │ MCP over stdio / http://127.0.0.1/mcp
│ (local or │ (targets, │ (off by default; token; NOT egress)
│ cloud AI) │ off default) │
┌─────▼─────┐ ┌──────▼───────────┐ │ ┌──────────────────────────────┐
│ Ollama / │ │ Nextcloud·Seafile│ └───│ Claude · Codex · Copilot · │
│ Anthropic │ │ ·Cloudreve·… │ │ OpenCode (user's own agents) │
│ /OpenAI │ │ |OneDrive·Dropbox│ │ — they may forward to their │
└───────────┘ └──────────────────┘ │ own cloud (outside WA) │
└──────────────────────────────┘
The mcp arrow points inward: agents connect to WA, not the reverse. WA opens no socket for it.
Each Rust service lives in its own module under src-tauri/src/<service>/ and exposes a trait
plus a default implementation. Callers depend on the trait (NFR-MNT-1/2). The frontend never
touches files, DB, or network directly — only Tauri commands/events (04-api-contracts.md).
Services (responsibilities & key dependencies)
| Service | Responsibility | Primary crate(s) |
|---|---|---|
audio |
WASAPI loopback capture (+ optional microphone, mixed into the transcript stream, FR-CAP-7); PCM ring buffer; write WAV to disk; pause/resume | wasapi, hound |
hardware |
Enumerate NPU/GPU/CPU; rank backends; report capabilities | ort, DXGI via windows |
transcription |
Load model on a backend; stream segments (whisper.cpp) or NPU (ONNX) | whisper-rs, ort |
diarization |
Post-process audio → speaker spans; align to segments; merge | sherpa-onnx (FFI) |
storage |
SQLite index + file layout; CRUD; FTS search; retention; recovery scan | sqlx, serde_json |
notes |
Assemble Markdown from segments+speakers+summary; render/export | pulldown-cmark |
llm |
Talk to Ollama/custom endpoint; build prompts; stream; parse action items | reqwest, serde |
calendar |
Parse .pst events/attendees; (future) Graph/ICS sources |
outlook-pst |
sync |
Upload artifacts to user-configured targets; durable queue + retry; WebDAV + OAuth providers; OS-keychain creds | reqwest, reqwest_dav, keyring |
mcp |
Host a local (loopback) MCP server exposing meeting context as tools; build feature briefs; scope + token + audit | rmcp, axum/hyper |
The audio service also owns recording retention (ADR-0009): the working WAV is kept as the
permanent audio.wav only when "Record this meeting" is on; otherwise it is deleted on finalize.
The llm service is extended (ADR-0011, Layer 1) with optional hosted providers (Anthropic
Messages, OpenAI-compatible) behind the same LlmProvider trait, in addition to local Ollama. An
optional agent module (Layer 3, later) spawns local coding-agent CLIs and/or creates task-tracker
issues from feature briefs.
Data flow — a typical meeting
- User selects/creates a meeting (optionally from a calendar event) →
start_recording. audiobegins WASAPI loopback capture, writing PCM toaudio.wavcontinuously (FR-CAP-2), and pushes frames into an in-memory ring buffer.transcriptionconsumes windowed audio from the ring buffer on the selectedhardwarebackend and emits interimTranscriptSegments →transcript_segmentevents to the UI (FR-TRX-2).- Provisional speaker turns (from cheap segmentation) tag segments live; the user may name a speaker, applied immediately to past/future segments (FR-SPK-2).
- The notes view renders Markdown that updates as segments arrive (FR-NOTE-1, FR-NOTE-6).
- On
stop_recording: finalize the WAV; rundiarization, align speaker IDs to segments, apply name mappings; persist transcript JSON + metadata viastorage(FR-STORE-1). When the mic is on,audio.wavis a dual-channel split (left = mic, right = loopback; ADR-0005 Phase 3.5, FR-SPK), so attribution is per-stream: sherpa clusters the right channel only →Speaker N, and "You" comes from left-channel voice activity. Blind whole-signal clustering- voiceprint is the mic-off (
summed) fallback. If "Record this meeting" is off, delete the working WAV after the transcript is finalized; if on, keep it asaudio.wav(ADR-0009, FR-REC-1/4).
- voiceprint is the mic-off (
- If an LLM provider is configured:
llmbuilds a prompt (transcript + metadata + template), streams a summary/decisions/action-items into the summary panel (FR-LLM-2/4). - Action items are parsed and presented for confirmation; confirmed ones persist and may raise local OS reminders (FR-LLM-3, FR-CAL-5).
- If sync is configured and enabled,
storagefinalization enqueues the selected artifacts (transcript/notes/summary, and the recording if retained) onto thesyncqueue;syncuploads them to each target with retry/backoff and reports progress via events (FR-SYNC-3/5). Nothing is uploaded if no target is enabled (FR-SYNC-1).
Data flow — meeting → code (agent handoff, ADR-0011)
The "customer asks for a feature, get started right away" path, pull model:
- WA distills the relevant part of the transcript into a feature brief (via the configured
llm): problem, desired outcome, acceptance criteria, target repo/context, source meeting (FR-MCP-4). It is stored with the meeting and offered as a tool result. - The user has the
mcpserver enabled (off by default) with a chosen exposure scope (FR-MCP-1/3). - In their own agent (Claude Code, Codex, Copilot, OpenCode), the developer asks it to act;
the agent connects to WA's loopback MCP server and calls
get_feature_brief/get_action_itemsto pull structured context (FR-MCP-2), then scaffolds/branches/PRs in their repo. - WA logs what was read and reminds the user that the agent may forward served data to its own cloud (FR-MCP-5). WA itself sends nothing (FR-MCP-7).
- (Layer 3, later) Alternatively, WA pushes: an
AgentRunnerspawns a CLI agent headless against a repo, or WA opens a GitHub issue (optionally assigned to Copilot's cloud agent) from the brief (FR-AGENT-1/2).
Threading & concurrency model
- UI/IPC thread: Tauri commands return quickly. Nothing that runs for the length of a meeting blocks a command — long work is spawned and reports via events.
- Audio thread: a dedicated thread runs the WASAPI capture loop, waiting on the WASAPI event
handle (short timeout so it can also notice a stop/pause request) rather than busy-polling —
see
07-research-findings.mdfor why the earlier "polling only" note was corrected. It only does capture + disk write + ring-buffer push; no inference. - Inference worker(s): transcription runs on its own worker, pulling audio windows from the
ring buffer; diarization runs as a discrete post-stop job. Backends are constructed once per
session from the
hardwareranking. - Async runtime:
tokioforstorage(sqlx),llm(reqwest streaming), and orchestration. - State: a
tauri::State<AppState>holdsArc-wrapped service handles and aRecordingSessionguarded so start/stop/pause transitions are atomic. - Backpressure: the ring buffer is bounded; if transcription falls behind (CPU-only, large model), it drops to interim/skip strategy for the live view while the on-disk audio remains complete for an accurate post-meeting pass.
Idle behavior (NFR-RES-1)
When not recording, no audio thread, inference worker, or polling timer runs. Background indexing (FTS, retention) is event-driven or scheduled with idle-priority, and is reduced further under the "Low overhead" preset (NFR-RES-3). Models are unloaded from memory when idle and lazy-loaded on the next recording (NFR-PERF-4).
Error handling & resilience
- Each module defines a
thiserrorerror enum; commands surface typed errors to the frontend. - Hardware/backend failures degrade one tier at a time down to CPU (FR-HW-4, NFR-REL-2) and surface a non-blocking notice; they never abort a recording.
audiois the source of truth: a crash leaves a recoverableaudio.wav; on next launch a reconcile scan finds meetings with audio but no finalized transcript and offers recovery (FR-REL-1, NFR-REL-3).- PST parse failures and missing LLM providers are non-fatal: core capture/transcription/notes keep working.
Security boundaries (FR-SEC-1, NFR-SEC-*)
- WA opens sockets only to destinations the user configured: the LLM endpoint (local Ollama or, if chosen, a hosted AI provider — ADR-0011), enabled sync target hosts, a configured task tracker (Layer 3), and model-download hosts during an explicit download. These form an allowlist derived from settings; a privacy self-check (and a CI network test) asserts no connection to any off-allowlist host (NFR-SEC-3). With nothing configured, WA makes no content egress at all.
- MCP server (ADR-0011) is inbound, not egress. WA listens on loopback for the user's agents; it opens no outbound socket and adds nothing to the allowlist (FR-MCP-7/NFR-SEC-5). It is off by default, token-gated, scope-limited (never serves recordings unless allowed), and audited. The data-leaves-device disclosure for this path is "the agent you connected may forward served data to its own provider" — outside WA's control, like the remote-LLM caveat (ADR-0007).
- Sync is off by default and is an explicit user export (ADR-0010): credentials live in the OS credential store (never in settings/DB, FR-SYNC-6/NFR-SEC-4); TLS is required (plaintext only via explicit per-target LAN opt-in, FR-SYNC-7); third-party targets are clearly labeled (FR-SYNC-8).
- The WebView CSP also restricts
connect-srcto localhost, so the frontend itself cannot reach the network — all egress goes through the Rustsync/llmservices where the allowlist is enforced. - Data is stored under the user profile honoring ACLs; no admin rights required; optional at-rest encryption in Phase 8, optionally applied before upload so a sync target holds only ciphertext (FR-SYNC-10).
Portability seam (NFR-MNT-3)
Windows-specific code (WASAPI, DXGI enumeration, toast notifications, PST) sits behind the service traits. A future macOS/Linux port implements the same traits with platform equivalents (CoreAudio/PulseAudio capture, Metal/Vulkan backends, etc.) without changing callers or the frontend.