Skip to content

Latest commit

 

History

688 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

LIA — Your life. Your AI. Your rules.

LIA

Smart multi-agent conversational assistant with LangGraph orchestration, Human-in-the-Loop, enterprise-grade observability, and full i18n support (6 languages)

If you find my project and work valuable, I would be grateful for a star on GitHub. Thank you !

Try LIA    GitHub Stars

Python 3.14 Node.js 24 LTS FastAPI Next.js 16 LangGraph LangChain 6 languages 360° technical audit: 8.3/10 on the normalized 24-area grid — full public report License Ask DeepWiki

FeaturesAdmin & MonitoringQuick StartArchitectureDocumentationContributing

Version 1.43.1A written debrief per relationship, and a register that no longer hides what LIA does on her own. A relationship card stacks ten sections; nobody reads ten sections. What a reader actually wants — where I stand with this person, and what to raise next — is a synthesis no aggregate produces, so one is now written per person, at most once per local day, and read by the chat when that person is named. Facing it, the record closed three holes it could not see: the effect register is fed by the tool gate and proactive work calls no tool, so 0 out-of-turn runs out of 228 over fourteen days left a trace while conversational surfaces scored 24/24 — "acts of her own initiative" was empty by construction, whatever she did. Nine surfaces that read through connector clients rather than tools recorded nothing either. And the download meant to hand that record over carried a row cap applied to the wrong variable: 49 195 real rows against 1 000 per source — 97,9 % of the inference record absent from every extraction, under a header that truthfully said "truncated", which repairs nothing. A server-side cursor replaces the cap: what is bounded is now the memory a download holds, never what it contains. — 7 September 2026.


Table of Contents


Why LIA?

LIA solves the fundamental problems of today's AI assistants:

Problem LIA Solution
Unpredictable LLM costs Real-time token tracking, budget alerts, 93% optimization
Uncontrolled hallucinations Human-in-the-Loop (HITL) with 6 approval levels
Fragmented integrations Unified multi-domain orchestration (20+ agents + MCP + sub-agents)
Limited observability 450+ Prometheus metrics, 28 Grafana dashboards (including a product-value cockpit), email alerting with runbooks, GeoIP analytics
Inconsistent performance Gemini embedding-001 with asymmetric task types, semantic routing with hybrid scoring

Primary Use Cases

📅 "Find my meetings for tomorrow and send a reminder to all participants"
📧 "Summarize my unread emails from this week that have attachments"
👥 "Update the companies of my contacts who work at startups"
🔔 "Remind me tomorrow at 9am to call Marie for her birthday"

Try LIA Online

Try LIA

LIA is available as a hosted service at https://lia.jeyswork.com/ — no installation required.

Interactive showroomlia.jeyswork.com/demo runs six guided synthetic missions, one per differentiating mechanism: orchestration under approval, proactivity, persistent memory, outbound calls, rich replies, and in-app configuration. Pick one, watch LIA read its sources, then approve, edit, or refuse each prepared change through the real approval UI — and read LIA's closing reply rendered by the production rich-HTML pipeline. Everything is clearly labeled synthetic — no account, model, or external service is contacted, and a proof drawer links every visible capability to its exact source. Self-hosting follows the Quick Start below — manually, or through the guided ./install.sh (ADR-215); until its disposable clean-machine qualification gates pass, prebuilt-image installation stays locked behind a qualified release manifest.

Closed beta: Access is currently limited to a restricted number of users, at the administrator's discretion. To request an invitation, contact liamyassistant@gmail.com.


Built by an AI, Directed by a Human

"Speed comes from the AI. Quality comes from the framework."

Nearly 100% of this codebase was written by an AI, under human direction: a written engineering rulebook, blocking automated checks, systematic review, adversarial audits. The result is measured, not proclaimed:

46 functional domains 570,000 lines of code (excl. tests) 33,000+ automated tests 272 ADRs
251 versions shipped 6 languages, parity enforced in CI 541 Prometheus metrics 8.3/10 technical audit, 24 normalized areas
  • The full story — method, trade-offs, results and what remains to be done, weaknesses included: lia.jeyswork.com/story
  • The audit itself — 24 normalized areas mapped to ISO/IEC 25010:2023, every score backed by executed evidence, 7 open worksites included, with the protocol and the full standalone report: docs/audit/

Screenshots

Dashboard — Homepage with usage statistics
Dashboard — Homepage with quick access, usage statistics, and personalized greeting

Chat — Multi-agent conversation with debug panel
Chat — Multi-agent conversation with real-time debug panel (right sidebar)

More screenshots

Chat — Debug panel detail
Chat — Debug panel: per-message routing, tool calls, token cost and reasoning timeline

Chat — Interactive skill widgets
Chat — Interactive skill widgets: maps, dashboards, calendars and mini-apps rendered inline

Settings — Preferences (connectors, MCP, themes)
Settings — Preferences: connectors, MCP servers, language, timezone, and themes

Settings — Features (memory, interests, notifications)
Settings — Features: LIA Style, long-term memory, interests, proactive notifications, scheduled actions, sub-agents, channels

Settings — Long-term memory
Settings — Long-term memory: pinned facts, automatic extraction, edit / delete / pin per memory

Settings — Psyche Engine
Settings — Psyche Engine: Big Five personality traits modulating the assistant's emotional responsiveness

Settings — Administration panel
Settings — Administration: LLM config, RAG Spaces, users, connectors, pricing, skills, voice, broadcast, debug

Settings — One-click administration
Administration — One-click simplicity: every admin action is accessible in a single click, no technical skills required

Settings — LLM Configuration with multi-provider support
Administration — LLM Configuration: 7 providers (OpenAI, Anthropic, Google Gemini, DeepSeek, Qwen, Perplexity, Ollama), per-node model selection

FAQ — Searchable help center
FAQ — Searchable help center with categorized Q&A sections


Features

