Jarvis AI Voice Assistant

repository·main·Indexed 23 days ago

https://github.com/isair/jarvis

A private, offline AI voice assistant that runs locally on Windows, macOS, and Linux. Jarvis features conversational awareness, unlimited memory via a knowledge graph, and a built-in dictation mode. It integrates with the Model Context Protocol (MCP) for tool extension and supports various LLM providers including Ollama, LM Studio, and Jan. Key capabilities include Whisper-based speech recognition, Piper TTS, and local location detection using GeoLite2.

Tokens
46.9K
Snippets
30
Records
231
Agent score
80%

What's inside Jarvis

  1. Understand Jarvis core features and capabilities

    main

    Jarvis is a 100% private, local AI voice assistant designed for natural, conversational interaction. Key features include:

    • Conversational Awareness: Understands context in ongoing discussions. You can say "Jarvis, what do you think?" and it will understand the preceding conversation.
    • Unlimited Memory: Maintains a knowledge graph of your conversations and preferences. Includes a Memory Viewer GUI.
    • Natural Voice Interaction: Supports natural wake word placement (say "Jarvis" anywhere in a sentence) and echo detection to ignore its own speech.
    • Dictation Mode: A free, offline alternative to WisprFlow. Hold a hotkey, speak, and release to paste text into any application.
    • Extensible via MCP: Integrates with the Model Context Protocol (MCP) to connect to tools like GitHub, Slack, Home Assistant, and more.
    • Smart Tool Selection: Uses embedding-based relevance filtering to select the correct tools from your collection without performance degradation.
    • Built-in Tools: Includes web search (DuckDuckGo → Brave → Wikipedia), screenshot OCR, weather, file access, and nutrition tracking.
  2. How the Diary Summariser works

    main

    The diary summariser (conversation.py::generate_conversation_summary) condenses recent conversation chunks (the last 10) into a single daily row in the conversation_summaries table. This summary is used for downstream memory tasks like vector search, Full-Text Search (FTS), and knowledge-graph extraction.

    Core Behavior:

    • Input: Recent conversation chunks + the previous summary for that day (if available).
    • Output: A free-form summary (≤ 200 words) and 3–5 comma-separated topic keywords.
    • Storage: One row per (date_utc, source_app) in conversation_summaries, which is upserted on each update.
    • Embedding: The concatenation of the summary and topics is embedded and stored for vector retrieval.
    • Error Handling: LLM failures are non-fatal; the summariser returns (None, None) and the update is skipped. Pending messages remain queued for the next cycle.
  3. Understand the Agentic-Loop Evaluator

    main

    Overview

    Note: This component is deprecated. The task-list planner (planner.spec.md) replaces its per-turn correction role. This information is preserved for reference only.

    In the legacy system, the Evaluator was a lightweight LLM used after each agentic-loop turn that produced natural-language content (instead of a tool call). Its purpose was to decide whether the loop should terminate (the agent has finished) or continue (the agent replied in prose when it should have used a tool).

    Decision Logic

    The evaluator uses a binary decision model:

    • Terminal: The agent has satisfied the user request, delivered a real answer, or truthfully stated it cannot perform the action. Both 'satisfied' and 'needs_user_input' states are treated as terminal.
    • Continue: The user expressed a clear action that a tool in the allow-list could perform, but the agent responded with prose (suggestions, offers, or descriptions) instead of invoking the tool.

    It also acts as a safety mechanism for garbled output. If the agent produces raw tool-protocol markers, truncated JSON, or special sentinel tokens (like <unused88>), the evaluator attempts to salvage the intent or nudges the agent to produce a natural-language reply.

  4. Understand the Jarvis Listening Modes and State Transitions

    main

    Jarvis operates in three distinct listening modes that transition based on user interaction and system state:

    1. Wake Word Mode (Default): The system listens for the specific wake word. When detected, it triggers the Intent Judge.
    2. Hot Window Mode: Triggered after TTS (Text-to-Speech) finishes. The system stays in a high-sensitivity listening state for a set duration (hot_window_seconds) to allow for immediate follow-up questions without repeating the wake word.
    3. During TTS: While Jarvis is speaking, the system monitors for a 'Stop' command to interrupt the playback.

    State Transition Logic

    • WakeWord $\rightarrow$ IntentJudge: Triggered when a wake word is detected via text.
    • IntentJudge $\rightarrow$ DuringTTS: Triggered when a query is successfully dispatched and TTS starts.
    • DuringTTS $\rightarrow$ HotWindow: Occurs when TTS ends, subject to echo_tolerance.
    • HotWindow $\rightarrow$ IntentJudge: Triggered if speech is detected during the hot window.
    • HotWindow $\rightarrow$ WakeWord: Occurs if the hot window timer expires without speech detection.
    stateDiagram-v2
        direction LR
        [*] --> WakeWord: System Starts
    
        WakeWord: Listening for Wake Word
        HotWindow: Listening for Follow-up
        DuringTTS: TTS Playing
    
        WakeWord --> IntentJudge: Wake detected (text-based)
        IntentJudge --> DuringTTS: Query dispatched, TTS starts
        IntentJudge --> WakeWord: Not directed / no query
        DuringTTS --> HotWindow: TTS ends + echo_tolerance
        HotWindow --> IntentJudge: Speech detected
        HotWindow --> WakeWord: Timer expires
        DuringTTS --> WakeWord: Stop command detected
  5. Understand the LLM Backend architecture

    main

    The jarvis.llm package provides a provider-agnostic interface for interacting with various LLM runtimes. It allows the same core logic (reply engine, planner, tools, etc.) to run against different backends by subclassing LLMBackend.

    Supported runtimes include:

    • Ollama
    • OpenAI-compatible servers (e.g., LM Studio, oMLX, llama.cpp's llama-server, vLLM, LocalAI)
    • Anthropic-compatible servers

    There are two primary ways to interact with backends:

    1. Object-style (Preferred): Use get_llm_backend(cfg) to get a backend instance. This is the standard used throughout the project because it allows swapping providers via configuration without changing call sites.
    2. Function-style: Use call_llm_direct(base_url, ...) for lightweight wrappers when only a base URL is available (common in performance testing or evaluation scripts).
  6. How the Jarvis startup flow works

    main

    The startup sequence is designed to be resilient, especially when local dependencies like Ollama are involved.

    The sequence follows these logic gates:

    1. Single Instance Check: Prevents multiple copies. If an instance is already running, it offers to terminate the old one.
    2. Splash Screen: An animated loading screen is shown immediately.
    3. Setup Check: If no setup has been completed, the Setup Wizard is launched.
    4. Ollama Gating:
      • If using an OpenAI-compatible provider (remote chat/embeddings), Ollama checks are skipped.
      • If Ollama is used, the app attempts to auto-start the Ollama server (up to 15s wait).
      • If Ollama fails to start or models are missing, the Setup Wizard is presented for diagnosis.
    5. Reachability Check: For OpenAI-compatible setups, the app performs a one-off check (GET /v1/models) to ensure the remote server is reachable, showing a warning if it fails.
    6. Initialization: Once dependencies are verified, the system tray is initialized, the daemon thread starts, and the splash screen closes.
  7. Manage PortAudio lifecycle with the audio lock

    main

    To prevent application crashes (especially on Windows) caused by non-thread-safe PortAudio calls, all stream lifecycle operations must be serialized using the process-wide lock: jarvis.utils.audio_lock.portaudio_lock.

    Required Operations under lock:

    • InputStream/OutputStream construction
    • start / stop / close / abort calls

    Implementation Note: The system uses _serialised_stream instead of a standard with stream: context manager to ensure these calls are properly synchronized across the dictation engine, TTS, and thinking tune.

  8. How the Desktop App integrates with the Jarvis daemon

    main

    The Jarvis desktop app manages the daemon in two different modes depending on whether you are running a production build or a development environment:

    1. Bundled Mode (Production): The daemon runs within the same process using a QThread. This allows for direct callback registration via set_diary_update_callbacks() for features like diary updates.
    2. Subprocess Mode (Development): The daemon runs as a separate process. Communication (IPC) is handled via stdout. The desktop app intercepts log lines to drive the UI.

    Daemon Callbacks (Bundled Mode)

    When the daemon is running in a QThread, the DiaryUpdateDialog receives the following signals:

    • on_chunks: A list of conversation chunks being summarized.
    • on_token: Streaming tokens as the diary is generated.
    • on_status: Status messages (e.g., "Writing diary entry...").
    • on_complete: A signal indicating completion (success or failure).

    IPC via stdout (Subprocess Mode)

    In development, the daemon emits JSON events prefixed with __DIARY__: to communicate with the UI. For example: __DIARY__:{"type":"token","data":"Hello"}

    __DIARY__:{"type":"token","data":"Hello"}
  9. Consolidate Knowledge Graph nodes

    main

    Jarvis uses a "rewrite-on-write" strategy to keep the Knowledge Graph clean via merge_node_data(). This prevents the graph from becoming cluttered with redundant or contradictory information.

    Consolidation Rules: When new facts are added to an existing node, the system applies these rules:

    1. Contradictions: If a new fact contradicts an old one, the old version is dropped.
    2. Near-Duplicates: Similar phrasings are collapsed into a single entry.
    3. Patterns: Repeated daily activities are consolidated into general patterns.
    4. Pruning: Common-knowledge facts are removed.

    Safety Mechanisms:

    • Hallucination Guard: If the LLM produces a rewrite that is significantly larger than the input (len(existing) + len(new) + 2), the rewrite is rejected, and the system falls back to a simple append_to_node to prevent data loss or bloat.
    • Maintenance: Users can trigger a full consolidation of all populated nodes using the 🧹 button in the memory viewer (invokes consolidate_all_populated_nodes).
  10. Understand the Intent Judge evaluation

    main

    The Intent Judge is a specific component of Jarvis responsible for voice intent classification. It is currently pinned to the gemma4:e2b model. Unlike other parts of the system, the Intent Judge's performance is independent of the primary judge model.

    Evaluations for the Intent Judge cover a wide range of edge cases, including:

    • Cross-segment imperatives: Resolving commands that span multiple segments (e.g., cross_segment_answer_that_with_noise).
    • Wake word behavior: Handling wake words in various contexts like trailing after brand names (wake_word_trailing_after_capitalised_brand) or mid-sentence.
    • Hot window mode: Ensuring the system correctly identifies when it is in an active listening state.
    • Echo detection: Verifying that TTS (Text-to-Speech) text is correctly identified to prevent the system from responding to its own voice.
  11. Web Search Tool Timeouts and Budgets

    main

    The tool manages latency through a hierarchical budget system:

    • _TOTAL_WALL_CLOCK_SEC (20s): The absolute maximum time allowed for the entire provider chain (DDG + Brave + Wikipedia).
    • _CASCADE_WALL_CLOCK_SEC (8s): The maximum time allowed for a single parallel fetch pool (e.g., the top 3 DuckDuckGo results).

    Before moving to Brave or Wikipedia, the tool checks the remaining budget. If the budget is exhausted, remaining providers are skipped and an 'honest-block' envelope is emitted to ensure predictable latency.

  12. Extract facts into the Knowledge Graph

    main

    After a daily conversation summary is generated, Jarvis runs extract_graph_memories() to pull structured facts from the text into a Knowledge Graph.

    Fact Classification (Branches): Facts are categorized into one of three branches based on a heuristic:

    • USER: Information about the user (e.g., "I live in London").
    • DIRECTIVES: Instructions on how the assistant should behave.
    • WORLD: External facts (e.g., "The capital of France is Paris").

    Heuristics & Guards:

    • Avoid Ephemera: The system is instructed not to extract transient snapshots like the current time or weather as persistent facts.
    • Avoid Assistant Recommendations: It distinguishes between external facts and recommendations generated by the assistant to prevent polluting the graph with model-generated suggestions.