Architecture
How Echolect is wired.
A meeting has one fast path for live, in-the-moment answers, and slower side paths for screenshots, web research, speaker naming, summaries, and project memory. The live session only ever receives the new transcript delta; everything durable is a file on disk. Each box below is a real session, IPC channel, or artifact.
Meeting start — STT path & live-session spin-up
Starting a meeting opens the overlay, minimizes the dashboard, and spins up two persistent Deepgram streams (mic + system audio) alongside the warm live LLM session. Finalized lines are de-duplicated, speaker-labeled, written to transcript.md, and bridged to the UI.
flowchart TD
classDef llm fill:#241f3a,stroke:#8B7CF6,color:#d8d2fb;
classDef stt fill:#10302e,stroke:#54C7C0,color:#bdeae6;
classDef disk fill:#332a16,stroke:#E7B24C,color:#f0dca6;
classDef ext fill:#202531,stroke:#6b7280,color:#cdd2da;
MS["meeting:start"]
MS --> OVw["create overlay window"]
MS --> DASHmin["minimize dashboard"]
MS --> AP["new AudioPipeline"]
AP --> STF["transcript.md"]:::disk
AP --> APstart["pipeline.start()"]
MS --> SLS["startLiveSession"]:::llm
subgraph CAP["overlay renderer — audio capture"]
MIC["mic getUserMedia"]:::stt
SYS["system / screen-share audio"]:::stt
MIC --> DSm["downsample 16k mono"]:::stt
SYS --> DSs["downsample 16k mono"]:::stt
end
DSm -->|"audio:data (mic)"| AD["audio:data handler"]
DSs -->|"audio:data (system)"| AD
AD --> FEED["feedAudio()"]:::stt
APstart --> MC["micClient ⌁"]:::stt
APstart --> SC["systemClient ⌁"]:::stt
FEED --> MC
FEED --> SC
MC -->|"wss nova-3"| DGm["Deepgram"]:::ext
SC -->|"wss nova-3"| DGs["Deepgram"]:::ext
DGm --> ORr["onResult (mic)"]:::stt
DGs --> ORs["onResult (system)"]:::stt
ORr --> DEDUP{"dup of system? 50% / 4s"}:::stt
DEDUP -->|drop| X[" "]
DEDUP -->|keep| FIN
ORs --> FIN["final line — Me / Them"]:::stt
FIN --> TFILE["append transcript.md"]:::disk
FIN --> BR["bridge → overlay + dashboard"]
The warm live session — what context goes in
The live session starts pre-loaded: a behavior prompt, your personal context, the project's rolling context, and a read-only scope over the meeting directory and project codebase. It's warmed up once, then streams answers back to the overlay — and the same handles are reused by research.
flowchart LR
classDef llm fill:#241f3a,stroke:#8B7CF6,color:#d8d2fb;
classDef disk fill:#332a16,stroke:#E7B24C,color:#f0dca6;
classDef cfg fill:#1b2230,stroke:#3a4252,color:#cdd2da;
subgraph KNOW["system prompt"]
MP["mode prompt — project or general"]
PC["personal-context.md"]:::disk
PJ["project context.md"]:::disk
FL["files readable on demand (add-dirs)"]
end
KNOW --> SESS
SESS["live session ⌁ — fast model · cwd = meeting dir · Read / Grep / Glob · add-dir: meeting + codebase"]:::llm
SESS --> WARM["warm up — Reply: READY"]
SESS -->|stream| TOK["assist:token / assist:turn → overlay"]
GLOB["globals — liveSession · meetingDir · addDirs · knowledge"]:::cfg
SESS -.reused by research.-> GLOB
Asking — intents, the delta, screenshots & research
An intent or a typed question composes a turn from only the new transcript delta plus any buffered screenshot/research findings, then sends it to the warm session. Screenshots and research run in their own sessions so the live thread never blocks; their findings buffer in and fold into your next ask.
flowchart TD
classDef llm fill:#241f3a,stroke:#8B7CF6,color:#d8d2fb;
classDef disk fill:#332a16,stroke:#E7B24C,color:#f0dca6;
HOT["global hotkey"] --> PILL["intent pill — Answer / Suggest / Ask back / Explain"]
PILL --> ASK["ask()"]
TYPE["typed question"] --> ASK
ASK --> BC["buildContext()"]
BC --> D1["transcript delta since last cursor"]
BC --> D2["unsent screenshot marks"]
BC --> D3["unsent research marks"]
D1 --> COMP["compose turn"]
D2 --> COMP
D3 --> COMP
COMP -->|"assist:send"| LSND["liveSession.send()"]:::llm
LSND -->|tokens| BUB["answer bubble (streams)"]
SHOT["screenshot hotkey / pill"] --> GRAB["grab frame from screen stream"]
GRAB --> SVPNG["screenshot.png"]:::disk
SVPNG --> ANA["analyze — fast model · Read / Grep / Glob"]:::llm
ANA --> SMARK["buffer screenshot mark"]
SMARK -. next ask .-> D2
RES["research toggle / command"] --> DOR["doResearch()"]
DOR --> RSESS["new session — heavy model · WebSearch / WebFetch / Read"]:::llm
RSESS -->|"research:token"| RBUB["research bubble"]
RSESS --> RMARK["buffer research mark"]
RMARK -. next ask .-> D3
Meeting stop — reconcile, summarize & roll up
On stop, the side processes tear down, then two heavy passes run in the background: speaker reconciliation rewrites the transcript with real names, and the summary is generated and written. For project meetings, the summary is then merged and condensed into context.md — leaving your own notes untouched.
flowchart TD
classDef llm fill:#241f3a,stroke:#8B7CF6,color:#d8d2fb;
classDef disk fill:#332a16,stroke:#E7B24C,color:#f0dca6;
STOP["meeting:stop"]
STOP --> KILL["stop pipeline · live session · overlay · restore dashboard"]
STOP --> REC["reconcileSpeakers — heavy model · rewrite transcript with real names"]:::llm
REC --> GS["generateSummary — heavy model · cwd = meeting dir"]:::llm
GS --> RD["reads transcript + context + chat + screenshots"]:::disk
RD --> FMT["TITLE + summary markdown"]
FMT --> WSUM["write summary.md"]:::disk
FMT --> RNAME["auto-apply AI title — rename meeting"]
FMT --> NOTI["notify dashboard — summary ready"]
FMT --> ISP{"project meeting?"}
ISP -->|yes| UPC["updateProjectContext — heavy model · no tools"]:::llm
UPC --> MRK["read rolling-summary block"]
MRK --> REWRITE["merge + condense newest summary"]
REWRITE --> WCTX["write context.md — your notes untouched"]:::disk
Every session at a glance — model, scope & tools
Echolect routes work through distinct sessions, each with the right model tier, working directory, read scope, and toolset. Live and screenshot work use a fast model to stay responsive; research, summaries, and rollups use a heavier model. Either runs on the Claude or OpenAI Codex CLI.
flowchart LR
classDef llm fill:#241f3a,stroke:#8B7CF6,color:#d8d2fb;
L1["LIVE ASSIST ⌁ — fast · cwd = meeting · add-dir: meeting + codebase · Read / Grep / Glob"]:::llm
L2["RESEARCH • — heavy · cwd = meeting · + WebSearch / WebFetch"]:::llm
L3["SCREENSHOT • — fast · cwd = meeting · Read / Grep / Glob"]:::llm
L4["SUMMARY • — heavy · cwd = meeting · Read / Grep / Glob"]:::llm
L5["CONTEXT ROLLUP • — heavy · cwd = project · no tools"]:::llm
L6["AI EDIT ⌁ — dashboard · caller-supplied dirs / tools"]:::llm
Want the real thing?
Every box above is in the source — read it, or run it yourself.