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 !
Features • Admin & Monitoring • Quick Start • Architecture • Documentation • Contributing
Version 1.43.1 — A 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.
- Why LIA?
- Try LIA Online
- Built by an AI, Directed by a Human
- Screenshots
- Features
- Administration & Monitoring
- Quick Start
- Architecture
- Technologies
- Documentation
- Tests
- CI/CD
- Performance
- Security
- Contributing
- Support
- License
- Acknowledgments
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 |
📅 "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"
LIA is available as a hosted service at https://lia.jeyswork.com/ — no installation required.
Interactive showroom — lia.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.
"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/
Dashboard — Homepage with quick access, usage statistics, and personalized greeting
Chat — Multi-agent conversation with real-time debug panel (right sidebar)
More screenshots
Chat — Debug panel: per-message routing, tool calls, token cost and reasoning timeline
Chat — Interactive skill widgets: maps, dashboards, calendars and mini-apps rendered inline
Settings — Preferences: connectors, MCP servers, language, timezone, and themes
Settings — Features: LIA Style, long-term memory, interests, proactive notifications, scheduled actions, sub-agents, channels
Settings — Long-term memory: pinned facts, automatic extraction, edit / delete / pin per memory
Settings — Psyche Engine: Big Five personality traits modulating the assistant's emotional responsiveness
Settings — Administration: LLM config, RAG Spaces, users, connectors, pricing, skills, voice, broadcast, debug
Administration — One-click simplicity: every admin action is accessible in a single click, no technical skills required
Administration — LLM Configuration: 7 providers (OpenAI, Anthropic, Google Gemini, DeepSeek, Qwen, Perplexity, Ollama), per-node model selection
- 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 viapostMessage), iframe auto-resize, CSP-sandboxed client-side interactivity (addEventListener,crypto.getRandomValues), bundledsegnofor 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_8000audio - 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).
/resumecommand for manual trigger. 4 HITL safety conditions prevent compaction during active approval flows - Scroll-up History Pagination:
GET /conversations/me/messagesexposes a keyset cursor (?before=<created_at>) withhas_more/next_cursor. The chat UI binds anIntersectionObserveron a top sentinel — older pages prepend with id-based dedup, scroll position preserved via a sharedwasPrependRefthat 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
- 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
- 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-motionfreezes it into static poses
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 inprovider_configJSONB. 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.XXXbadge 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.
- 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 asinterruptedwith 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_transcriptionslot, then ElevenLabs Scribe / OpenAIgpt-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_synthesisslot fills the user's sections (paragraph, bullets, topics, action items), condensing the transcript part by part when it overflows the model's window;repair_reportfolds 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_tokensunder the archived message'srun_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 yieldsnull, 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:
replacerewrites in place through the durable regeneration;newderives a second meeting row pointing at its source (source_meeting_id, FKSET 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_FROMthroughEmailService, 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,GLOBALin 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.
- A processed notification queues a wake, and the webhook stays dumb:
SET NXper (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_idsandskip_probabilistic_gateand 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.txtlisting files gone from the disk, refused beyondRAG_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_idis 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.
# 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
| 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 |
- 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 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)
- 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
- 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
- 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=trueto enable
| 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.
- 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
| 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_idforced 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.pydraws the line —provider_api_keyshas nouser_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.pynames 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
- 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_EURand 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 aRetry-Afterto 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 indocs/runbooks/DATABASE_BACKUP_RESTORE.md
- Three registers, never one list with a filter:
agent_effectstakes one row per ACTION (claimed before it happens, closed from an explicit result),agent_treatmentsone row per CONSULTATION (no uniqueness, no owner token, no content column — which capability, when, how long, with what outcome), andagent_decisionsone 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)
- 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,$refinto$defs,const,enuminference — 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/destructiveHintas 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_totalwith 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,
issvalidation 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=trueto enable per-user
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.jsonbaked 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:androiddrives 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
- 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/BaseChannelWebhookHandlerabstraction for future channels (Discord, WhatsApp) - Observability: 12 dedicated Prometheus RED metrics (latency, errors, volumes)
- Feature flag:
CHANNELS_ENABLED=trueto enable
- 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=trueto enable
- 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.pyreplays 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
- 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
- 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=trueto enable (default: false)
- 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_querydetection 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)
- 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
level—L0raw observation,L1operational directive (WHEN→DO BECAUSE),L2transversal pattern,L3portrait facet. L2/L3 are produced exclusively at consolidation through active topic clustering (ADR-079) - Epistemic status:
confidence∈ {low, medium, high} plusevidence_countandcontradiction_countcounters per entry. The journal distinguishes hypotheses still in test from observations validated across many turns - Deferred self-evaluation T → T+1:
MessagesState.injected_journal_idscarries IDs across turns; the post-conversation extractor sees the previous turn's directives + the current user reaction, signalsevidence_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 onsearch_hintskeywords. Search usesLEAST(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 aportrait_brief(~60 tokens) diffused across 6 secondary flows (ReAct setup, interest proactive, reminder notification, voice, heartbeat, fallback sync+async). Standalone builderbuild_journal_user_model_block(user_id, format, flow)mirrorsbuild_psyche_prompt_block. - Three corrective levers on the portrait (never directly editable): edit L3 source entries,
POST /journals/portrait/feedback(free text → L0user_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_validatoron 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
portraitkey - Feature flags:
JOURNALS_ENABLED=false(system), user-level toggle in Settings > Features. ADRs: ADR-057 → ADR-064 → ADR-069 → ADR-079
- Two token-authenticated endpoints (
POST /api/v1/ingest/health/stepsand/api/v1/ingest/health/heart_rate): an iPhone Shortcut automation pushes daily batches of samples. Each sample carries its own ISO 8601date_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 akinddiscriminator (heart_rate|steps). Extending tospo2/sleep/caloriesreduces to a newkindvalue — no new table, no new endpoint. - Idempotent UPSERT (
ON CONFLICT (user_id, kind, date_start, date_end) DO UPDATE) using PostgreSQL'sRETURNING (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 CASCADEon 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 (
413beyond). - Feature flag:
HEALTH_METRICS_ENABLED=false(system). ADR-076 · Guide iPhone · Technical doc
- Single
health_agentwith 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, mirroringemail_agent/event_agent. time_min/time_maxwindowed queries: aggregation tools accept ISO 8601 bounds exactly likecalendar_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.messageso the Response LLM surfaces them without reaching intostructured_data(pattern fromweather_tools). - Extensible registry (
HEALTH_KINDS): adding sleep / SpO2 / calories = one entry inkinds.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
bootstrap→rollingmode switch after 7 days of data, tunable thresholds (HEALTH_METRICS_VARIATION_*env vars). - Heartbeat / Memory / Journal integration:
health_signalssource injected for proactive context;context_biometricJSONB 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_enabledtoggle governs the four integrations (tool access, Heartbeat, memory extraction, journal injection).PATCH /auth/me/health-metrics-agents-preference.
- 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 iframesandbox(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_meconvention: MCP servers exposing aread_metool 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)
- 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 (
exactvsnormalized, 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_totalis 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_toolintodomains/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
- 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
peerdomain (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
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
- 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_STATSdocuments each origin) - Two-mode diagram: faithful LangGraph topology — router fork, five numbered pipeline steps (human approval highlighted), ReAct reason→act→observe loop, streaming convergence
/storyfield 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.txtfor 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
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.
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 |
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.
| 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.
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.jsonis qualified (qualification="passed"); an absent or candidate manifest keeps the local-build default; ./install.sh --local-buildinside 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.
# 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 devManual 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| Service | URL | Credentials |
|---|---|---|
| Frontend | http://localhost:3000 | — |
| API Docs | http://localhost:8000/docs | — |
| Grafana | http://localhost:3001 | admin/admin |
| Prometheus | http://localhost:9090 | — |
# 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 notificationsProduction 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) ││
│ └─────────────────────────────────────────────────────────────────────┘│
└──────────────────────────────────────────────────────────────────────────┘
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:
- A smart Planner decomposes the request into an optimized execution plan (DSL)
- A Semantic Validator checks plan coherence (cardinality, scope, dependencies)
- An Approval Gate handles HITL for mutations
- A Task Orchestrator executes tools in parallel waves via
asyncio.gather() - 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]
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
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
| 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) |
| 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/showand 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 assumed —
OLLAMA_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 native —
num_predict, the grammar-constrainedformatfield, and token counts on every response.
| 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) |
| 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. |
| Technology | Role |
|---|---|
| Prometheus | 473 metrics |
| Grafana | 28 dashboards |
| Loki | Aggregated logs |
| Tempo | Distributed tracing |
| Langfuse | LLM observability |
| structlog | Structured JSON logs |
| Document | Description |
|---|---|
| GETTING_STARTED.md | Detailed installation guide |
| ARCHITECTURE.md | Complete system architecture |
| INDEX.md | Full documentation map (190+ docs) |
| Domain | Documents |
|---|---|
| Agents & LLM | ARCHITECTURE_LANGRAPH • PLANNER • SEMANTIC_ROUTER |
| HITL | HITL |
| Voice | VOICE • VOICE_MODE |
| Memory | LONG_TERM_MEMORY • MEMORY_RESOLUTION |
| MCP | MCP_INTEGRATION • GUIDE_MCP_INTEGRATION |
| Heartbeat | HEARTBEAT_AUTONOME • GUIDE_HEARTBEAT |
| Channels | CHANNELS_INTEGRATION • GUIDE_TELEGRAM |
| Scheduled Actions | SCHEDULED_ACTIONS • GUIDE_SCHEDULED_ACTIONS |
| Skills | SKILLS_INTEGRATION |
| Sub-Agents | SUB_AGENTS |
| RAG Spaces | GUIDE_RAG_SPACES • ADR-055 • ADR-058 |
| Browser Control | BROWSER_CONTROL • ADR-059 |
| Personal Journals | JOURNALS • ADR-057 |
| LLM Providers | LLM_PROVIDERS |
| CI/CD | CI_CD |
| Security | SECURITY • OAUTH • RATE_LIMITING |
| Observability | OBSERVABILITY_AGENTS • METRICS_REFERENCE |
| Cost Tracking | LLM_PRICING_MANAGEMENT • GOOGLE_API_TRACKING |
| 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 |
272 ADR files (ADR-001 through ADR-273 — ADR-008 has no separate file) documenting major architectural decisions:
- ADR-007: Service Layer Pattern for Node Complexity
- ADR-048: Semantic Tool Router
- ADR-051: Reminder & Notification System
- View all ADRs
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| 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 |
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).
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
| 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 |
| 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
| 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.
- 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
| 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) |
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.
We welcome all contributions! See our Contributing Guide to get started.
# 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- Bug fixes
- New features
- Documentation
- Tests
- i18n translations (6 supported languages)
- Performance optimizations
- 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)
| Channel | Usage |
|---|---|
| GitHub Issues | Bugs, feature requests |
| GitHub Discussions | Questions, ideas |
| liamyassistant@gmail.com | General inquiries |
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.
This project builds on excellent open source technologies:
Backend & Infrastructure
- Python - Primary runtime
- FastAPI - Modern async web framework
- LangGraph - Multi-agent orchestration
- LangChain - LLM abstraction & tools
- SQLAlchemy - Async ORM
- Pydantic - Data validation & settings
- Alembic - Database migrations
- PostgreSQL + pgvector - Database & vector search
- Redis - Cache, sessions, rate limiting
- Google Gemini Embeddings - gemini-embedding-001 for multilingual semantic search
- Edge TTS - Free neural voice synthesis
- structlog - Structured JSON logging
- Docker - Containerization & multi-arch builds
Frontend
- Node.js - JavaScript runtime
- Next.js - React framework
- React - UI library
- TypeScript - Type safety
- TailwindCSS - Utility-first styling
- Radix UI - Accessible UI primitives
- TanStack Query - Server state management
- react-i18next - Internationalization (6 languages)
Observability
- Prometheus - Metrics & alerting
- Grafana - Dashboards & visualization
- Loki - Log aggregation
- Tempo - Distributed tracing
- Langfuse - LLM observability & prompt management
LIA — Next-Generation Intelligent Conversational Assistant
Built with ❤️ using Python, Node.js, FastAPI, LangGraph, and Next.js