Multi-Agent Intelligence (LangGraph 1.x)

  • 20+ Specialized Agents: Contacts, Emails, Calendar, Drive, Tasks, Reminders, Places, Routes, Weather, Wikipedia, Perplexity, Brave, Web Search, Web Fetch, Browser Control (with progressive screenshot streaming), Smart Home (Philips Hue), Context, Query + dynamic MCP agents
  • ReAct Execution Mode (ADR-070): Alternative to the pipeline — the LLM iteratively reasons about tool outputs and decides next steps autonomously. User-toggleable preference, 4-node LangGraph architecture with native HITL support, timeout enforcement, cross-domain initiative via prompt engineering. Supports all tools including MCP and Skills
  • MCP (Model Context Protocol): aligned on the protocol's current revision (2026-07-28) on both halves — how a server is spoken to and how its tool declarations are read. Per-user external tool servers with OAuth 2.1, SSRF protection, structured items parsing, MCP Apps (interactive iframe widgets), Iterative Mode (ReAct) for complex servers — a dedicated agent reads docs then calls tools correctly
  • Agent Initiative Phase: Post-execution cross-domain enrichment — the assistant proactively verifies related information (e.g., weather shows rain → checks calendar for outdoor events). Prompt-driven, read-only, fully configurable
  • Skills (agentskills.io) with Rich Outputs: Open standard for expert instructions (SKILL.md), model-driven activation, progressive disclosure (L1/L2/L3), sandboxed scripts, marketplace import, auto-translated multi-language descriptions, ZIP download, admin management. Rich Skill Outputs (v1.16.8): skills can return interactive HTML frames (iframe srcDoc or external URL) and/or images in addition to text, via a simple JSON contract (SkillScriptOutput). Automatic theme & locale sync (theme switch propagates live to frames via postMessage), iframe auto-resize, CSP-sandboxed client-side interactivity (addEventListener, crypto.getRandomValues), bundled segno for QR codes. Seven built-in rich skills: interactive-map, weather-dashboard, calendar-month, qr-code, pomodoro-timer, unit-converter, dice-roller. Planner skill guard: multi-domain deterministic skills are protected from false-positive early clarification requests via domain overlap detection (_has_potential_skill_match). Built-in Skill Generator: create custom skills in natural language — the assistant guides you through need analysis and archetype selection (the dialogue keeps its context across turns), then validates and installs the finished skill directly into My Skills, announced by name and immediately usable. Every import path (chat-generated or manual upload) goes through one hardened pipeline: strict name validation, zip-expansion caps, name-conflict rejection, atomic install with automatic rollback
  • Agent Plugins (agent-plugins.org) (ADR-225): LIA is a conformant client of the open Agent Plugins v1.0.0 standard (TSC: AWS, Cursor, Microsoft, OpenAI, Vercel) — portable plugin packages bundling agentskills.io skills and streamable-http MCP servers install in one step (zip upload or SSRF-hardened https URL) and behave exactly like their manually-created counterparts afterwards. Every install returns an exhaustive per-component report (installed / updated / skipped with a translated reason / removed — never a silent partial success); updates are re-imports that preserve configured OAuth credentials; uninstall removes the plugin and all its components as a group, and that group removal is the only way plugin components leave (individual deletion is refused server-side and guarded in the UI). Documented conformance deviations: stdio servers are never launched (multi-user server), endpoints are HTTPS-only
  • Agentic Telephony (ADR-127): LIA places real outbound phone calls on your behalf via your own per-user ElevenLabs + Twilio connector (BYO — zero cost on LIA's side). Every call is HITL-confirmed before dialing; the goal-driven voice agent greets the instant the line opens, resolves relative dates against a live temporal anchor, and hangs up when done. Privacy by capability: the call agent can only read free/busy availability — never event titles or contents; no recording, no stored transcript. A strict mandate boundary forbids any expense or commitment beyond the objective (offers are captured with their price and deferred to you), and the asynchronous post-call summary must state every cost and flag every open point. Config self-heals: fingerprint-based lazy re-sync of the vendor agent, self-healing one-active-call guard (vendor status probe, deleted-conversation 404 handling), pinned thinking-free agent LLM, telephony-native ulaw_8000 audio
  • AI Image Generation & Editing: Generate images from text prompts (gpt-image-1), edit existing images with natural language instructions. Multi-provider factory architecture, per-user quality/size preferences, cost tracking with DB-cached pricing, attachment-based storage with cascade cleanup
  • AI Document Generation (ADR-226): Ask for a CSV, Excel, Word, PowerPoint, PDF, Markdown or text file in the chat — a dedicated writer LLM slot produces structured content per format family, pure local renderers build the exact bytes (zero new dependency, no third-party document service), and the file arrives as a downloadable card with an explicit expiry deadline. Chains on same-request web research, formula-injection-safe spreadsheets, PDFs open inline, instance-wide capability switch
  • File Attachments (Images, PDF): Upload with client-side compression, configurable LLM vision analysis, PDF text extraction, strict per-user isolation
  • Response Grounding on Recent Entities (ADR-147): on a turn that produces no tool data, the response model is re-grounded on the most recent entities already in state (zero I/O, age-bounded, explicitly non-authoritative) instead of paraphrasing older prose — and the prompt forbids inventing an entity attribute rather than admitting it is unknown
  • Semantic Routing: Binary classification with confidence scoring (high >0.85, medium >0.65)
  • Multi-Step Planning: ExecutionPlan DSL with dependencies and conditions
  • Parallel Execution: asyncio.gather for independent domains
  • Intelligent Context Compaction: LLM-based conversation history summarization when token count exceeds dynamic threshold (ratio of response model context window). Preserves identifiers (UUIDs, URLs, emails). /resume command for manual trigger. 4 HITL safety conditions prevent compaction during active approval flows
  • Scroll-up History Pagination: GET /conversations/me/messages exposes a keyset cursor (?before=<created_at>) with has_more / next_cursor. The chat UI binds an IntersectionObserver on a top sentinel — older pages prepend with id-based dedup, scroll position preserved via a shared wasPrependRef that skips the auto-scroll-to-bottom for that cycle. Conversations of any length stay fully reachable; the existing (conversation_id, created_at DESC) composite index makes each page an index-only seek. Bounds env-tunable (CONVERSATION_HISTORY_DEFAULT_LIMIT / _MAX_LIMIT)
  • Location-Aware Everywhere (ADR-219): one resolution cascade for every feature — live browser position, else the fresh opt-in last-known position (encrypted, TTL-bounded, never historized), else home address. A dated position always travels with its age (as_of), so the assistant says "as of 9:30" instead of presenting a two-hour-old point as current; "at home" never resolves from a road-captured position; scheduled actions, briefing and skills inherit the cascade without a browser. The PWA lifecycle is handled: silent position refresh on return-to-foreground, and a proactive re-enable banner supplying the user gesture iOS requires when it drops the permission

Psyche Engine — Dynamic Emotional Intelligence

  • 5-Layer Psychological State: Big Five personality traits (permanent) → PAD mood space with 14 moods (hours) → 22 discrete emotions with cross-suppression (minutes) → 4-stage relationship progression (weeks) → curiosity/engagement drives (per-session)
  • Show, Don't Tell: Mood and emotions subtly influence word choice, sentence rhythm, energy level, and relational tone — the assistant never declares "I'm feeling happy"
  • Emotional Avatar: Mood-responsive emoji with colored ring on each message. Historical avatars persisted per-message for reload consistency
  • Evolution Awareness: The assistant knows how its mood shifted since the last message, providing narrative continuity
  • 4-Chart Dashboard: Interactive recharts visualization of mood (PAD), emotions, relationship, and drives over time (24h to 90 days)
  • Education Guide: 7-section interactive documentation explaining every layer, with descriptive tables for 14 moods and 22 emotions
  • Customizable Temperament: Expressiveness (stoic → highly expressive) and stability (volatile → very stable) sliders. Soft reset (mood only) and full reset (everything) with explicit scope descriptions
  • Global Injection: Behavioral directives injected via template variables into all user-facing text generation (response, notifications, reminders, voice) within semantic XML blocks (<InnerState purpose="tone-calibration">)
  • Safety Guardrail: Explicit instruction prevents the LLM from projecting its own emotional state onto the user
  • Self-Report: Zero-cost emotion tracking via hidden <psyche_eval/> tag — no additional LLM call

Expressive Eyes — A Living Face (ADR-240, ADR-252, ADR-264)

  • Zero new signals: a pure decision-table engine derives one of twenty expressions from the chat state machine, the SSE execution steps, the HITL card, the voice state machine and the psyche engine — no extra LLM call, no new endpoint
  • Answers the register, not the mood: the response model declares the tone of what it just wrote (ADR-253), and the face plays it — twelve distinct faces, only two of them smile; a technical answer keeps a focused face
  • Motion lives in a rig: a TypeScript runtime (analytic springs, additive loops, key tapes) publishes --rig-* custom properties that the stylesheet only reads; every coupling is written as an absolute contribution, never an increment, and a test compares 20,000 small steps against one to prove nothing drifts
  • Brows and a mouth that live: an arched brow present at rest, one breath for the whole face, the gaze and the blink coupled to the brows, speech phrases, nine mouth mimics at an unhurried random cadence, ten 3–5 s sketches every 45–120 s on a resting face — dropped by any expression change, the face exactly where it was
  • Six looks, two surfaces: six selectable styles with live previews in Settings (previews keep the breath and nothing else); the same widget greets visitors on the public home page — capsule look, fixed on scroll, draggable, a position of its own, no account — and prefers-reduced-motion freezes it into static poses

Voice: Input & Output

Voice Input (STT)

  • Push-to-Talk: Hold microphone button to speak, release to transcribe. Optimized for mobile (anti-long-press CSS, touch gesture handling)
  • Wake Word: Say "OK Guy" to activate hands-free recording. Sherpa-onnx WASM (Whisper Tiny.en) runs entirely in-browser — no audio sent externally for wake word detection
  • Per-User Language: STT transcription uses the user's preferred language setting (Whisper Small, 99+ languages, fully offline)
  • Latency Optimized: Mic stream reuse, WebSocket pre-warming, parallel setup, cached AudioWorklet (~50-100ms wake-to-record)

Voice Output (TTS)

Provider Models Cost Latency (TTFA) Notes
Edge TTS (Microsoft Neural) edge-tts Free ~250 ms Multilingual neural voices, free fallback
OpenAI TTS tts-1 / tts-1-hd $15 / $30 per 1M chars ~500 ms 6 stable voices (alloy, echo, fable, onyx, nova, shimmer)
ElevenLabs TTS eleven_multilingual_v2 $100 / 1M chars ~300 ms High-quality multilingual, Voice Library access
eleven_turbo_v2_5 $50 / 1M chars ~250 ms Sweet-spot quality / latency
eleven_flash_v2_5 $50 / 1M chars ~75 ms Ultra-low-latency for conversational agents
  • Catalogue-driven (ADR-081): provider/model/voice are admin-controlled via Configuration LLM (LLM type voice_tts). Voice + tuning live in provider_config JSONB. No env vars to maintain across deployments.
  • Sentence streaming (ADR-082): TTS runs sentence-by-sentence pipelined with the LLM stream. First audio lands in ~1 s on chat mode (was ~5 s).
  • Per-message cost transparency: 🔊 N chars · €X.XXX badge on the assistant bubble (paid providers only — Edge stays badge-free as it's $0).
  • Graceful degradation: missing API key on a paid provider transparently falls back to Edge with a structured warning log.
  • Persistent HTTP pool on ElevenLabs: keep-alive across sentences saves ~100–300 ms TLS handshake per call.

Meeting Recording & Structured Minutes (ADR-258, ADR-259)

  • One gesture: the composer's + button records a meeting with the phone or the computer as microphone; a banner follows the user on every dashboard page (duration, level, segments uploaded) while the chat stays usable — spoken answers and the wake word pause so the microphone never hears the assistant.
  • Capture that survives real life: audio leaves in short segments (Opus through MediaRecorder, raw PCM through the shared AudioWorklet on Apple devices), one atomic file per sequence under four API workers, ordered retrying uploads that wait offline instead of failing. A reload, a lost microphone or a locked phone comes back as interrupted with resume / finalize / discard; a silence watchdog asks « still recording? »; the maximum duration finalizes by itself; a gap is stated in the minutes, never filled in.
  • The meeting row is the durable job: atomic conditional transitions, lease + heartbeat publishing the stage (normalizing, transcribing, synthesizing, indexing), reapers for stale recordings, expired leases and orphans, bounded retry budget — and every read after a bulk update expires the session first.
  • A chain of engines, walked again at processing time: the admin voice_transcription slot, then ElevenLabs Scribe / OpenAI gpt-4o-transcribe-diarize (whole file, speaker separation), then the local Sherpa Whisper — now unbounded through Silero VAD speech windows ≤ 20 s, the same fix that closed the 30 s truncation of voice input. A permanent fault of one provider hands over to the next; only silence or a transient fault stops the walk.
  • The template is the contract: one structured-output call on the dedicated meeting_synthesis slot fills the user's sections (paragraph, bullets, topics, action items), condensing the transcript part by part when it overflows the model's window; repair_report folds the permissive answer into the strict report, participants restricted to speakers who actually spoke (S1…Sn, a name only when established). Edit, restore the generated version, rebuild with the current template.
  • One serializer, three outputs: Markdown for the « Meetings » knowledge space (found by role, one document per meeting rewritten in place and deleted with it), sectioned content for the PDF renderer, HTML for the email through the user's own connector.
  • Every paid unit accounted and shown: audio through the remote-STT statistics, tokens through track_proactive_tokens under the archived message's run_id (regenerations included); the row keeps the minutes' spend, the page states the exact total with its breakdown, the chat card both units and their sum; an unpriced model yields null, never zero.
  • A library of formats, and one place that chooses (ADR-259): thirty built-in templates in seven categories plus the user's own, each named by a TemplateRef (builtin:<key> / user:<uuid>) that meetings, preferences and requests exchange instead of a row — so a built-in needs no database row and a deleted template leaves a reference its readers know how to fall back from. One precedence decides the format: the meeting's own reference, then the preference's default, then the model's choice over a transcript excerpt above a confidence floor, then the built-in default. Every outcome is counted (meeting_template_selection_total) and written on the row with the reason the model gave.
  • A fifth section kind, the transcript itself: the exchange rewritten turn by turn, split under a character budget and each part bounded by the slot's effective output window — a missing index splits the part once, a suspiciously short answer is retried once. Transcript templates are never chosen automatically: they are long and priced like a whole meeting, so they stay an explicit choice.
  • Reformatting, two modes, one transcript: replace rewrites in place through the durable regeneration; new derives a second meeting row pointing at its source (source_meeting_id, FK SET NULL), READY with no report while the server writes, indexed as its own knowledge-space document. Never a « copy »: the transcript is the same, the minutes are not.
  • The minutes leave from the platform: APPLICATION_SMTP_FROM through EmailService, whose SMTP exchange runs off the event loop; the subject is the localized « Meeting minutes » followed by the title. The user's email connector — and its refusal when there was none — is gone from the path.

Redis Key Families & What a Reset May Purge (ADR-260)

  • A key declares its scope, and the reset reads the declaration: CONVERSATION, USER_CACHE, USER_LEARNING, USER_RUNTIME, GLOBAL in one registry (infrastructure/cache/key_families.py). The purge deletes by family instead of by glob — the scan is unchanged, so nothing escapes it; only the decision moved.
  • An undeclared family is KEPT and counted, never guessed: keeping an unknown key costs a cache miss, deleting it can cost weeks of learning. Two guards refuse the drift — a boot assertion over every Redis prefix constant, and an AST guard over literal f-string keys.
  • Deletion stays total where it must be: "Forget everything" and account deletion remove the learning keys too, by the same registry.

Push-Driven Wake & Reading Presence (ADR-261, ADR-214)

  • A processed notification queues a wake, and the webhook stays dumb: SET NX per (user, provider), so a storm of notifications is ONE wake, dated by the first; the webhook answers 200 and decides nothing.
  • A leader-elected sweep serves the queue under the FULL eligibility checker — window, quota, cooldowns, the user's source preference. Only the "guaranteed minimum" smoothing is bypassed, because a wake answers an event; the runner gained user_ids and skip_probabilistic_gate and nothing else.
  • The mail delta is previewed, never consumed until the wake is served, so a refused wake leaves the message for the next tick. The two Gmail anchors stay distinct on purpose: the channel's is the last event seen, the heartbeat's the last mail consumed.
  • The pre-filter is deterministic and published (PUSH_WAKE_*): a required label, excluded categories, list mail out; an event starting within the lookahead, changed by someone else or still awaiting the user's answer. Every verdict is a bounded reason.
  • A Drive change reindexes exactly what changed, under the same lock the manual sync uses, through the per-file ingestion both paths now share.
  • Reading counts as presence (HABITS_PRESENCE_ENABLED, off by default): opening the app counts, a thumb on a notification counts, a notification sent never does. At most one banked hour per local hour, written straight into the durable rollup; the heartbeat's inactivity gate reads the last presence as much as the last sign-in.

Gmail Label as a Knowledge Source (ADR-262)

  • The opt-in IS the label (RAG_SPACES_MAIL_SYNC_ENABLED, off by default): a space follows one Gmail label, only the threads carrying it are rendered and indexed, and removing the label in Gmail removes the document at the next pass — the deletion gesture is the one the user already knows.
  • One thread, one Markdown document: the subject as the title, messages in date order, plain text preferred, attachment names only, a hard size cap. The display name is the subject, never a participant — and it is sanitised, because a subject is written by a third party.
  • Two ways in, one ingestion: the full sync anchors Gmail's history before listing the threads, so a message arriving mid-listing is replayed by the next incremental pass instead of falling in the gap; the incremental path rides the push wake and answers to no notification gate — indexing is not deciding.
  • A synced source is a durable job, whichever kind: the lease, reclaim and reaper queries take the table as a parameter, validated against a two-name allowlist, so a Drive folder and a Gmail label recover through the same code.

Knowledge-Space Document Operations (ADR-259)

  • One path builder, one ownership check (document_access.py): the storage root, the owner, the space, then the stored filename, each segment resolved and contained. Reading, deleting, downloading and moving all go through it.
  • Download, archive, move, bulk delete: a single file by its original name; a selection as one zip (deduplicated member names, a _missing.txt listing files gone from the disk, refused beyond RAG_SPACES_ARCHIVE_MAX_MB); a move to another space of the same user; a batch delete. A batch never fails as a whole for one document — every id is reported done or skipped with a stable code the UI localizes.
  • A move takes the index with it: rag_chunks.space_id is denormalized and read by retrieval, so the row and its chunks are updated and committed BEFORE the file is moved; a rename that fails reverts both and reports that document only. Refused wholesale during a reindex; Drive-synced and meeting-owned documents stay where their owner put them.

FOR_EACH Iteration Pattern

# DSL Syntax
ExecutionStep(
    tool_name="send_email",
    for_each="$steps.get_contacts.contacts",
    for_each_max=10
)
  • Per-parent correlation identity: an enrichment fetched for one iteration belongs to that iteration — results keyed on content alone (weather → place + day) used to collide between two parents sharing them, silently costing one of them its data; ids are now derived per parent and stay stable across replays and resumed checkpoints
  • HITL Thresholds: Mutations >= 1 trigger mandatory approval
  • Bulk Operations: Send emails, update contacts, mass deletions

Smart Services (Token Savings 89%)

Service Role Optimization
QueryAnalyzerService Routing decision LRU Cache
SmartPlannerService ExecutionPlan generation Pattern Learning
SmartCatalogueService Tool filtering 96% token reduction
PlanPatternLearner Bayesian learning Bypass >90% confidence

Google Integrations (OAuth 2.1 + PKCE)

  • Gmail: Search, read, send, reply, trash
  • Contacts: Fuzzy search, list, details (14+ schemas)
  • Calendar: Search, create, update events
  • Drive: Search, file/folder listing
  • Tasks: Full CRUD with completion

Apple iCloud Integrations

  • Apple Mail: Search, read, send, reply, forward, trash (IMAP/SMTP)
  • Apple Calendar: Search, create, update, delete events (CalDAV)
  • Apple Contacts: Search, list, create, update, delete (CardDAV)

Microsoft 365 Integrations (OAuth 2.0 + PKCE)

  • Outlook: Search, read, send, reply, forward, trash (Graph API)
  • Calendar: Search, create, update, delete events (calendarView)
  • Contacts: Search, list, create, update, delete
  • To Do: Full CRUD with completion (task lists + tasks)
  • Multi-tenant: Personal accounts (outlook.com) and business accounts (Azure AD) via tenant=common

3-Way Mutual Exclusivity

  • Only one provider per functional category (email, calendar, contacts, tasks)
  • 3 supported providers: Google, Apple, Microsoft
  • Activating a new provider automatically deactivates the active competitor

Smart Home — Philips Hue

  • Voice-controlled lighting: Turn lights on/off, adjust brightness and colors via natural language
  • Room & scene management: Control entire rooms or activate predefined scenes ("dim the living room", "activate movie mode")
  • Local or cloud connection: Connect via local bridge IP or Philips Hue cloud API
  • Feature flag: PHILIPS_HUE_ENABLED=true to enable

Human-in-the-Loop (HITL)

Type Trigger Severity
Plan Approval Destructive actions CRITICAL
Clarification Detected ambiguity WARNING
Draft Critique Email/Event review INFO
Destructive Confirm Deletion of >= 3 items CRITICAL
FOR_EACH Confirm Bulk mutations WARNING
Modifier Review Review and approve AI-suggested modifications to draft content INFO

Note: the plan-approval level is currently auto-approved — tool-level HITL supersedes it (see ADR-106); the other five levels interrupt execution and wait for the user.

Enterprise Observability

  • Prometheus: 541 custom metrics (agents, LLM, infrastructure)
  • Grafana: 28 production-ready dashboards
  • Langfuse: LLM-specific tracing with prompt versions
  • Loki: Structured JSON logs with PII filtering
  • Tempo: Distributed cross-service tracing
  • Probes: liveness (GET /health, always 200 while the process serves — what Docker healthchecks poll) split from readiness (GET /ready, 503 unless PostgreSQL and Redis answer) — ADR-115
  • Alerting: a 14-alert vital core (service/DB/Redis down, disk, container OOM, 5xx rate, SSE latency, backup failure, public-endpoint & TLS-certificate probes, chain self-monitoring) evaluated by Prometheus and emailed by a dedicated Alertmanager — unit-tested with promtool test rules, every alert linking its runbook — ADR-119
  • Self-diagnostics (ADR-247, ADR-266): a leader-elected self-check of the golden signals plus direct probes, one incident per outage whichever observer saw it first, and a budget-capped diagnosis written in each administrator's language from an evidence pack collected at diagnosis time — breakdown metrics, a sanitized log excerpt, the running build, the alert's runbook — with every source failing open and what the model read shown under its verdict in Settings › Platform health

Cost Tracking & Billing

Type Tracking Export
LLM Tokens Per node, per provider Detailed CSV
Google API Per endpoint, per user Detailed CSV
Aggregated Per user, per period CSV summary
  • Google Maps Platform: Places, Routes, Geocoding, Static Maps
  • Dynamic Pricing: Admin UI for full LLM catalogue CRUD — provider, 8 capability flags (max input/output tokens, tools, structured output, strict mode, streaming, vision, reasoning) and pricing per model, all stored in the database. The reasoning identity is written by ticking the depths the model's family offers — resolved live from (provider, model) by the same function the API validates against, so the form cannot offer a depth a provider refuses — and every row states where its capabilities came from: corroborated by the vendored public registries, verified by a human, or never curated. Same surface for image generation models (provider + quality/size/pricing). Cross-worker cache invalidation via Redis Pub/Sub (ADR-063), live cross-sibling refresh in the frontend — no code change, no redeploy
  • ContextVar Pattern: Implicit tracking without explicit parameter passing
  • Admin CSV Exports: Token usage, Google API usage, Consumption summary (all users or filtered by user)
  • User CSV Exports (v1.9.1): Personal consumption export in Settings > Features — users export their own data only (user_id forced server-side, IDOR-safe)
  • Every platform-paid token answers to BOTH ceilings (ADR-272): what one account may consume, and what the instance may spend in a day. cost_bearers.py draws the line — provider_api_keys has no user_id, so models, TTS, STT, image and Maps run on the deployment's key, while Perplexity, Brave, weather and telephony run on the person's own and are outside. Measured: of the 46 declared LLM spend sites, 5 were bounded by nothing. A chokepoint is the INNERMOST door, never its wrapper; a gate that returns early bounds nothing; and how a caller receives a refusal follows its transport while the verdict never does — a request path raises (429, dedicated code, Retry-After), a background path degrades and logs skipped, never failed
  • Where a module's spend is recorded is DECLARED, not inferred (ADR-270): accounting here is ambient — a node spends through a tracking context an ancestor published — so reading files to answer "is this tracked?" produced nine wrong conclusions in one session. spend_roads.py names all 46 sites and the ledger each reaches; an AST guard refuses an omission, a stale entry or a road whose module does not do what it claims, and it caught 6 of the author's own classifications. Reading a provider's usage metadata likewise had eight divergent implementations (only one read Anthropic's cached-prompt tokens, only one clamped at zero) and now has one
  • A euro nobody owns still reaches a ledger: self-diagnosis and catalogue translations run for no account, so they must not touch per-account counters — they feed the instance daily budget instead, and ask it for permission first. Measured before the fix: 84 personality translations recorded nowhere at all, while the ledger held 5 976 rows from 17 other surfaces over the same window

Security & Compliance

  • OAuth 2.1: PKCE (S256), single-use state token
  • BFF Pattern: HTTP-only cookies, server-side Redis sessions (fixed 7-day lifetime, 30 with remember-me)
  • Strong authentication: WebAuthn passkeys (discoverable credentials, conditional UI on the login form, single-use challenges, clone detection, zero enumeration) + TOTP second factor (two-step login, matched-timestep anti-replay, 10 single-use backup codes revealed once); step-up re-authentication guards every sensitive action behind a 5-minute sudo window (typed 403 contract; password, code, passkey, or provider re-sign-in). Feature flag: MFA_ENABLED=true — ADR-143
  • Device sessions: "My devices" lists every live session with deliberately bounded metadata (browser/OS families, truncated IP, coarse last-seen), per-device revocation and step-up-guarded revoke-others; a revoked session's SSE streams close within one keepalive tick; new-login push alerts with FCM device attestation — ADR-144
  • Encryption: Fernet (credentials), bcrypt (passwords)
  • GDPR: Automatic PII filtering, pseudonymization, and full-account export (Art. 20): durable jobs build a ZIP (JSON + readable Markdown + uploaded files) from a total data classification where secret tables are unexportable by construction. Feature flag: ACCOUNT_EXPORT_ENABLED=true — ADR-145
  • Offline PWA: one unified service worker serves push and a branded 6-language offline page; /api/ is never cached — ADR-146
  • Per-User Usage Limits: Token, message, and cost quotas (period/global) with 5-layer defense-in-depth enforcement, admin kill switch, real-time dashboard with WebSocket gauges. Feature flag: USAGE_LIMITS_ENABLED=true
  • Instance Daily Spend Ceiling: a durable UTC ledger caps what the whole deployment may spend in a day, not what one account consumes — atomic UPSERT with column arithmetic inside the transaction that persists the run's token summary, so concurrent runs can never lose spend to a read-modify-write race. Two bounds compose (INSTANCE_DAILY_BUDGET_EUR and an admin setting) and the smaller wins. Unlike per-user limits, which fail open, an unknown instance spend fails closed; refusals carry a dedicated code and a Retry-After to the next UTC midnight — ADR-216
  • Administrable Platform Capabilities: ten non-connector capabilities switch off from the admin panel with no redeploy, each declaring the mode by which it is really enforced — planner catalogue exclusion, a route dependency refusing with a stable code, or an internal chokepoint for capabilities that have no route at all. Two boot guards recalculate the declaration against the live agent catalogue and the live routers — ADR-217
  • Backups: Automated daily PostgreSQL dumps (pg_dump sidecar, daily/weekly/monthly rotation, all .env-driven) with a tested one-command restore and a verification drill (task backup:verify) — ADR-109, runbook in docs/runbooks/DATABASE_BACKUP_RESTORE.md

Transparency Registers — What Was Done, What Was Read, and Who Asked (ADR-263, ADR-270, ADR-273)

  • Three registers, never one list with a filter: agent_effects takes one row per ACTION (claimed before it happens, closed from an explicit result), agent_treatments one row per CONSULTATION (no uniqueness, no owner token, no content column — which capability, when, how long, with what outcome), and agent_decisions one row per TURN, the spine the other two hang off. Every capability that acts declares what it owes the reader (mutation_policy), checked at boot; a third-party MCP tool never declares it — the policy is derived from the server's own annotations and never looser
  • A third tab: acts of LIA's own initiative (ADR-270). The registers were fed by the tool gate alone, and proactive work calls no tool: measured in production, 0 out-of-turn runs out of 228 over fourteen days left a row, so "acts of her own initiative" was empty by construction. A proactive notification is now claimed before it is sent and settled from the delivery result; the nine surfaces that read through connector clients rather than tools (briefing, relationship debrief, heartbeat sweep, interests, knowledge spaces) record their consultations; and each surface DECLARES its vocabulary rather than a third copy being transcribed
  • Authorship is a property of the call site, not of the plumbing: nothing schedules a briefing (it answers a request), a reminder is the person's own deferred instruction, and only a runner sweep is LIA's own initiative. The parameter has no default — a default would have filed all three identically
  • No extraction is capped (ADR-273). The five downloadable records — readable, CSV, technical, unified Article-12, and the operator's cross-account view — return every row their filters match. There used to be a measured ceiling (a five-record, 5000-row extraction peaked at 33,9 MB on the Raspberry Pi 5 this deploys to), but it was applied to the wrong variable: the whole document was assembled in memory. What was scarce was memory; what was bounded was the truth — 49 195 real rows against 1 000 per source meant 97,9 % of the inference record was absent. A server-side cursor bounds the buffer instead; the count is exact (an aggregate over the same statement the body streams) and published before the first row, and the download is gzipped when the client offers to decompress (measured ×10,1) without the file name changing
  • Per-account hash-chain sealing (LEDGER_CHAIN_ENABLED, off by default): per account and never global, because that is what lets inalterability and the right to erasure coexist. Notarising is asynchronous on a measurement (6,0 ms against 0,21 ms for the write itself), so it has a window — and that window is published, alerted and named on every surface rather than implied away by the word "verified". Nothing repairs a chain: a repair tool serves an attacker as well as an operator
  • Two endpoints serving one screen make ONE act of reading (ADR-271): the dashboard fetches cards and synthesis in parallel, and each used to build the nine-section bundle on its own — measured over seven days, 151 builds, 44 duplicates, 39 % of page loads, 44 of 44 concurrent. Every connector was called twice and two batches of consultation rows were filed for one act. Whoever asks first runs it and whoever asks while it runs is handed the same object, in-process and across workers (production runs WEB_CONCURRENCY=4, so the two requests share a worker about one time in four)

MCP (Model Context Protocol)

  • Dual-era protocol client (ADR-224): speaks the stateless 2026-07-28 revision AND falls back automatically to the legacy handshake — old and new-generation servers both plug in, with actionable diagnostics when neither is possible
  • Tool declarations read to the letter of the same revision (ADR-255): the spec admits every JSON Schema 2020-12 keyword in a tool's inputSchema, and LIA reads them — union types, anyOf/oneOf/allOf, $ref into $defs, const, enum inference — through a single authority shared by the tool adapter and the planner catalogue, with a parity test holding the two readings together. Every function there is total: a declaration LIA cannot use degrades the property, never the tool, because a lost tool is a capability the user no longer has without being told. What a server actually enforces (closed sets, bounds, sizes) is published to the planner in the same constraint vocabulary native tools use
  • Behaviour annotations tighten, never relax: the spec requires a client to treat readOnlyHint / destructiveHint as untrusted, so a declared mutation is believed while a read-only claim never is — and a tool the server declares destructive asks for confirmation even on a server whose confirmation is switched off
  • A dropped tool is visible: mcp_tool_registration_failures_total with two panels, because a tool that fails to load leaves nothing else behind
  • Per-user external servers: Each user connects their own MCP servers (third-party tools)
  • Flexible authentication: None, API Key, Bearer Token, OAuth 2.1 (DCR + PKCE S256, iss validation per RFC 9207, issuer-bound credentials with automatic re-registration)
  • Enhanced security: HTTPS-only, SSRF prevention (DNS resolution + IP blocklist), encrypted credentials (Fernet)
  • Structured Items Parsing: Automatic JSON array detection into individual items with McpResultCard HTML
  • Auto-generated descriptions: LLM analysis of discovered tools to generate domain descriptions optimized for intelligent routing
  • Per-server rate limiting: Redis sliding window per server/tool
  • Feature flag: MCP_USER_ENABLED=true to enable per-user

Native Mobile Apps — Android & iOS (ADR-246)

One published app per store, a client for any self-hosted LIA server: the WebView loads your server's remote origin, whose URL you type at first launch. The UI is never duplicated — web releases reach the app with no store update — and every platform claim below was measured on the real engines, not assumed:

  • Sign-in the only way Google permits: the OAuth flow leaves for the system browser and returns through a lia:// deep link redeemed against a verifier only the app holds — an intercepted link is worthless. Wiring it closed a pre-existing hole where Google sign-in skipped TOTP
  • Native push, deliberately asymmetric: Android initialises Firebase at runtime with options your server publishes (no google-services.json baked in — a self-hoster's notifications never leave their own project); iOS is woken through a stateless relay — the handle is the sealed device token, the notification is one fixed sentence in six languages, and the relay never learns who was woken or why. Doubt never deletes a device
  • Every OAuth departure comes home: twelve flows (connectors, MCP servers, sign-in) return to the surface that opened them, decided once at the single chokepoint they already shared
  • A real verification bench: task mobile:verify:android drives the debug app on an emulator through ten scenes — setup, HTTPS refusal at the door, the offline screen on an unreachable server, deep links routed and refused warm and cold, the forget escape hatch — and found three live defects before its first green run
  • Bundled setup & offline screens in all six languages, with safe-area handling and 44px touch targets; the offline screen offers a way out of a mistyped server, so a typo never means reinstalling

Multi-Channel Messaging (Telegram)

  • Bidirectional Telegram: Full chat with LIA via Telegram (text, voice, HITL)
  • OTP Linking: Secure account-to-Telegram linking via 6-digit OTP code (single-use, 5min TTL, brute-force protection)
  • HITL Inline Keyboards: Approval/rejection buttons localized in 6 languages directly in Telegram
  • Voice Transcription: Telegram voice messages to STT (Sherpa Whisper) to text processing
  • Proactive Notifications: Reminders and interest alerts also sent via Telegram
  • Extensible Architecture: BaseChannelSender/BaseChannelWebhookHandler abstraction for future channels (Discord, WhatsApp)
  • Observability: 12 dedicated Prometheus RED metrics (latency, errors, volumes)
  • Feature flag: CHANNELS_ENABLED=true to enable

Autonomous Heartbeat — Proactive Notifications

  • LLM-driven proactivity: LIA takes the initiative to inform you when relevant (weather, calendar, interests)
  • Multi-source aggregation: Calendar, Weather (with change detection), Tasks, Interests, Memories, Activity — parallel fetch
  • 2-phase LLM decision: Phase 1 (structured output, cost-effective model) decides whether to notify, Phase 2 rewrites with user personality and language
  • Intelligent anti-redundancy: Recent history + cross-type dedup (heartbeat vs. interests) in the decision prompt
  • User control: Push notifications (FCM/Telegram) independently toggleable, configurable daily max (1-8), dedicated time windows (independent from interests)
  • Feedback loop: thumbs up/down on every notification card, persisted on the archived message so a verdict is given once, across devices
  • Weather change detection: Rain start/end, temperature drops, wind alerts — truly actionable notifications
  • Feature flag: HEARTBEAT_ENABLED=true to enable

Interest Learning — Admission Doctrine

  • A creation requires a named ground (ADR-166): stated passion, own practice, prior knowledge, or digging into the same subject across an exchange — and the model must quote the user's words that carry it. Asking about a subject is a task, not a taste.
  • Six exclusion classes, stated as classes rather than examples: the subject of a request, a remark about the assistant, a third party's taste, something tried once, a daily action, anything the assistant introduced itself
  • Deduplication reads every status: a blocked subject cannot be re-created under a neighbouring name, renamed back into place, or deleted to free the slot; a dormant one is revived instead of duplicated
  • Cap on destructive actions: beyond EXTRACTION_MAX_DELETES_PER_RUN (2), the batch's deletions are dropped and counted — one replayed production window proposed 19
  • Anchored confidence floor (INTEREST_EXTRACTION_MIN_CONFIDENCE, 0.75): the prompt's scale is tied to the ground it can name, so the written rule is enforceable
  • Measured, not asserted: scripts/measure_extraction_selectivity.py replays a production-derived battery plus a held-out one — noise on negatives 0.50 → 0.00, recall 0.75 → 1.00, reproduced on two model providers

Scheduled Actions

  • Recurring actions and reminders: one engine answers "when?" for both — a recurrence is a product of calendar days and times of day, so "every three days", "the 2nd Tuesday of the month" and "every two hours between 9 and 5" are expressible, and a reminder can repeat. Days are enumerated, never delegated to a cron: the previous engine skipped a whole day when a timezone's offset changed at local midnight
  • Timezone-aware: Correct timezone handling per user
  • Retry logic: Automatic retries on failure with back-off
  • Auto-disable: Automatic deactivation after N consecutive failures
  • Multi-channel integration: Result notifications via FCM, SSE, and Telegram
  • Week view (ADR-265): a grid above the list — hours down, days across — routines numbered in trigger order, each cell of the current week coloured from a run history the executor writes at the result (executed, failed, proposed, paused); a real table, one tab stop, arrow keys walk it, the week computed server-side by the scheduler's own cron engine
  • Run history: scheduled_action_runs, one row per tick with its served slot and outcome, bounded retention purged inside the executor's tick
  • Always on: no feature flag — the router is included unconditionally

Sub-Agents (F6)

  • Persistent specialized agents: Create sub-agents with custom instructions, skills, and LLM configuration
  • Read-only V1: Sub-agents perform research, analysis, and synthesis — no write operations
  • Template-based creation: Pre-defined templates (Research Assistant, Writing Assistant, Data Analyst)
  • Invisible to user: The principal assistant orchestrates sub-agents and presents results naturally
  • Token guard-rails: Per-execution budget, daily budget, auto-disable after consecutive failures
  • Feature flag: SUB_AGENTS_ENABLED=true to enable (default: false)

RAG Knowledge Spaces

  • Personal knowledge bases: Create spaces, upload documents in 15+ formats (PDF, DOCX, PPTX, XLSX, CSV, RTF, HTML, EPUB, and more), automatic chunking and embedding
  • Google Drive folder sync: Link Google Drive folders to spaces for automatic file vectorization with incremental change detection (new, modified, deleted). Feature flag: RAG_SPACES_DRIVE_SYNC_ENABLED
  • Hybrid search: Semantic similarity (pgvector cosine) + BM25 keyword matching with configurable alpha fusion
  • Response enrichment: RAG context automatically injected into assistant responses when active spaces exist
  • Full cost transparency: Embedding costs tracked per document and per query, visible in chat bubbles and dashboard
  • System knowledge spaces: Built-in FAQ knowledge base (250 Q/A across 24 sections) indexed from Markdown files (docs/knowledge/, 27 documents). is_app_help_query detection by QueryAnalyzer, RoutingDecider Rule 0 override, App Identity Prompt injection with lazy loading (zero overhead on normal queries). Auto-indexed at startup with SHA-256 hash-based staleness. Admin UI for reindex and staleness monitoring. ADR-058
  • Admin reindexation: Full reindex when embedding model changes, with Redis mutual exclusion and automatic dimension ALTER. System spaces have independent reindex via admin API
  • Observability: 17 Prometheus metrics (14 user + 3 system), dedicated Grafana dashboard
  • Feature flags: RAG_SPACES_ENABLED=true (user spaces), RAG_SPACES_SYSTEM_ENABLED=true (system FAQ spaces)

Personal Journals (Carnets de Bord) — Stratified consciousness

  • Introspective notebooks: The assistant maintains thematic journals (self-reflection, user observations, ideas & analyses, learnings) written in first person, colored by its active personality
  • Four abstraction levels: Each entry carries a levelL0 raw observation, L1 operational directive (WHEN→DO BECAUSE), L2 transversal pattern, L3 portrait facet. L2/L3 are produced exclusively at consolidation through active topic clustering (ADR-079)
  • Epistemic status: confidence ∈ {low, medium, high} plus evidence_count and contradiction_count counters per entry. The journal distinguishes hypotheses still in test from observations validated across many turns
  • Deferred self-evaluation T → T+1: MessagesState.injected_journal_ids carries IDs across turns; the post-conversation extractor sees the previous turn's directives + the current user reaction, signals evidence_outcome="evidence" | "contradiction", and the service atomically increments the counters. Zero added LLM cost (same extractor call, enriched prompt). Anti-hallucination layer 4: LLM never writes absolute counter values.
  • Dual trigger: Post-conversation extraction (fire-and-forget) + periodic consolidation (APScheduler, 4–12 h cooldown)
  • Gemini dual-vector embeddings: gemini-embedding-001 (1536d) — one vector on title+content, one on search_hints keywords. Search uses LEAST(dist_content, dist_keyword) per row to bridge the assistant's introspective vocabulary and the user's vocabulary (ADR-069)
  • Ambient diffusion of the user-model portrait: Consolidation produces, in the same LLM call, a portrait_full (~200 tokens) for conversation/planner and a portrait_brief (~60 tokens) diffused across 6 secondary flows (ReAct setup, interest proactive, reminder notification, voice, heartbeat, fallback sync+async). Standalone builder build_journal_user_model_block(user_id, format, flow) mirrors build_psyche_prompt_block.
  • Three corrective levers on the portrait (never directly editable): edit L3 source entries, POST /journals/portrait/feedback (free text → L0 user_correction + synchronous re-consolidation), POST /journals/consolidate (manual, bypasses cooldown).
  • Prompt-driven lifecycle: The assistant manages its own journals — no hardcoded auto-archival. Mandatory pairwise dedup at consolidation STEP 1, classification audit, active L1→L2 clustering at STEP 5
  • Heartbeat integration: Journal entries enrich proactive notifications via dynamic second-pass query built from aggregated context. The compiled portrait brief is also injected so the notification voice is aligned with the same user model used by conversation
  • Full user control: Enable/disable (data preserved), consolidation toggle, conversation history analysis (with cost warning), 4 configurable numeric settings, group-by Theme/Level toggle, filter "show only entries never used", full CRUD in Settings (level + confidence editable)
  • 4-layer anti-hallucination: prompt guidance with ID reference tables, field_validator on UUIDs, known-ID filtering in extraction and consolidation, atomic counter increments
  • 11 Prometheus metrics: journal_entries_total{action,theme,source}, journal_evidence_total{outcome}, journal_consolidation_promotions_total{from_level,to_level}, journal_level_distribution{level}, journal_portrait_present_total{flow,format}, journal_portrait_age_hours, journal_portrait_feedback_total{outcome}, etc.
  • Debug panel: Dedicated "Personal Journals" section showing injection metrics AND background extraction results (CREATE/UPDATE/DELETE badges with theme/title/mood, even on partial updates where the LLM omits fields)
  • Cost transparency: Real token costs tracked via TrackingContext, visible in Settings and dashboard
  • GDPR: Account deletion scrubs the three portrait columns alongside entries; export endpoint includes the compiled portrait under a portrait key
  • Feature flags: JOURNALS_ENABLED=false (system), user-level toggle in Settings > Features. ADRs: ADR-057ADR-064ADR-069ADR-079

Health Metrics — iPhone Shortcuts Batch Ingestion

  • Two token-authenticated endpoints (POST /api/v1/ingest/health/steps and /api/v1/ingest/health/heart_rate): an iPhone Shortcut automation pushes daily batches of samples. Each sample carries its own ISO 8601 date_start / date_end — UTC-normalized server-side and second-truncated to keep uniqueness stable.
  • Polymorphic single-table storage (health_samples): one row per sample with a kind discriminator (heart_rate | steps). Extending to spo2 / sleep / calories reduces to a new kind value — no new table, no new endpoint.
  • Idempotent UPSERT (ON CONFLICT (user_id, kind, date_start, date_end) DO UPDATE) using PostgreSQL's RETURNING (xmax = 0) trick to split insert vs update counts in a single round-trip. Re-sending the same batch is free — last value wins.
  • Flexible body parser: accepts JSON array, NDJSON, {"data": [...]} envelope, and the iOS Shortcuts "Dictionnaire" wrapping ({"<ndjson_blob>": {}}) — no contract pressure on the user's Raccourci authoring.
  • Per-user hashed tokens: SHA-256 digest stored, raw value (hm_xxx) returned once at generation, display prefix shown in Settings, individually revocable. Multiple tokens may coexist for rotation.
  • Mixed per-sample validation: out-of-range / malformed / missing-field / invalid-date samples are individually rejected with their 0-based index + reason, while valid siblings in the same batch persist.
  • Bucketed aggregation (hour / day / week / month / year): heart rate averaged (plus min / max), steps SUM-ed per bucket; gaps kept (has_data=False) so the UI displays honest curves.
  • Settings visualization: four-section panel (ingestion API + tokens, recharts line/bar charts with period average overlays, statistics, deletion by kind or full wipe).
  • GDPR-aware: deletion by kind (DELETE ?kind=...), full erasure (DELETE /all), ON DELETE CASCADE on the user FK.
  • Observability: bounded-cardinality Prometheus metrics (health_samples_upserted_total{kind, operation}, validation rejections, rate-limit hits, auth failures, token lifecycle, deletions, latency histogram) + Grafana dashboard 21.
  • Guards: 60 req/h/token sliding-window rate limit (configurable), 1000 samples/batch cap (413 beyond).
  • Feature flag: HEALTH_METRICS_ENABLED=false (system). ADR-076 · Guide iPhone · Technical doc

Health Metrics — Assistant Agent

  • Single health_agent with 7 hand-crafted tools: steps (summary, daily breakdown, baseline delta), heart rate (summary, baseline delta), cross-kind (overview, change detection). One agent ↔ one domain pattern, mirroring email_agent / event_agent.
  • time_min / time_max windowed queries: aggregation tools accept ISO 8601 bounds exactly like calendar_tools.search_events_tool. The QueryAnalyzer resolves "this week" / "last month" into concrete date ranges, and the planner splits them across the two parameters.
  • Inlined figures in the LLM message: all factual data (totals, averages, per-day values) ship in the UnifiedToolOutput.message so the Response LLM surfaces them without reaching into structured_data (pattern from weather_tools).
  • Extensible registry (HEALTH_KINDS): adding sleep / SpO2 / calories = one entry in kinds.py — bounds, merge strategy, aggregation method, baseline kind. Service helpers iterate the registry so cross-kind logic stays generic.
  • Baseline & variation detection: rolling 28-day median with bootstraprolling mode switch after 7 days of data, tunable thresholds (HEALTH_METRICS_VARIATION_* env vars).
  • Heartbeat / Memory / Journal integration: health_signals source injected for proactive context; context_biometric JSONB persists deltas and trends in memories (never raw values) when emotional weight crosses a threshold.
  • Per-day server-side rollup (ADR-148): baselines and variations read one aggregated row per day instead of every raw sample — 50× cheaper, and the heartbeat no longer drops its health signals on half the ticks
  • Per-user opt-in: single health_metrics_agents_enabled toggle governs the four integrations (tool access, Heartbeat, memory extraction, journal injection). PATCH /auth/me/health-metrics-agents-preference.

MCP Apps — Interactive Widgets

  • Sandboxed iframes via a CSP airlock (ADR-098): third-party widgets boot through a same-origin shell (public/widget-frame.html) served with its own permissive CSP, so external-CDN widgets (Excalidraw, …) work while the main app keeps a strict policy. Isolation is the iframe sandbox (opaque origin, no parent cookies/DOM), not the CSP; the shell is hardened by anti-abuse locks + frame-ancestors 'self'
  • JSON-RPC Bridge: Bidirectional communication between iframe app and chat via PostMessage JSON-RPC 2.0
  • Excalidraw Iterative Builder: Intent-based diagram generation via dedicated LLM calls (shapes + arrows) with cheat sheet injection for format accuracy. Runs under a dedicated MCP-step timeout family (300 s floor / 600 s ceiling, ADR-100) so complex diagrams are not cut off mid-generation
  • read_me convention: MCP servers exposing a read_me tool have their content auto-injected into the planner prompt
  • Auto-generated descriptions: LLM analysis of discovered tools for domain description optimized for routing
  • App-only tools: Tools with visibility: ["app"] filtered from the LLM catalogue (iframe only)

Personal CRM — Relations

  • A 360° lens over people you deal with (ADR-176): open loops (commitments), phone calls and name-matching memories aggregated per person — no new truth store, identity resolved by accent/case folding with the confidence stated (exact vs normalized, honesty over false precision)
  • Persisted favorites: star anyone in one tap — the star survives its live signals expiring, leads the overview before the cap, and rides GDPR export/purge; idempotent PUT/DELETE with an optimistic, server-reconciled toggle
  • Readable at scale: stable-tint initial avatars, colored signal pills, distinct Favorites/Others bands with counts, a name filter past nine people, and a 🤝 badge for relations who are also connected LIA users (read-only peers bridge)
  • First-class navigation destination: desktop nav + mobile menu (one shared table); the spaces page keeps its permanent one-click door through the chat indicator, which now always renders
  • Three read capabilities the assistant was missing (ADR-193): past calls, open commitments and relayed messages are now answerable in chat. Each lives in the domain whose catalogue had none — a domain that can only write will push to write, which is how "when did I last call my wife?" became a plan to phone her and ask. All three project the SAME service the relationship card uses, so the tool and the card cannot disagree about who someone is, and each returns the exact total next to its page
  • Merging two relationships, manually and reversibly (ADR-193): folding decides who is literally the same spelling; it cannot know that a raw phone number and a name are one person. The user says so, once — and sees what was merged, with a per-row undo. The alias table is flat (no chain to walk, no cycle writable) and the merge never touches the peer directory: a display decision must not redirect a message to another account
  • Facts about a named peer, injected rather than searched (ADR-193, opt-in): naming a connected person used to correct only the routing, so the assistant announced a lookup for facts already one query away. The three local blocks are injected — never the connector-backed ones, because merely naming someone must not trigger an external call
  • A written debrief per relationship (ADR-269): where you stand with someone, what to raise next and what is worth remembering, written by the model at the top of the card — because ten sections is not something anyone reads. Built lazily at card open, never by a scheduler (relations_total is unbounded) and never during a chat turn, at most once per the reader's LOCAL day, with exactly three legitimate rebuilds: language, scope, an explicit ask. Nothing is invented — no evidence settles it empty with no model call, and a failed refresh KEEPS the previous text under a line saying so, because replacing a usable synthesis with an empty panel turns "I could not refresh this" into "there is nothing"
  • The 360° evidence assembly is one implementation, shared (ADR-269): it was extracted out of get_person_overview_tool into domains/relations/overview/, and the tool became its first consumer — two assemblies would be two authorities on who someone is (ADR-185). The extraction is pinned by a golden file captured on the code BEFORE it, 18 scope cases compared byte for byte. The provider half is now read only for the sections the scope asks for: up to eleven external calls used to be billed against a selection the reader had already made, which needs a third status — NOT_REQUESTED, since "I did not look, on purpose" is neither "found nothing" nor "could not look"
  • The debrief joins the chat with the OPPOSITE directive to the peer block (ADR-269, opt-in per account): the peer block states EXACT facts because it reads them in the turn itself; the same sentence over a dated synthesis would be a false-claim machine. The template says it is dated, carries its AGE, and sends every date, count and status to the tools. An ambiguous name match injects NOTHING — the directory holds every relationship ever opened, and a false positive hands one person's file to a question about another

Peer Connections — Users of the Same Instance, Assistant to Assistant

  • Opt-in discovery by exact full name (accent/case-folded, never prefix search): a masked-email hint disambiguates homonyms, results carry the relationship status, and an empty profile name plainly means "unfindable"
  • Connection lifecycle from chat or settings: request with an optional context note, accept/decline in one click (chat quick-actions or the « Connexions » settings section), removal notifies BOTH users through their assistants
  • Silent anti-harassment blocking: blocking ends the connection without notifying the other side — blocked, unknown and cooldown targets answer the byte-identical 404 (no existence leak, ADR-180)
  • Assistant-to-assistant relay: "tell Marie…" produces an HITL-confirmed draft; the recipient's OWN assistant delivers it in its personality, memory and language, naming the sender — whose assistant then confirms delivery; indirect speech is rephrased into direct address; LLM delivery cost is billed to the sender; quotas cap relays per day and per pair
  • Field-level read-only shares: calendar (free/busy or titled slots) and task titles — nothing shared by default, both directions visible to each side, every access re-validated at read time and recorded in a retention-pruned access log. Reads target the calendar or task list the owner configured as their default, never a hardcoded primary, and the answer is rendered in the ASKING user's timezone with all-day entries kept apart from real busy hours (a birthday blocks nothing at 10:00 — ADR-182)
  • Routing that knows who is a peer (ADR-182): the analyzer is given the user's accepted connections, so a named person is recognised as another USER of the instance rather than an address-book contact — with a deterministic guard that adds the peer domain (never substitutes it) when the verdict stays on a confusable one. The correction is counted and logged without the names
  • GDPR-complete: account export and purge cover both sides of every pair; message content is scrubbed after delivery

Internationalization (i18n) — 6 Languages

LIA is fully translated in 6 languages: English, French, German, Spanish, Italian, and Chinese.

  • Complete UI coverage: All interfaces, dialogs, notifications, error messages, FAQ, and landing page
  • HITL localized: Human-in-the-Loop approval prompts adapted per language
  • Proactive notifications: Heartbeat and reminders delivered in the user's language
  • Telegram: Inline keyboards and messages localized
  • Skills: Auto-translated descriptions in all 6 languages
  • react-i18next: Namespace-based translations with locales/{lang}/translation.json

Landing Page & Public Showcase

  • Animated hero chat demo: three rotating scenarios mirroring the real display modes — HITL draft approval, rich HTML weather card + proactive cross-domain initiative, multi-agent Markdown reply — with per-mode title-bar chips
  • Proof band: verifiable engineering numbers (agents, tools, providers, tests, ADRs, releases, audit score) sourced from the codebase (LANDING_STATS documents each origin)
  • Two-mode diagram: faithful LangGraph topology — router fork, five numbered pipeline steps (human approval highlighted), ReAct reason→act→observe loop, streaming convergence
  • /story field report (6 languages): how LIA is built — method, trade-offs, operations, measured audit profile — on the /why–/how guide pattern
  • SEO & OpenGraph: dynamically generated OG image, per-locale hreflang, JsonLd (WebSite, Organization, SoftwareApplication, breadcrumbs), llms.txt for AI crawlers
  • Public-route guard: the 401 handler's public-page list is pinned by a filesystem-completeness test — a new public page missing from the list fails CI instead of ejecting anonymous visitors to /login
  • Authenticated redirect: automatic redirect to dashboard if already logged in
  • The character on the home page: LIA's expressive face greets visitors in the capsule look — fixed while the page scrolls, draggable, its position kept apart from the chat's, no account needed

Administration & Monitoring

LIA includes a full-featured administration interface — giving operators complete control and real-time visibility over the system without touching configuration files or the database.

Admin Dashboard

A web-based administration panel covering every operational aspect:

Section Capabilities
LLM Configuration Model selection per node, provider parameters, temperature/token limits, prompt versions
RAG Knowledge Spaces Manage document spaces, embedding configuration, user reindex operations, system knowledge spaces (FAQ staleness, reindex)
Personalities Create and manage assistant personalities (tone, language, behavior rules)
User Management User accounts, roles, permissions, connector status overview
Connector Management Google/Apple/Microsoft OAuth status, token health, per-user provider activation
Skills Management Enable/disable skills, edit descriptions, translate in 6 languages, delete
MCP Servers Admin-level MCP server configuration, tool discovery, domain descriptions
LLM Pricing CRUD for the full LLM catalogue — provider, 8 capability flags (max input/output tokens, tools, structured output, strict mode, streaming, vision, reasoning), the accepted reasoning depths (ticked from the model's resolved family), the capability provenance badge and pricing (input/output/cache tokens) per model. Source of truth for the LangChain factory and the agent constraints. Live cross-worker invalidation, no redeploy
Image Generation Pricing CRUD for image models — provider, quality, size and pricing. Drives the user preferences dropdowns directly
Google API Pricing Per-endpoint pricing configuration for Google Maps Platform services
Voice Settings TTS catalogue management (Edge / OpenAI / ElevenLabs) via Configuration LLM (voice_tts type), per-provider tuning, voice picker (live ElevenLabs voices)
Broadcasting Send system-wide notifications to all users or targeted groups
Debug Settings Toggle debug panel visibility, configure diagnostic verbosity per user
Usage Limits Per-user token/message/cost quotas (period + global), real-time gauges, manual block/unblock, WebSocket live updates
Instance Daily Budget Instance-wide spend ceiling in euros (ADR-216) — today's spend, run count, the ceiling that actually applies and what remains. The operator value may only tighten the deployment bound, never widen it, and the panel shows both side by side
Platform Capabilities Ten capabilities (dictation, speech, images, uploads, document spaces, web search, browsing, skills, MCP, telephony) switched off instantly without redeploying (ADR-217) — each row shows the deployment bound, the operator choice and the state actually enforced, with an "Unavailable" badge and its reason
Public Demo Link Publish or retract the guided showroom link surfaced to visitors
Consumption Export CSV export of token usage, Google API usage, and aggregated consumption per user/period

Real-Time Debug Panel

A 24-section debug panel embedded in the chat interface, organized into 6 logical groups with always-visible sections (empty sections show "N/A" instead of disappearing):

Group Sections
Request Analysis Intent classification, Domain detection, Routing decision, Query transformations
Planning & Execution Planner output, Tool selection, Context resolution, Token budget, Execution timeline, ForEach analysis, Execution waves
Intelligent Mechanisms Cache hits, pattern learning, semantic expansion, Skills activation
Context Injection Memory injection (scores), RAG injection (scores), Knowledge enrichment (Brave), Journal injection (per-entry scores, budget)
Background Extraction Memory detection (create/update/delete), Journal extraction, Interest profile
LLM & API Pipeline Request lifecycle (timing breakdown per node), LLM Pipeline (chronological reconciliation), LLM call details (model, tokens, latency, cost), Google API calls

The debug panel is designed for developers and operators to diagnose issues, optimize prompts, and understand the agent's decision-making process in real time — without needing external tools or log access.


Quick Start

Prerequisites

Software Version Required
Python 3.14 Yes
Node.js 24 LTS Yes
Docker 24+ Yes
pnpm 10+ Yes
Task 3+ Yes (build tool)

All commands are defined in Taskfile.yml. Quick start: task setup then task dev.

Self-host installer (./install.sh)

A guided installer for production self-hosting lives at the repository root (ADR-215). Full guide: docs/guides/GUIDE_SELF_HOSTING.md — what it installs, every setting, and what to do when a step fails.

Its mode is conditional and the same rule holds before and after release qualification:

  • a complete source checkout (this repository) defaults to a local build of the API and Web images;
  • an official release directory defaults to prebuilt digests only when its adjacent lia-self-host-manifest.json is qualified (qualification="passed"); an absent or candidate manifest keeps the local-build default;
  • ./install.sh --local-build inside a release directory builds from the release's verified embedded source context, never from an unpinned checkout;
  • if neither a complete checkout nor a valid embedded context exists, the installer fails before touching anything and prints the exact qualified release asset to download.

The installer asks a short questionnaire (exposure: LAN, your own reverse proxy, or managed HTTPS with Caddy), generates a private .env and Compose overlay, applies the reference seeds atomically, creates the admin and provider keys over stdin (never argv), verifies the installation beyond /ready, and prints a non-secret report. Resume after interruption with ./install.sh --resume; adjust routing later with ./install.sh --reconfigure.

Express Setup (5 minutes)

# 1. Clone the repository
git clone https://github.com/jgouviergmail/LIA-Assistant.git
cd LIA-Assistant

# 2. Configure environment
cp .env.example .env  # Edit with your API keys

# 3. Full setup (backend + frontend + git hooks)
task setup

# 4. Start all services (API + Web + PostgreSQL + Redis + observability)
task dev
Manual setup (without Task)
# 1. Start the infrastructure
docker compose up -d postgres redis prometheus grafana

# 2. Backend setup
cd apps/api
python -m venv .venv && source .venv/bin/activate  # Windows: .venv\Scripts\activate
pip install --require-hashes -r requirements.lock.txt  # compiled lockfile (reproducible)
cp ../../.env.example .env  # Configure your API keys

# 3. Database migrations
alembic upgrade head

# 4. Frontend setup
cd ../web
pnpm install

# 5. Start the services
# Terminal 1 - Backend:
cd apps/api && uvicorn src.main:app --reload --port 8000

# Terminal 2 - Frontend:
cd apps/web && pnpm dev

Development URLs

Service URL Credentials
Frontend http://localhost:3000
API Docs http://localhost:8000/docs
Grafana http://localhost:3001 admin/admin
Prometheus http://localhost:9090

Minimal Configuration (.env)

# Database
DATABASE_URL=postgresql+asyncpg://user:pass@localhost:5432/lia
REDIS_URL=redis://localhost:6379/0

# Security (REQUIRED - change in production)
SECRET_KEY=change-me-in-production-use-openssl-rand-base64-32
FERNET_KEY=your-fernet-key-here

# LLM Provider API keys are configured via Admin UI after first login
# (Settings > Administration > LLM Configuration)
# At least one provider (typically OpenAI) is required.

# Google OAuth (optional)
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...

# Feature Flags (optional, disabled by default)
MCP_ENABLED=false              # Admin MCP servers
MCP_USER_ENABLED=false         # Per-user MCP (requires MCP_ENABLED)
CHANNELS_ENABLED=false         # Multi-channel messaging (Telegram)
HEARTBEAT_ENABLED=false        # Autonomous proactive notifications
SUB_AGENTS_ENABLED=false       # Persistent specialized sub-agents
SKILLS_ENABLED=false           # Skills system (agentskills.io standard)
RAG_SPACES_ENABLED=true        # RAG Knowledge Spaces (document upload & retrieval)
FCM_NOTIFICATIONS_ENABLED=false # Firebase push notifications

Architecture

Overview

Production targets include Raspberry Pi (ARM64) via multi-arch Docker builds (linux/amd64,linux/arm64).

┌─────────────────────────────────────────────────────────────────────────┐
│                        FRONTEND (Next.js 16 + React 19)                  │
│    Chat UI • Settings • i18n (6 languages) • SSE Streaming • Voice Mode  │
└─────────────────────────────┬───────────────────────────────────────────┘
                              │ HTTP-only cookies (session_id, 24h TTL)
┌─────────────────────────────┴───────────────────────────────────────────┐
│                     BACKEND (FastAPI + LangGraph 1.x)                    │
│                                                                          │
│  ┌────────────────────────────────────────────────────────────────────┐ │
│  │                 LangGraph Multi-Agent Orchestration                 │ │
│  │                                                                      │ │
│  │   Router → QueryAnalyzer → Planner → ApprovalGate → Orchestrator   │ │
│  │      ↓                                        ↓                     │ │
│  │   ┌─────────────────────────────────────────────────────────────┐  │ │
│  │   │  Contacts │ Emails │ Calendar │ Drive │ Tasks │ Reminders  │  │ │
│  │   │  Places │ Routes │ Weather │ Wikipedia │ Perplexity      │  │ │
│  │   │  Brave │ Web Search │ Web Fetch │ Browser │ Context │ Query│  │ │
│  │   └─────────────────────────────────────────────────────────────┘  │ │
│  │                              ↓                                      │ │
│  │               MCP Tools (per-user external servers)                │ │
│  │                              ↓                                      │ │
│  │                       Response Node (synthesis)                     │ │
│  └────────────────────────────────────────────────────────────────────┘ │
│                                                                          │
│  ┌─────────────────────────────────────────────────────────────────────┐│
│  │  Domain Services: Auth, Users, Connectors, RAG, Voice, Skills...    ││
│  └─────────────────────────────────────────────────────────────────────┘│
│                                                                          │
│  ┌─────────────────────────────────────────────────────────────────────┐│
│  │  Infrastructure: Redis (cache) • PostgreSQL (checkpoints) •         ││
│  │  MCP Client Pool • Prometheus (metrics) • Langfuse (traces)       ││
│  └─────────────────────────────────────────────────────────────────────┘│
└──────────────────────────────────────────────────────────────────────────┘

Two Execution Modes

LIA offers two execution strategies, switchable per user via a toggle in the chat header:

Pipeline mode (default) — A feat of engineering that delivers the same power as ReAct with 4–8× fewer tokens:

  1. A smart Planner decomposes the request into an optimized execution plan (DSL)
  2. A Semantic Validator checks plan coherence (cardinality, scope, dependencies)
  3. An Approval Gate handles HITL for mutations
  4. A Task Orchestrator executes tools in parallel waves via asyncio.gather()
  5. Bayesian learning optimizes planning patterns over time

ReAct mode (⚡) — The LLM reasons iteratively, calling tools one by one and adapting to each result. More autonomous but higher token cost. Ideal for exploratory, research, or ambiguous queries.

graph TD
    A[User Message] --> B[Router Node]
    B -->|conversation| C[Response Node]
    B -->|pipeline mode| D[Planner Node]
    B -->|react mode| R1[ReAct Setup]
    D --> E[Semantic Validator]
    E --> F{Approval Gate}
    F -->|approved| G[Task Orchestrator]
    F -->|rejected| C
    G --> H[Domain Agents + Tools]
    H --> G
    G --> C
    R1 --> R2[ReAct Call Model]
    R2 -->|tool_calls| R3[ReAct Execute Tools]
    R2 -->|done| R4[ReAct Finalize]
    R3 --> R2
    R4 --> C
    C --> J[SSE Stream]
Loading

Code Structure (DDD)

apps/api/src/
├── core/                    # Modular configuration (9 modules)
│   ├── config/              # Settings per domain
│   ├── constants.py         # Global constants
│   └── bootstrap.py         # Initialization functions
├── domains/                 # Bounded Contexts (DDD)
│   ├── agents/              # LangGraph nodes, services, tools
│   │   ├── nodes/           # Graph nodes (router, planner, react ×4, response...)
│   │   ├── services/        # Smart services, HITL
│   │   ├── tools/           # Domain-specific tools
│   │   └── orchestration/   # ExecutionPlan, parallel executor
│   ├── auth/                # JWT, sessions, OAuth
│   ├── connectors/          # Google + Apple + Microsoft clients, provider resolver
│   ├── conversations/       # Conversation CRUD & history
│   ├── google_api/          # Google API pricing & usage tracking
│   ├── rag_spaces/          # RAG Knowledge Spaces (upload, embed, retrieve, system FAQ)
│   ├── user_mcp/            # Per-user MCP servers (CRUD, OAuth, domain routing)
│   ├── voice/               # TTS factory, STT, Wake Word
│   ├── skills/              # Skills system (agentskills.io standard)
│   ├── sub_agents/          # Persistent specialized sub-agents (F6)
│   ├── interests/           # Interest Learning System
│   ├── heartbeat/           # Autonomous Heartbeat (Proactive Notifications)
│   ├── channels/            # Multi-channel messaging (Telegram)
│   ├── reminders/           # Reminder & notification scheduling
│   ├── scheduled_actions/   # Recurring scheduled actions
│   ├── journals/            # Personal Journals (introspective notebooks)
│   ├── health_metrics/      # iPhone Shortcuts health ingestion + charts
│   └── users/               # User management
└── infrastructure/          # Cross-cutting concerns
    ├── cache/               # Redis sessions, LLM cache
    ├── llm/                 # Factory, providers, embeddings
    ├── mcp/                 # MCP client pool, auth, security, tool adapters
    ├── browser/             # Playwright session pool, CDP accessibility
    ├── rate_limiting/       # Distributed rate limiter
    └── observability/       # Metrics, logging, tracing

Key Design Patterns

Tool System (5-layer architecture) — Tools are built in five composable layers: ConnectorTool[ClientType] (generic base with OAuth auto-refresh), @connector_tool (meta-decorator composing metrics + rate limiting + context save), Formatters (domain-specific result normalization), ToolManifest + Builder (declarative declaration with semantic keywords), and Catalogue Loader (dynamic introspection). Per-tool boilerplate reduced from ~150 to ~8 lines (94% reduction). Category-based rate limits: Read (20/min), Write (5/min), Expensive (2/5 min).

Domain Taxonomy — Each domain is a declarative DomainConfig (agents, result_key, related_domains, priority, routability). The DOMAIN_REGISTRY is the single source of truth consumed by SmartCatalogue (filtering), semantic expansion (adjacent domains), and the Initiative phase (structural pre-filter).

Data Registry — An InMemoryStore decouples tool results from message history. Results survive per-node message windowing (5/10/20 turns) via @auto_save_context, and cross-step references ($steps.X.field) resolve against the registry — this is what makes aggressive windowing viable without losing tool output context.

Semantic Validator — Before HITL approval, a dedicated LLM (distinct from the planner) inspects plans against 14 issue types across four categories: Critical (hallucinated capability, ghost dependency), Semantic (cardinality mismatch, scope overflow), Safety (dangerous ambiguity), and FOR_EACH-specific validations.

Adaptive Re-Planner — On execution failure, a rule-based analyser classifies the failure pattern and selects a recovery strategy. In Panic Mode, the SmartCatalogue expands to all tools for one retry, solving cases where domain filtering was too aggressive.

Connector Abstraction — Python protocols enable transparent switching between Google, Apple, and Microsoft providers. Normalizers convert provider-specific responses into unified domain models. The ProviderResolver guarantees only one provider per functional category (email, calendar, contacts, tasks).

Error Architecture — All tools return ToolResponse/ToolErrorModel with a ToolErrorCode enum (18+ types) and a recoverability flag. API-side centralized exception raisers replace raw HTTPException everywhere.

Feature Flags — Every optional subsystem is controlled by a {FEATURE}_ENABLED flag, checked at startup, route wiring, and node entry (instant short-circuit).

Full technical details: How does LIA work? — 25-section architecture guide


Technologies

Backend

Technology Version Role
Python 3.14 Primary runtime
FastAPI 0.136.3 REST API + SSE framework
LangGraph 1.2.11 Multi-agent orchestration
LangChain 1.3.15 LLM abstraction + tools
SQLAlchemy 2.0.50 Async ORM
Alembic 1.18.4 Database migrations
PostgreSQL 16 + pgvector Database + vector search
Redis 7.4.0 Cache, sessions, rate limiting
Pydantic 2.13.4 Validation + serialization
structlog latest Structured JSON logging
openai 2.x LLM provider
Edge TTS 7.2+ Voice synthesis (free)
mcp 1.9+ Model Context Protocol SDK (Streamable HTTP)
Docker 24+ Containerization (multi-arch amd64/arm64)

Frontend

Technology Version Role
Node.js 24 LTS JavaScript runtime
Next.js 16.2.11 React framework
React 19.2.7 UI library
TypeScript 6.0.2 Type safety
TailwindCSS 4.3.2 Styling
TanStack Query 5.101 Server state management
react-i18next 17.0.8 i18n (6 languages)
Radix UI latest Accessible UI primitives

Responsive Design: Fully optimized for desktop, tablet, and smartphone. Adaptive layouts, touch-friendly interactions, and mobile-first components ensure a seamless experience on any device.

Local Models as First-Class Models (ADR-267)

Any of the LLM slots can run on a model hosted on your own machine, with no cloud account involved. LIA drives Ollama through its native API rather than an OpenAI compatibility layer, which is what makes the difference:

  • Thinking is controlled, not endured — the configured depth reaches the server as think, including switching it off entirely, and the thinking trace comes back separated from the answer (streamed to the progress panel, as for DeepSeek).
  • The server declares the capabilities — tools, vision, thinking and context length are read from /api/show and feed both the runtime and the administration screen, so a depth never reaches a model that cannot think, and a control a local model would ignore is not offered.
  • The context window is requested, not assumedOLLAMA_NUM_CTX, else the model's own maximum capped at 32768. The same number decides when the history is summarised, so the accounting and the server agree.
  • Output cap, structured output and usage are nativenum_predict, the grammar-constrained format field, and token counts on every response.

Supported LLM Providers

Provider Models Use Case
OpenAI GPT-5.4, GPT-5.4-mini, GPT-5.2, GPT-5.1, GPT-5, GPT-5-mini/nano, GPT-4.1, GPT-4.1-mini/nano, GPT-4o, o1, o3-mini Primary (prompt caching, reasoning)
Anthropic Claude Opus 4.6/4.5, Claude Sonnet 4.6, Claude Haiku 4.5 Alternative (extended thinking)
Google Gemini 3.1/3/2.5 Pro, Gemini 3/2.5/2.0 Flash Multimodal
DeepSeek deepseek-v4-flash, deepseek-v4-pro (V4 family — thinking-mode toggle, v1.19.1+), deepseek-chat (V3, legacy), deepseek-reasoner (R1, legacy) Cost-effective reasoning. V4 supports tools + structured output via JSON-mode fallback when thinking is on.
Perplexity sonar-small/large-128k-online Web-augmented responses. Base URL configurable via PERPLEXITY_BASE_URL env var (v1.19.1+).
Qwen qwen3-max, qwen3.5-plus, qwen3.5-flash Thinking + tools + vision (Alibaba Cloud DashScope). Base URL configurable via QWEN_BASE_URL (regional US/CN swap, v1.19.1+).
Ollama Any local model (capabilities discovered from the server) Zero API cost, self-hosted. Native client (langchain-ollama): thinking control, num_ctx, grammar-constrained JSON, usage on every response. OLLAMA_BASE_URL (server root) + optional OLLAMA_NUM_CTX.

Observability

Technology Role
Prometheus 473 metrics
Grafana 28 dashboards
Loki Aggregated logs
Tempo Distributed tracing
Langfuse LLM observability
structlog Structured JSON logs

Documentation

Main Documentation

Document Description
GETTING_STARTED.md Detailed installation guide
ARCHITECTURE.md Complete system architecture
INDEX.md Full documentation map (190+ docs)

Technical Documentation

Domain Documents
Agents & LLM ARCHITECTURE_LANGRAPHPLANNERSEMANTIC_ROUTER
HITL HITL
Voice VOICEVOICE_MODE
Memory LONG_TERM_MEMORYMEMORY_RESOLUTION
MCP MCP_INTEGRATIONGUIDE_MCP_INTEGRATION
Heartbeat HEARTBEAT_AUTONOMEGUIDE_HEARTBEAT
Channels CHANNELS_INTEGRATIONGUIDE_TELEGRAM
Scheduled Actions SCHEDULED_ACTIONSGUIDE_SCHEDULED_ACTIONS
Skills SKILLS_INTEGRATION
Sub-Agents SUB_AGENTS
RAG Spaces GUIDE_RAG_SPACESADR-055ADR-058
Browser Control BROWSER_CONTROLADR-059
Personal Journals JOURNALSADR-057
LLM Providers LLM_PROVIDERS
CI/CD CI_CD
Security SECURITYOAUTHRATE_LIMITING
Observability OBSERVABILITY_AGENTSMETRICS_REFERENCE
Cost Tracking LLM_PRICING_MANAGEMENTGOOGLE_API_TRACKING

Practical Guides

Guide Description
GUIDE_DEVELOPPEMENT Complete development workflow
GUIDE_AGENT_CREATION How to create a new agent
GUIDE_TOOL_CREATION How to create a new tool
GUIDE_TESTING Testing strategy (24,042 backend tests across 1,451 files)
GUIDE_DEBUGGING LangGraph and log debugging

Architecture Decision Records (ADR)

272 ADR files (ADR-001 through ADR-273 — ADR-008 has no separate file) documenting major architectural decisions:


Tests

Running Tests

cd apps/api

# Unit tests (parallel: task test:backend:unit:fast, ~4 min)
pytest tests/unit -v

# Integration tests (require PostgreSQL + Redis)
pytest tests/integration -v

# LangGraph agent tests
pytest tests/agents -v

# Full coverage
pytest --cov=src --cov-report=html -v
# Report: htmlcov/index.html

Statistics

Metric Value
Total backend tests 20,468 collected (pytest tests/unit tests/agents --collect-only, 2026-08-27)
Frontend tests (vitest) 6,327 across 496 files (+ hermetic Playwright E2E specs incl. axe/dark/zoom)
Coverage floor 70% backend enforced, 71.37% measured (shrink-only ratchet) · frontend thresholds per glob
CI Workflows 3 (CI, Security, Release)
Technical audit 8.3/10 across 24 normalized areas — full public report & protocol

CI/CD

LIA uses a two-layer quality gate: a local pre-commit hook (fast, on staged files only) and a GitHub Actions CI pipeline (comprehensive, on every push/PR to main).

Pipeline Overview

Pre-commit (local)              GitHub Actions CI
===================             ==================
.bak files check                Lint Backend (Ruff + Black + MyPy)
Secrets grep                    Lint Frontend (ESLint + TypeScript)
Ruff + Black + MyPy             Fast unit tests + coverage (70%)
Fast unit tests                 Integration tests (PostgreSQL + Redis)
Critical pattern detection      Agents suite
i18n keys sync                  Code Hygiene (i18n, Alembic, lockfiles, patterns)
Alembic migration conflicts     Docker build smoke test
.env.example completeness       Secret scan (Gitleaks)
ESLint + TypeScript check       ──────────────────────
                                Security workflow (weekly)
                                  CodeQL (Python + JS)
                                  Dependency audit (pip-audit + pnpm audit)
                                  Trivy filesystem scan
                                  SBOM generation

Key Practices

Practice Implementation
SHA-pinned Actions All GitHub Actions pinned by commit SHA (supply-chain security)
Reproducible builds Universal Python lockfiles (linux/amd64 + arm64 + Windows), SHA256 hash-verified installs everywhere; CI guard fails manifest edits without lock regeneration (ADR-112)
Least privilege permissions: contents: read on CI workflow
Branch protection PR required (external contributors), 7 status checks, force push forbidden
Dependabot Weekly updates for pip, npm, Docker, Actions — minor/patch grouped
Pre-commit / CI alignment CI covers everything the pre-commit does (and more)
Coverage threshold 70% enforced in CI, 71.37% measured — a shrink-only ratchet: never lowered, raised only while at least 2 points of margin remain against the measurement
Documentation gate Every version and threshold a document states is recomputed from the code that owns it and a mismatch fails the build; links, code paths and unreachable documents too

Workflows

Workflow Trigger Jobs
CI (ci.yml) Push to main, PR 8 jobs: lint, unit tests, integration tests, code hygiene, docker build, secret scan
Security (security.yml) PR, weekly schedule, manual CodeQL, dependency audit, Trivy, SBOM
Release (release.yml) Tag v* Docker multi-arch build + push (ghcr.io), GitHub Release

Full details: CI/CD Documentation


Performance

Key Metrics (P95)

Metric Value SLO
API Latency 450ms < 500ms
First SSE event (request acknowledged) 380ms < 500ms
Router Latency 800ms < 2s
Planner Latency 2.5s < 5s
Gemini Embedding ~100-200ms < 300ms
Token Reduction (Windowing) 93% > 80%
Context Compaction Savings ~60% per compaction

These figures measure the infrastructure. The full perceived response time depends on the LLM call cascade (seconds to tens of seconds depending on request complexity and hardware) — this is the main optimization programme in progress, measured in production. The July 2026 technical audit scores Performance 7.5/10: instrumentation and caching are in place, but no sustained load campaign has been executed yet.

Implemented Optimizations

  • Message Windowing: 5/10/20 turns depending on node
  • Context Compaction: LLM summarization of old messages (dynamic threshold from response model context window, configurable via COMPACTION_* settings)
  • Prompt Caching: OpenAI/Anthropic (90% discount)
  • Gemini Embeddings: gemini-embedding-001 with asymmetric task types (multilingual)
  • Parallel Execution: asyncio.gather for independent domains
  • Redis O(1): Optimized operations (vs O(N) SCAN)
  • Connection Pooling: httpx persistent connections

Security

Compliance

Standard Status
GDPR PII filtering, data minimization
OWASP Top 10 XSS, SQL injection, CSRF protection
Prompt Injection External content wrapping (<external_content> safety markers), trust classified by DATA TYPE rather than by producing tool, and provenance that survives history compaction — a summary built from third-party text inherits its provenance banner instead of promoting the claim to system authority
OAuth 2.1 Mandatory PKCE
Supply chain Hash-verified universal lockfiles, pip-audit on the full transitive tree, SBOM per release
Untrusted code Skill scripts execute in a throwaway container: no Docker socket, no network, read-only filesystem, unprivileged uid, all capabilities dropped — and no sandbox means no execution, never a weaker fallback
Resource abuse Global Redis-backed rate limit in front of every route, request bodies bounded before they are read (webhooks included, ahead of authentication)

Reporting a Vulnerability

DO NOT create a GitHub Issue for security vulnerabilities.

Send an email to liamyassistant@gmail.com with:

  • Description of the vulnerability
  • Steps to reproduce
  • Potential impact

We respond within 48 hours.


Contributing

We welcome all contributions! See our Contributing Guide to get started.

Quick Start for Contributors

# 1. Fork and clone
git clone https://github.com/YOUR-USERNAME/LIA-Assistant.git
cd LIA-Assistant

# 2. Create a branch
git checkout -b feature/my-feature

# 3. Full setup (backend + frontend + git hooks)
task setup

# 4. Develop and test
task test:backend:unit:fast

# 5. Commit (Conventional Commits)
git commit -m "feat(agents): add weather forecast agent"

# 6. Push and create PR
git push origin feature/my-feature

Types of Contributions

  • Bug fixes
  • New features
  • Documentation
  • Tests
  • i18n translations (6 supported languages)
  • Performance optimizations

Standards

  • Python: Black + Ruff + MyPy (strict)
  • TypeScript: ESLint + Prettier
  • Commits: Conventional Commits
  • Coverage: >= 45% enforced in CI (ratchet +2 per release, never lowered)
  • Pre-commit hook: Installed via task setup — runs linters + tests on staged files
  • CI: All PRs must pass 7 status checks before merge (see CI/CD)

Support

Getting Help

Channel Usage
GitHub Issues Bugs, feature requests
GitHub Discussions Questions, ideas
liamyassistant@gmail.com General inquiries

Resources


License

This project is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0).

See LICENSE for details.

A commercial license is also available for organizations that cannot comply with AGPL-3.0 terms. Contact liamyassistant@gmail.com for details.


Acknowledgments

Open Source Technologies

This project builds on excellent open source technologies:

Backend & Infrastructure

Frontend

Observability

  • Prometheus - Metrics & alerting
  • Grafana - Dashboards & visualization
  • Loki - Log aggregation
  • Tempo - Distributed tracing
  • Langfuse - LLM observability & prompt management

Inspirations


LIA — Next-Generation Intelligent Conversational Assistant

Built with ❤️ using Python, Node.js, FastAPI, LangGraph, and Next.js

Back to top

About

Smart multi-agent conversational assistant with LangGraph orchestration, Human-in-the-Loop, enterprise-grade observability, and full i18n support (6 languages)

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

45 stars

Watchers

2 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages