Jesse A. Green / notebook

Jan Administrator & Developer Guide

Generated June 2026 from janhq/jan at 15e372f

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

This guide is the in-depth technical reference for Jan — its architecture, configuration, security model, operations, and internals. It is written for administrators deploying Jan on managed machines, developers building against or contributing to Jan, and architects evaluating how it works. For end-user instructions (installing, chatting, managing models), see the companion User Guide.

Everything here is derived directly from the Jan source code at the documented commit. Where the source contradicts common assumptions, this guide follows the source.


Table of Contents

  1. Executive Summary
  2. System Architecture
  3. Component Inventory
  4. Repository Structure
  5. System Requirements
  6. Build & Packaging
  7. Configuration Reference
  8. The Tauri Command (IPC) Surface
  9. Inference Engines & Plugins
  10. The Extension System
  11. The Local API Server
  12. MCP Subsystem
  13. Remote Provider Integration
  14. RAG & Vector Database
  15. Security Analysis
  16. Operations
  17. Data Model & Storage
  18. Code Quality & Conventions
  19. Potential Improvements
  20. Quick Reference

1. Executive Summary

Jan is a cross-platform desktop (and mobile) application for running large language models locally and connecting to cloud providers, built by Menlo Research. It is a privacy-first, open-source alternative to cloud chat applications.

Architecture at a Glance

Jan is a Tauri 2 application: a Rust backend (src-tauri/) hosting a React 19 frontend (web-app/) in a system WebView. Inference is provided by:

  • llama.cpp — for GGUF models on all platforms, managed by an in-process router (a single llama-server process that loads/unloads models on demand), driven by the Rust tauri-plugin-llamacpp plus the TypeScript @janhq/llamacpp-extension.
  • MLX — for Apple Silicon, via a bundled Swift mlx-server binary, the tauri-plugin-mlx Rust plugin, and @janhq/mlx-extension.
  • Cloud providers — OpenAI, Anthropic, Google, Mistral, Groq, xAI, OpenRouter, and more, via the Vercel AI SDK in the frontend.

It additionally exposes an OpenAI-compatible HTTP server (default 127.0.0.1:1337), a jan CLI binary (a separate Cargo build target), an MCP host for tool use, and a built-in RAG pipeline (document parsing + a SQLite vector store).

Key Facts

Property Value
Vendor Menlo Research
App version (Cargo) 0.8.2 (src-tauri/Cargo.toml)
Web-app version 0.7.6
@janhq/core version 0.1.10
Primary language mix TypeScript (frontend/extensions), Rust (backend/plugins), Swift (MLX server), Python (AutoQA)
Desktop framework Tauri 2.8.5
Frontend React 19.0.0, TanStack Router, Vite, Tailwind CSS 4, Zustand
Local engine llama.cpp (GGUF) + MLX (Apple Silicon)
Default local API http://127.0.0.1:1337/v1
CLI server http://localhost:6767/v1
License Apache-2.0 (root LICENSE/README); component crates MIT; @janhq/core AGPL-3.0 — see §15
Platforms Windows, macOS (universal), Linux (deb/AppImage/Flatpak); Android + iOS (Tauri mobile)
Rust toolchain 1.77.2 (MSRV)
Node / Yarn Node ≥ 20.0.0, Yarn 4.5.3

Note

Version numbers diverge by component because Jan is a monorepo of independently versioned packages. The user-facing "app version" tracks the src-tauri/Cargo.toml package (Jan 0.8.2). The CLI was introduced in 0.7.8, router mode in 0.8.0, and the in-app OpenClaw agent was removed in 0.7.9.


2. System Architecture

High-Level Diagram

┌─────────────────────────────────────────────────────────────────────┐
│  Jan Desktop (Tauri 2 application)                                    │
│                                                                       │
│  ┌───────────────────────────────┐    Tauri IPC    ┌───────────────┐ │
│  │  Frontend  (web-app/, React)  │ <────invoke────> │ Rust backend  │ │
│  │  - TanStack Router routes     │                  │ (src-tauri/)  │ │
│  │  - Zustand stores             │                  │  core/{app,   │ │
│  │  - services/ (typed IPC)      │                  │  server,mcp,  │ │
│  │  - AI SDK (cloud providers)   │                  │  threads,...} │ │
│  │  - Extensions (@janhq/*)      │                  └──────┬────────┘ │
│  └───────────────────────────────┘                         │          │
│                                                  Tauri plugins (Rust): │
│                                          llamacpp │ mlx │ hardware │   │
│                                          rag │ vector-db              │
└──────────────────────────────────────────────────┼───────────────────┘
                                                    │ spawns / HTTP
                  ┌─────────────────────────────────┼──────────────────┐
                  ▼                    ▼             ▼                  ▼
        llama-server (router)   mlx-server     MCP servers       Cloud provider
        GGUF models             (Swift, macOS) (npx/python)       APIs (HTTPS)
        loads/unloads on demand                                  OpenAI/Anthropic/…

  Local OpenAI-compatible API server  ──►  127.0.0.1:1337/v1
  `jan` CLI binary (separate build)   ──►  localhost:6767/v1

Process Model

Jan is a single-instance application (tauri-plugin-single-instance); launching a second time focuses the existing window rather than starting a new process (src-tauri/src/lib.rs).

The Rust backend (app_lib::run() in src-tauri/src/lib.rs) builds the Tauri app, registers plugins, manages a shared AppState, and wires lifecycle handlers. Local inference runs in child processes:

  • llama.cpp router — a single llama-server process spawned and supervised by tauri-plugin-llamacpp. It serves all GGUF models, loading/unloading on demand.
  • mlx-server — a Swift binary (macOS only) spawned by tauri-plugin-mlx.
  • MCP servers — external npx/python/node/bun processes spawned and monitored by the MCP subsystem with exponential-backoff restart.

Graceful Shutdown

The backend installs careful lifecycle handlers in app.run(...) (src-tauri/src/lib.rs):

  • On CloseRequested/ExitRequested, if the llamacpp router is running and models are busy, Jan prevents close, emits llamacpp-busy-on-exit, and runs handle_graceful_exit() which repeatedly calls try_graceful_stop_router() until no model is busy.
  • On the final Exit event, it runs background_cleanup_mcp_servers() (10s timeout), cleanup_llama_processes(), and (macOS) cleanup_mlx_processes() synchronously so no child process is orphaned.

Note

This busy-aware shutdown is why closing Jan during active generation can pause — the app is draining in-flight model work and stopping the router cleanly before exit.

Frontend ↔ Backend Boundary

The React frontend never touches the filesystem or spawns processes directly. It calls Rust commands via Tauri's invoke('command_name', args). The frontend's web-app/src/services/ layer provides typed wrappers around these commands; web-app/src/lib/ holds the heavier logic (model factory, MCP orchestrator, custom chat transport). The complete command surface is enumerated in §8.


3. Component Inventory

Component Path Language Role
Rust backend src-tauri/src/ Rust Tauri app, IPC commands, server, MCP, threads, downloads, updater
jan-cli binary src-tauri/src/bin/jan-cli.rs + src/core/cli/ Rust Standalone CLI (built with cli feature)
Frontend web-app/src/ TS/React UI, routing, state, cloud providers
Core SDK core/src/ (@janhq/core 0.1.10) TS Extension API, type definitions, browser/event abstractions
llamacpp plugin src-tauri/plugins/tauri-plugin-llamacpp Rust Spawn/supervise llama-server router; model load
mlx plugin src-tauri/plugins/tauri-plugin-mlx Rust MLX server management (macOS)
hardware plugin src-tauri/plugins/tauri-plugin-hardware Rust GPU/CPU/RAM info, NVML + Vulkan probing
rag plugin src-tauri/plugins/tauri-plugin-rag Rust Document parsing (PDF/DOCX/XLSX/CSV/HTML)
vector-db plugin src-tauri/plugins/tauri-plugin-vector-db Rust SQLite vector store (rusqlite + sqlite-vec)
llamacpp extension extensions/llamacpp-extension (1.0.1) TS Engine logic: model load/unload, chat completion
mlx extension extensions/mlx-extension (1.0.0) TS MLX inference orchestration
assistant extension extensions/assistant-extension (1.0.2) TS Default AI assistant + tools
conversational extension extensions/conversational-extension (1.0.0) TS Thread/state persistence on the filesystem
download extension extensions/download-extension (1.0.0) TS File/model download management
rag extension extensions/rag-extension (0.1.0) TS RAG orchestration across parser/embeddings/vector DB
vector-db extension extensions/vector-db-extension (0.1.0) TS Vector store integration (sqlite-vec w/ linear fallback)
MLX server mlx-server/ (Swift) Swift Apple-Silicon inference server binary
jan-utils crate src-tauri/utils/ Rust Shared helpers: crypto, network, paths, CUDA discovery
Docs site docs/ (Next.js/Nextra) TS Public documentation at jan.ai
AutoQA autoqa/ (Python) Python End-to-end UI test harness (CUA agent + ReportPortal)

4. Repository Structure

jan/
├── core/                       # @janhq/core — extension SDK + type defs
│   └── src/
│       ├── browser/            # extension base classes, engines, models
│       └── types/              # api, assistant, config, engine, inference,
│                               #   mcp, message, model, setting, thread
├── web-app/                    # React 19 frontend (@janhq/web-app)
│   └── src/
│       ├── routes/             # TanStack Router pages (settings, hub, threads)
│       ├── services/           # typed IPC wrappers (app, core, models, mcp, …)
│       ├── lib/                # model-factory, mcp-orchestrator, transport
│       ├── providers/          # React context providers
│       ├── constants/          # providers, models, routes, localStorage keys
│       ├── stores/             # Zustand stores
│       └── locales/            # i18n (18 languages)
├── extensions/                 # @janhq/* engine + feature extensions
│   ├── llamacpp-extension/
│   ├── mlx-extension/
│   ├── assistant-extension/
│   ├── conversational-extension/
│   ├── download-extension/
│   ├── rag-extension/
│   └── vector-db-extension/
├── src-tauri/                  # Rust backend (Tauri app + CLI)
│   ├── src/
│   │   ├── lib.rs              # app_lib::run() — Tauri builder, invoke_handler!
│   │   ├── main.rs             # binary entry; fix_path_env; deps-analyzer hook
│   │   ├── bin/jan-cli.rs      # CLI entry (feature = "cli")
│   │   └── core/               # app, server, mcp, threads, downloads,
│   │                           #   extensions, filesystem, system, updater, cli
│   ├── plugins/                # tauri-plugin-{llamacpp,mlx,hardware,rag,vector-db}
│   ├── utils/                  # jan-utils crate (crypto, network, path, system)
│   ├── capabilities/           # Tauri capability/ACL definitions
│   └── gen/android/            # Android (Tauri mobile) project
├── mlx-server/                 # Swift MLX inference server
├── docs/                       # Next.js/Nextra documentation site
├── autoqa/                     # Python E2E UI test harness
└── flatpak/                    # Flatpak packaging

Triage Notes (for source navigators)

  • The web-app/src/lib/ directory (~216 KB across 27 files) holds the heaviest frontend logic: model-factory.ts, custom-chat-transport.ts, remoteModelCatalog.ts, ai-model.ts, and the mcp-orchestrator/.
  • web-app/src/routes/settings/ (~154 KB) contains the settings UI, including the local-api-server page.
  • The authoritative backend command list is the tauri::generate_handler! macro in src-tauri/src/lib.rs — not any generated index. Tauri commands are neither CLI nor HTTP routes, so static analysis under-reports them.
  • The user-facing docs live as MDX under docs/src/pages/docs/desktop/ and are the canonical source for user workflows.

5. System Requirements

Runtime (End User)

Platform Minimum GPU acceleration
macOS 13.6+ Metal (automatic on Apple Silicon)
Windows 10+ CUDA (NVIDIA), Vulkan (AMD/Intel Arc)
Linux Most modern distros CUDA, Vulkan

Memory guidance: ~8 GB RAM for 3B models, 16 GB for 7B, 32 GB for 13B. A GPU backend is only preferred when a GPU reports ≥ 6 GB VRAM; below that Jan runs on CPU. llama.cpp builds require AVX2 (use a noavx build on older CPUs).

GPU prerequisites (NVIDIA): driver 470.63.01+, CUDA Toolkit 11.7+. On Linux additionally gcc-11/g++-11/cpp-11 and LD_LIBRARY_PATH configured.

Build (Developer)

Tool Version
Node.js ≥ 20.0.0
Yarn ≥ 4.5.3 (packageManager: yarn@4.5.3)
Make ≥ 3.81
Rust 1.77.2+ (Tauri)
Metal Toolchain macOS Apple Silicon only: xcodebuild -downloadComponent MetalToolchain

6. Build & Packaging

Quick Build

git clone https://github.com/janhq/jan
cd jan
make dev          # installs deps, builds core, launches the app

Make targets: make dev (full setup + launch), make build (production), make test (tests + lint), make clean (reset).

Yarn Workspaces

The root package.json (jan-app, private) defines workspaces core and web-app, with extensions/ and src-tauri/plugins/ as nested workspace sets. Package manager is Yarn 4.5.3 with hoistingLimits: workspaces.

Key scripts (package.json):

Script Purpose
dev / dev:tauri Build icon, copy assets, tauri dev (with IS_CLEAN=true)
build build:web then build:tauri
build:tauri:win32 download:bin → build:cli → tauri build
build:tauri:linux download:bin → build:cli → tauri build (no-strip) → build AppImage
build:tauri:darwin download:bin → build:mlx-server → build:cli → tauri build --target universal-apple-darwin
build:extensions Build core, then each extension, packing .tgz into pre-install/
build:cli make build-cli (the jan-cli Rust binary)
build:mlx-server make build-mlx-server (Swift)
ios / android tauri {ios,android} dev

Note

Extensions are built into .tgz tarballs placed in pre-install/, then copied into src-tauri/resources/pre-install/ and installed into the data folder at first run (see §10). The frontend depends on @janhq/core as workspace:*, while extensions depend on a packed ../../core/package.tgz.

Cargo Build Targets & Features

src-tauri/Cargo.toml defines two binaries:

  • Jan (default) — the desktop application (crate-type = ["staticlib", "cdylib", "rlib"], lib name app_lib).
  • jan-cli (src/bin/jan-cli.rs) — requires --features cli.

Cargo features:

Feature Effect
default desktop + Tauri features (wry, tray-icon, macos-private-api, custom-protocol, …)
desktop enables deep-link + hardware
hardware tauri-plugin-hardware (GPU/CPU info)
deep-link tauri-plugin-deep-link (custom URL scheme)
mobile mobile build, adds sqlx (SQLite on Android/iOS)
cli builds jan-cli with env_logger, dialoguer, indicatif, console

Important

The cli feature and the default desktop feature are mutually exclusive at the source level: large parts of lib.rs are gated #[cfg(not(feature = "cli"))]. The CLI is a thin adapter (src/core/cli/mod.rs) that calls core logic without an AppHandle, so it can run headless.

Release Profile

The release profile is tuned for minimal binary size: opt-level = "z", lto = "fat", strip = "symbols", codegen-units = 1, panic = "unwind" (allows catching library panics), overflow-checks = false.

Platform-Gated Dependencies

  • MLX (tauri-plugin-mlx) is a macOS-only dependency (cfg(target_os = "macos")), not a Cargo feature, since it's Apple-Silicon-only.
  • Desktop-only: tauri-plugin-updater, tauri-plugin-single-instance, once_cell.
  • Linux: zbus, gtk.
  • Windows: windows-sys (FileSystem).
  • HTTP TLS: tauri-plugin-http is configured with native-tls (OS trust store) instead of the bundled rustls roots, plus http2, cookies, unsafe-headers, and macos-system-configuration (system proxy).
  • MCP transport: the rmcp crate (0.8.5) provides MCP client transports (SSE, streamable-HTTP, child-process).

7. Configuration Reference

Jan's configuration surface spans (a) app settings stored in the data folder, (b) per-model inference settings in the router preset, (c) environment variables, and (d) Tauri capabilities/ACLs. The AST config-key scrape only catches env::var/process.env reads, so this section is assembled from the actual config-bearing source files.

App Settings & Data Folder

The active data folder is resolved by get_jan_data_folder_path() (src-tauri/src/core/app/commands.rs) and can be relocated via the change_app_data_folder command. Per-platform defaults:

Platform Data folder Per-user config
Windows %APPDATA%\Jan\data %APPDATA%\Jan\settings.json
macOS ~/Library/Application Support/Jan/data (app support dir)
Linux ~/.local/share/Jan/data (XDG_DATA_HOME) ~/.config/Jan

App-level state is persisted to store.json in the data folder via tauri-plugin-store (it tracks the installed version for migration). On Windows the data-folder location is editable directly in settings.json (data_folder field, double-backslash paths).

Environment Variables

Read by the Rust backend / utils (src-tauri/utils/src/, src/core/):

Variable Read in Purpose
HF_TOKEN CLI HuggingFace download Bearer token for gated HF repos
ENABLE_SYSTEM_TRAY_ICON lib.rs (option_env!) Build-time toggle for the tray icon
IS_CLEAN core/setup.rs Dev clean-launch flag
LLAMA_ARG_* llamacpp router Forwarded to llama-server at startup (e.g. LLAMA_ARG_HOST)
CUDA_PATH, CUDA_HOME, CUDA_PATH_V* utils/system.rs CUDA toolkit discovery (Win/Linux)
ProgramFiles utils/system.rs Windows CUDA toolkit base path
LD_LIBRARY_PATH, DYLD_LIBRARY_PATH, PATH utils/system.rs Library path injection for inference child processes
TEMP utils/path.rs Temp dir resolution
HOME, USERPROFILE, APP_NAME, CI app/commands.rs Path/app-name resolution
XDG_CURRENT_DESKTOP core/setup.rs Linux desktop detection

Frontend/build-time (vite.config.ts): TAURI_DEV_HOST, IS_TAURI, IS_DEV, TAURI_ENV_PLATFORM.

Note

There is no large runtime env-var config surface like a server app. Jan is a client app: configuration is overwhelmingly the in-app settings (persisted to JSON) and the per-model router preset, not environment variables. The env vars above are mostly OS-integration and inference-process plumbing.

llama.cpp Router Preset (router.preset.ini)

Per-model inference settings live in <data-folder>/llamacpp/router.preset.ini, generated by the desktop UI. The router applies engine-wide settings as [*] defaults, with per-model [<model>] entries overriding them. Since v0.8.0 the router is launched as:

llama-server --models-preset <router.preset.ini> --models-max <N> --no-webui

and loads/unloads models via POST /models/load and POST /models/unload.

Engine-wide settings (apply to every loaded model):

Setting Flag Default Notes
Continuous Batching — Disabled Process multiple requests at once
Parallel Sequences — 1 1 enables -kvu single-request optimization
Threads --threads -1 (all cores) Generation threads
Threads (Batch) — -1 (= Threads) Prompt/batch threads
uBatch Size -ub 512 Physical max batch size
Devices for Offload --device empty (auto) e.g. CUDA0,CUDA1
GPU Split Mode — Layer Multi-GPU distribution
Main GPU Index — 0 Primary GPU
Flash Attention — Auto Force on/off only on kernel issues
Disable mmap --no-mmap Disabled Enable on crashes/pageouts
MLock --mlock Disabled Pin model in RAM
Context Shift — Disabled Trim prompt start when context fills
KV Cache K/V Type --cache-type-{k,v} f16 q8_0/q4_0 to save VRAM
KV Cache Defrag Threshold — 0.1 < 0 disables (deprecated upstream)
Max Concurrent Models --models-max — 1 = strict single-model; 0 = unlimited
Max Tokens to Predict -n -1 (infinite) Cap generation
Prompt Cache RAM --cache-ram -1 (unlimited) 0 disables
Cache Reuse --cache-reuse 0 (disabled) Min matching-prefix chunk to reuse
Full SWA Cache --swa-full Disabled Gemma/Mistral SWA full-context
Keep First N Tokens --keep 0 Tokens kept on context shift (-1 = all)
RoPE Scaling Method/Factor/Freq RoPE flags None/1/0 Extended-context tuning
Mirostat Mode/LR/Entropy mirostat flags Off/0.1/5 Alternative sampling
Grammar File / JSON Schema File --grammar/schema empty Constrained output

Per-model settings (override engine defaults): Context Size (ctx_size), GPU Layers (n_gpu_layers), Batch Size (batch_size), Disable KV Offload (no_kv_offload), Override Tensor Buffer Type, Chat Template (custom Jinja), and sampling parameters.

Warning

The default KV cache type was temporarily q8_0 in a prior release and reverted to f16. This is not auto-migrated — if models fail to load after updating to 0.8.0, manually set KV Cache K/V Type back to f16.

Assistant Configuration (assistants/jan/assistant.json)

Field Type Default Description
id string jan Assistant identifier
name string Jan Display name
model string * Allowed models (* = all)
instructions string — Default thread system prompt
tools array retrieval Available tools (currently retrieval only)
tools[].settings.top_k number 2 Max retrieval results
tools[].settings.chunk_size number 1024 Retrieval chunk size
tools[].settings.chunk_overlap number 64 Chunk overlap
file_ids array — OpenAI-compatibility field

Cloud Provider Defaults

The built-in provider catalog (web-app/src/constants/providers.ts) ships default base URLs and auth settings for: openai, azure, anthropic, openrouter, mistral, groq, xai, gemini, minimax, huggingface, nvidia — plus generic openai-compatible. Model capability tables (web-app/src/constants/models.ts) declare per-provider supportsImages/supportsToolCalls/supportsStreaming/supportsJSON. Anthropic ships custom headers (anthropic-version: 2023-06-01, anthropic-dangerous-direct-browser-access: true).

Tauri Capabilities (ACLs)

src-tauri/capabilities/ defines the permission set granted to the WebView and which plugin commands are reachable. Each custom plugin ships an autogenerated permission set under plugins/<plugin>/permissions/autogenerated/commands/ (one capability per command). This is the trust boundary between the frontend and the Rust backend.


8. The Tauri Command (IPC) Surface

The complete backend API is the tauri::generate_handler! block in src-tauri/src/lib.rs (macro invoke_commands_with_extras!). Commands are dispatched from the frontend via invoke('<name>', args) and grouped by module. This is the authoritative, version-stamped list.

Filesystem (core::filesystem::commands)

join_path, mkdir, exists_sync, readdir_sync, read_file_sync, rm, mv, file_stat, write_file_sync, write_yaml, read_yaml, decompress, open_dialog, save_dialog

Note

The filesystem commands are annotated "Deprecate soon" in source — newer extensions are expected to move off the raw FS bridge.

App Configuration (core::app::commands)

get_app_configurations, get_user_home_path, update_app_configuration, get_jan_data_folder_path, get_configuration_file_path, default_data_folder_path, change_app_data_folder, app_token

Extensions (core::extensions::commands)

get_jan_extensions_path, install_extensions, get_active_extensions

System (core::system::commands)

relaunch, open_app_directory, open_file_explorer, factory_reset, read_logs, is_library_available, launch_claude_code_with_config, check_jan_cli_installed, install_jan_cli, uninstall_jan_cli, clear_claude_code_env

Server (core::server::commands)

start_server, stop_server, get_server_status

Remote Providers (core::server::remote_provider_commands)

register_provider_config, unregister_provider_config, get_provider_config, list_provider_configs (+ abort_remote_stream on mobile)

MCP (core::mcp::commands)

get_tools, get_tools_for_servers, get_server_summaries, call_tool, cancel_tool_call, restart_mcp_servers, get_connected_servers, save_mcp_configs, get_mcp_configs, activate_mcp_server, deactivate_mcp_server, check_jan_browser_extension_connected

Threads & Messages (core::threads::commands)

list_threads, create_thread, modify_thread, delete_thread, list_messages, create_message, modify_message, delete_message, get_thread_assistant, create_thread_assistant, modify_thread_assistant

Downloads (core::downloads::commands)

download_files, cancel_download_task, pause_download_task

Lifecycle, Theme, Updater

confirm_exit; core::setup::get_system_theme, set_gtk_prefer_dark; (desktop) core::updater::commands::check_for_app_updates, is_update_available

Important

The download_files / cancel_download_task / pause_download_task commands back the model-download UI. install_extensions runs the extension migration on startup. factory_reset is the destructive reset; change_app_data_folder relocates the data folder. The launch_claude_code_with_config / clear_claude_code_env commands implement the Claude Code integration; install_jan_cli / uninstall_jan_cli manage the CLI binary.


9. Inference Engines & Plugins

Jan's inference is split between Rust plugins (process supervision, OS integration) and TypeScript extensions (engine logic, OpenAI-shaped chat completion). The two communicate via the guest-JS API each plugin exposes.

llama.cpp (tauri-plugin-llamacpp + @janhq/llamacpp-extension)

The Rust plugin (0.8.2) manages the llama-server router process. Notable internals:

  • LlamacppState holds the router PID (router_pid) and process registry. try_graceful_stop_router(), cleanup_llama_processes(), and run_deps_analyzer_if_requested() are exported for the host.
  • Out-of-process dependency analyzer: main.rs calls tauri_plugin_llamacpp::deps_analyzer::run_deps_analyzer_if_requested() before app_lib::run(). The binary re-invokes itself as an lddtree helper to analyze a backend's shared-library dependencies (CUDA/Vulkan/cuDNN) out-of-process — lddtree is excluded on macOS because its Mach-O handling has crashed in the field.
  • Binary discovery (src/core/cli/mod.rs::discover_llamacpp_binary) walks <data>/llamacpp/backends/<version>/<backend>/, checking build/bin/llama-server[.exe] then <backend>/llama-server[.exe], preferring the latest version.
  • HuggingFace download (fetch_hf_gguf_files, download_hf_model) hits https://huggingface.co/api/models/{repo}?blobs=true&files_metadata=true, filters .gguf siblings, sorts by size (smallest quant first), streams the file with SHA-256 from the LFS pointer, and writes a model.yml. HF_TOKEN is sent as a bearer token; 401/403 surface a "gated repo — set HF_TOKEN" hint.
  • Crypto: tauri-plugin-llamacpp uses hmac/sha2/base64 for API-key generation and byteorder for GGUF parsing (src/gguf/).

The TS extension (extensions/llamacpp-extension, 1.0.1) implements model load/unload and streaming chat completion. It depends on @janhq/tauri-plugin-llamacpp-api, @janhq/tauri-plugin-hardware-api, @tauri-apps/plugin-http, and fetch-retry. Error surface includes backend archive validation, model-exists checks, "Model already loaded," SSE chunk parsing ("Malformed chunk"), and model.yml not found.

MLX (tauri-plugin-mlx + @janhq/mlx-extension, macOS only)

The Rust plugin (0.8.2) supervises the Swift mlx-server binary. MlxState/SessionInfo/MlxConfig and load_mlx_model_impl/cleanup_mlx_processes are exported. Binary discovery (discover_mlx_binary, macOS) checks /Applications/Jan.app/Contents/Resources/{resources/,}bin/mlx-server, then next to the running executable. The TS extension orchestrates port allocation, model load, and chat completion, with crash detection ("MLX model has crashed! Please reload!"). The Swift server lives in mlx-server/Sources/MLXServer/.

Hardware (tauri-plugin-hardware)

GPU/CPU/RAM enumeration using sysinfo, plus desktop GPU probing: nvml-wrapper (NVIDIA), vulkano/ash (Vulkan), and libloading on Windows. Used by the Hub fit-pill calculation, GPU acceleration selection (highest-VRAM GPU), and the System Monitor. CUDA path discovery lives in jan-utils (system.rs): it scans CUDA_PATH/CUDA_HOME/CUDA_PATH_V*, the NVIDIA toolkit base, and common Linux paths (including Flatpak GL extension paths), then injects LD_LIBRARY_PATH/PATH/DYLD_LIBRARY_PATH per-platform into the inference child process.

jan-utils Crate

Shared Rust helpers re-exported flat (src-tauri/utils/src/lib.rs):

Module Highlights
crypto generate_app_token() (32-char alphanumeric), generate_api_key() (HMAC-SHA256), compute_file_sha256_with_cancellation() (64 KB chunks, cancellable)
network ProxyConfig, proxy validation (http/https/socks4/socks5), is_port_available, generate_random_port (3000–4000), find_process_using_port (lsof/netstat per OS), is_orphaned_mcp_process
http is_valid_host (trusted-hosts allowlist incl. localhost/127.0.0.1/0.0.0.0/host.docker.internal), CORS header detection, origin host extraction
path normalize_path (Windows verbatim-prefix handling), get_short_path (Win 8.3)
system CUDA discovery, setup_library_path, can_override_npx/can_override_uvx (AVX2 check for bun), Windows CREATE_NO_WINDOW flags, is_flatpak
math MCP exponential-backoff delay (base 1s, cap 30s, ×2, ±25% jitter)

10. The Extension System

Jan's features are delivered as extensions built on @janhq/core (the extension SDK). Extensions are TypeScript packages that the app installs into <data>/extensions/@janhq/ at first run.

Install / Migration Flow

On startup (lib.rs::setup), setup::install_extensions(app_handle, version_changed) runs. It compares the stored version (in store.json) against the current app version; when they differ it (re)installs the bundled extension tarballs from src-tauri/resources/pre-install/ (packed during build:extensions). MCP server configs are migrated via setup::migrate_mcp_servers.

Bundled Extensions

Extension Product name License Role
llamacpp-extension llama.cpp Inference Engine AGPL-3.0 GGUF inference
mlx-extension MLX Inference Engine AGPL-3.0 Apple-Silicon inference
assistant-extension Jan Assistant AGPL-3.0 Default assistant + tools
conversational-extension Conversational MIT Thread/state persistence on FS
download-extension Download Manager AGPL-3.0 File/model downloads
rag-extension RAG Tools AGPL-3.0 Retrieval orchestration
vector-db-extension Vector DB AGPL-3.0 Vector store (sqlite-vec)

Each extension declares main: dist/index.js (and some a module:/node: build), depends on the packed @janhq/core tarball, and is built with rolldown (1.0.0-beta.1).

Core SDK (@janhq/core)

core/src/ provides the extension API and the canonical type system (core/src/types/: api, assistant, config, engine, inference, mcp, message, model, setting, thread). core/src/browser/ holds extension base classes and engine/model abstractions. Runtime deps are minimal: rxjs and ulidx (ULID generation). The SDK is published under AGPL-3.0 and peers on React 19.

Note

The engine extensions and the Rust plugins are two halves of one system. The plugin owns the OS process and exports a guest-JS API (@janhq/tauri-plugin-*-api, linked locally); the extension calls that API to drive inference and shapes results into OpenAI-compatible responses for the frontend.


11. The Local API Server

Implemented in src-tauri/src/core/server/ (controlled by the start_server/stop_server/get_server_status commands), Jan exposes an OpenAI-compatible HTTP API powered by the llama.cpp router.

Defaults & Configuration

Setting Default Source-of-truth
Host 127.0.0.1 Server config (UI)
Port 1337 Server config; no auto-fallback if taken
API prefix /v1 Server config
API key empty (auth off) Authorization: Bearer
Trusted hosts — jan_utils::http::is_valid_host allowlist
Request timeout — Seconds to wait for the local model
Execute tools on server Disabled Server-side MCP tool dispatch
CORS Enabled Browser cross-origin
Verbose logs Enabled Server-log view

The server is "ready" when logs show JAN API listening at http://127.0.0.1:1337. The frontend route web-app/src/routes/local-api-server/ and routes/settings/local-api-server.tsx provide the UI; server logs open in a separate window (logs-window-local-api-server).

Endpoints

OpenAI-shaped, under the configured prefix:

POST {prefix}/v1/chat/completions     # chat (streaming + non-streaming)

A bundled Swagger UI is served from src-tauri/static/swagger-ui/ and the OpenAPI spec lives at docs/public/openapi/.

Security Posture

  • Default 127.0.0.1 binding keeps the server local-only.
  • Setting host to 0.0.0.0 exposes models to the LAN — the API key should be set and Trusted Hosts restricted. is_valid_host enforces an allowlist (localhost, 127.0.0.1, 0.0.0.0, host.docker.internal, plus configured hosts; * allows all).
  • The proxy/server uses an HMAC-SHA256 API-key scheme (jan-utils::crypto::generate_api_key(model_id, api_secret)) and a per-session random app_token (generate_app_token, 32 chars).

Warning

Known issue (#8272): on some systems (Fedora Silverblue reported) the API Server proxy can hang on local requests (a reqwest deadlock) while the UI chat works. If automated clients hang against 1337 but the UI is fine, suspect this path.


12. MCP Subsystem

Jan is an MCP host. The Rust side (src-tauri/src/core/mcp/) uses the rmcp crate to connect to MCP servers over SSE, streamable-HTTP, and child-process transports. The frontend's web-app/src/lib/mcp-orchestrator/ coordinates tool discovery, approval, and dispatch.

Lifecycle

  • Servers are configured via save_mcp_configs/get_mcp_configs and activated/deactivated via activate_mcp_server/deactivate_mcp_server.
  • get_tools/get_tools_for_servers/get_server_summaries/get_connected_servers enumerate capabilities; call_tool/cancel_tool_call execute.
  • AppState tracks mcp_servers, mcp_active_servers, mcp_server_pids, mcp_monitoring_tasks, mcp_settings, mcp_shutdown_in_progress, and a mcp_reconnect_notify.
  • Crashed/disconnected servers are restarted with exponential backoff (jan_utils::math::calculate_exponential_backoff_delay: 1s base, ×2, 30s cap, ±25% jitter).
  • Orphan detection: is_orphaned_mcp_process flags stray node/npx/bun MCP processes for cleanup on exit.

Runtime Dependencies

Most MCP servers are external programs spawned via npx/uvx/node/python/bun, so Node.js and/or Python must be installed on the host. Jan can override npx with a bundled bun and uvx with uv (gated on AVX2 on Intel macOS — can_override_npx).

Security

Tool calls require per-call user approval by default (inline in the chat tool card, showing arguments) unless Allow All MCP Tool Permissions is enabled. Permissions are persisted under mcpGlobalPermissions/toolApproval/toolAvailability (localStorage keys). The threat model explicitly calls out prompt injection and compromised-tool risk.

Caution

Enabling "Execute Tools on Server" (Local API Server advanced setting) plus "Allow All MCP Tool Permissions" removes the human-in-the-loop for tool execution over the HTTP API. For multi-user or networked deployments, keep both off unless every client and tool is trusted.


13. Remote Provider Integration

Cloud providers are integrated in the frontend via the Vercel AI SDK (ai v6, with @ai-sdk/{openai,openai-compatible,anthropic,google,mistral,xai,react}). The heavy logic lives in web-app/src/lib/:

  • model-factory.ts — builds provider clients, handles API-key rotation ("API key rotation exhausted" when all keys 401/403), and session resolution. Errors like "No running session found for model" originate here.
  • remoteModelCatalog.ts — fetches a provider's model list from its /models endpoint (requires base_url; "Catalog not supported for {provider}" / "Provider must have base_url configured").
  • custom-chat-transport.ts — the streaming transport bridging AI SDK to Jan's ServiceHub.
  • ai-model.ts — provider config validation.

The Rust side persists provider configs (register_provider_config/get_provider_config/list_provider_configs) and, on mobile, can abort remote streams (abort_remote_stream). Built-in provider defaults and capability tables are in web-app/src/constants/{providers,models}.ts (§7).

Note

Anthropic is reached directly from the WebView using anthropic-dangerous-direct-browser-access: true and a pinned anthropic-version header. Provider model capabilities (tools/vision/json/streaming) are read automatically after a key is added, driving which UI affordances appear.


14. RAG & Vector Database

Jan ships a built-in retrieval pipeline so Project files and the assistant's retrieval tool can ground answers in user documents.

Document Parsing (tauri-plugin-rag)

The Rust plugin parses many formats natively:

Format Crate
PDF pdf-extract
DOCX / OOXML zip + quick-xml
XLSX / spreadsheets calamine
CSV csv
HTML html2text
Encoding detection chardetng + encoding_rs
MIME sniffing infer

Vector Store (tauri-plugin-vector-db)

A SQLite-backed vector store using rusqlite (bundled, load_extension) — it loads the sqlite-vec extension when available and falls back to linear similarity search otherwise (@janhq/vector-db-extension description). UUIDs via uuid, store path via dirs.

Orchestration (rag-extension, vector-db-extension)

@janhq/rag-extension registers RAG tools and orchestrates retrieval across the parser, embeddings, and vector DB. Embeddings are produced by the llamacpp engine (an embedding model). Chunking defaults come from the assistant config (chunk_size 1024, chunk_overlap 64, top_k 2). Error surface includes "Vector DB extension not available," "Embedding dimension not available," and "llamacpp extension not available" (the embedding backend must be present).

Note

Embedding models can be loaded from the desktop app; the CLI's --embedding flag is not yet supported with llamacpp. RAG requires both the vector-db extension and an embedding-capable llamacpp model to be available.


15. Security Analysis

Trust Boundaries

  1. Frontend ↔ Rust backend — the WebView can only call commands listed in generate_handler!, gated by Tauri capabilities/ACLs (src-tauri/capabilities/, per-plugin autogenerated permissions). The raw filesystem bridge (core::filesystem::commands) is broad and marked for deprecation — a notable surface.
  2. Local API server ↔ network — default 127.0.0.1 only; LAN exposure requires explicit 0.0.0.0 + (ideally) API key + trusted-hosts allowlist (is_valid_host).
  3. Model ↔ MCP tools — per-call approval by default; "Allow All" removes the human-in-the-loop. Prompt injection is the primary risk.
  4. App ↔ cloud providers — API keys are stored in app settings and sent to provider endpoints; files/prompts leave the device for cloud models.

Secrets & Crypto

  • App token: 32-char alphanumeric per-session token (generate_app_token), held in AppState.app_token, used to authenticate frontend↔backend where required.
  • API keys: HMAC-SHA256 derivation (generate_api_key(model_id, api_secret)).
  • File integrity: streaming SHA-256 with cancellation for downloaded models; the GGUF model_sha256 is recorded in model.yml.
  • TLS: tauri-plugin-http uses native-tls (OS trust store) on desktop, rustls on mobile. HTTPS proxy support includes an Ignore SSL Certificates option (accepts self-signed certs) — a deliberate weakening for corporate proxies; document as risk.
  • Provider API keys render as password fields with unobscure/copy actions in the UI.

Network Posture

  • Outbound: HuggingFace (model metadata + downloads), cloud provider APIs, MCP servers, the updater endpoint, optional analytics (PostHog). Model downloads can route through the configured HTTPS proxy.
  • Inbound: only the optional local API server (1337) and the optional CLI server (6767), both loopback by default.
  • Proxy validation supports http/https/socks4/socks5 and a no_proxy allowlist with wildcard matching.

Update Channel

Desktop builds use tauri-plugin-updater (signed updates via Tauri's update mechanism, ed25519-dalek for signature verification is present among deps). Update reliability issues have been reported on Ubuntu (#7429).

Licensing (Compliance)

Component License
Project (root LICENSE, README) Apache-2.0 (Menlo Research; attribution requested)
Jan app crate + plugins (Cargo.toml) MIT (per-crate license field)
@janhq/core and most extensions AGPL-3.0
conversational-extension MIT

Important

The license picture is mixed. The repository's headline license is Apache-2.0 (root LICENSE and README), but the @janhq/core SDK and most TypeScript extensions are AGPL-3.0, while the Rust crates carry MIT in their Cargo.toml. Organizations redistributing or building on Jan's extension SDK must account for AGPL-3.0 obligations. Verify the license of each component you depend on.

Hardening Checklist

  • [ ] Keep the Local API Server bound to 127.0.0.1 unless LAN access is required.
  • [ ] If binding to 0.0.0.0, set a strong API key and restrict Trusted Hosts.
  • [ ] Leave Allow All MCP Tool Permissions off; approve tool calls individually.
  • [ ] Leave Execute Tools on Server off for networked deployments.
  • [ ] Only enable Ignore SSL Certificates on trusted networks.
  • [ ] Vet MCP servers before adding them; they run as arbitrary local processes.
  • [ ] Use local models for sensitive data; understand that cloud models transmit prompts/files.
  • [ ] Keep the app updated for upstream llama.cpp security/stability fixes.

16. Operations

Logs

All logs go to <data>/logs/app.log via tauri-plugin-log (configured in lib.rs at LevelFilter::Debug, with Stdout + Webview + Folder targets, file name app). Tags: [APP], [SERVER], [SPECS], and [llamacpp-router] / [llamacpp-router stdout] for engine output. The read_logs command exposes logs to the UI; logs are retained 24h when submitted to support.

# Tail logs
tail -n 100 ~/Library/Application\ Support/Jan/data/logs/app.log    # macOS

Port Management

Service Default Notes
Local API Server 1337 No auto-fallback; loopback by default
CLI server 6767 jan serve/launch
Docs dev server 3001 Development only

Find/free a stuck port:

netstat -an | grep 1337            # macOS
netstat -ano | find "1337"         # Windows
netstat -anpe | grep "1337"        # Linux

The backend itself uses find_process_using_port (lsof/netstat) and generate_random_port (3000–4000) for internal model-server port allocation.

Factory Reset & Data Relocation

  • Factory reset: factory_reset command (UI: Settings → General). Deletes all models, chats, settings. Requires typing RESET.
  • Relocate data: change_app_data_folder duplicates data to a new empty directory and restarts. On Windows, settings.json data_folder can be edited manually if the UI fails.

Updates

Desktop auto-update via tauri-plugin-updater (check_for_app_updates, is_update_available). The llama.cpp engine updates separately (backend downloads under <data>/llamacpp/backends/), with optional Auto Update Engine.

Recovery Runbook

Symptom Action
Won't start Delete/rename store.json; check single-instance; check port 1337
Model won't load Check [llamacpp-router] logs; reduce context/GPU layers; update backend; re-download GGUF
GPU unused Verify driver/CUDA; Settings → Hardware → GPU Acceleration; restart to re-probe (esp. after Linux sleep)
API server hangs (clients) Suspect the reqwest deadlock (#8272); restart server
Corrupt build Uninstall + delete data folder + reinstall latest
MCP server won't connect Ensure Node/Python installed; verify command/args; restart Jan

Telemetry

Optional product analytics via PostHog (posthog-js), gated by productAnalytic/productAnalyticPrompt (localStorage) and Settings → Privacy. The docs site has separate GTM/PostHog env vars (GTM_ID, POSTHOG_KEY, POSTHOG_HOST).


17. Data Model & Storage

On-Disk Layout

See the User Guide for the full tree. Key entities:

  • Threads — <data>/threads/<thread_id>/ with messages.jsonl (one message per line) and thread.json (metadata: assistant clone, model settings, title, timestamps). Message IDs are ULIDs.
  • Models — <data>/{llamacpp,mlx}/models/<id>/model.yml (+ the GGUF file for llamacpp). ModelYml fields: model_path (absolute or relative-to-data-folder), name, size_bytes, embedding, mmproj_path, capabilities[], model_sha256.
  • Assistants — <data>/assistants/<id>/assistant.json.
  • Extensions — <data>/extensions/extensions.json + @janhq/<ext>/.
  • Backends — <data>/llamacpp/backends/<version>/<backend>/ (downloaded llama.cpp builds).
  • Router preset — <data>/llamacpp/router.preset.ini.
  • App store — store.json (version + migration state).

Mobile Storage

On Android/iOS (mobile feature), threads use SQLite via sqlx (crate::core::threads::db::init_database) instead of the JSONL files. Desktop uses the filesystem.

Model Resolution

resolve_model_engine(model_id) probes llamacpp then mlx for a matching model.yml. resolve_model_by_id resolves the absolute model + mmproj paths (absolute paths used verbatim; relative paths joined to the data folder). list_models(engine) recursively scans <data>/<engine>/models/ for model.yml files.


18. Code Quality & Conventions

Languages & Tooling

  • Frontend: TypeScript + React 19, Vite 6, Tailwind 4, ESLint 9 + typescript-eslint, Vitest (+ Testing Library), TanStack Router with the router plugin, Zustand for state, i18next for 18 locales.
  • Backend: Rust 2021 edition, MSRV 1.77.2, thiserror for errors, tokio (full) for async, serde/serde_json/serde_yaml for serialization, clap (derive) for the CLI.
  • Build: rolldown for extensions/core, Make for CLI + MLX server, run-script-os for per-platform scripts.
  • Testing: Vitest projects (@janhq/core, @janhq/web-app, @janhq/llamacpp-extension) with --max-old-space-size=4096. Rust unit tests are colocated (#[cfg(test)]), with strong coverage in jan-utils (crypto, network, path, string, math). The Python AutoQA harness (autoqa/) drives full UI E2E tests via a CUA computer-use agent with ReportPortal reporting.

Patterns

  • Plugin + extension split — each engine is a Rust process supervisor plus a TS engine module communicating over a generated guest-JS API.
  • Command registry — backend API is a single generate_handler! block; frontend services/ mirror it as typed wrappers.
  • Capability-gated IPC — per-command Tauri permissions enforce least privilege.
  • Graceful, busy-aware shutdown — lifecycle handlers drain inference before exit.
  • Cancellable long operations — downloads and hashing accept CancellationTokens.
  • Exponential backoff with jitter — MCP reconnection.
  • Out-of-process risky work — the lddtree dependency analyzer runs as a self-reinvoked helper to isolate crashes.

Observations

  • The core::filesystem::commands set is marked "Deprecate soon" — a broad FS bridge is a maintenance and security smell the team intends to retire.
  • web-app/src/lib/ concentrates a lot of logic in a few large files (model-factory.ts, custom-chat-transport.ts) — candidates for decomposition.
  • Version skew across the monorepo (app 0.8.2, web-app 0.7.6, core 0.1.10) is intentional but can confuse "what version am I running" questions.

19. Potential Improvements

Derived from the source, error surfaces, and community issues:

  • API server reqwest deadlock (#8272) — proxy hangs on local requests on some Linux setups while UI works; investigate the blocking reqwest path in core::server::proxy.
  • SSE parser robustness (#8280) — the SSE parser fails on custom event types from OpenAI-compatible servers; the "Malformed chunk" path in llamacpp-extension and the remote transport should tolerate non-standard events.
  • Interrupted-download persistence (#8000) — model metadata isn't saved if a download is interrupted, forcing full re-downloads; persist partial state and support resume.
  • Multimodal/projector mismatches (#8273, #8278) — "unknown projector type" failures (gemma) should surface a clearer, actionable error.
  • Retire the filesystem IPC bridge — replace core::filesystem::commands (flagged for deprecation) with narrower, capability-scoped operations to shrink the frontend's privilege.
  • Attachment size-limit enforcement (#8247) — configured limits not consistently applied.
  • CLI router-mode flag clarity — --ctx-size/--n-gpu-layers/--threads/--fit are accepted but ignored in router mode; either honor them or warn at parse time.
  • Decompose large frontend modules — split model-factory.ts and custom-chat-transport.ts along provider/transport seams for testability.
  • Update reliability on Linux (#7429) — investigate updater hangs on Ubuntu.

20. Quick Reference

Versions

Component Version
App (src-tauri/Cargo.toml) 0.8.2
web-app 0.7.6
@janhq/core 0.1.10
llamacpp-extension 1.0.1
Tauri 2.8.5
React 19.0.0

Key Files

File Purpose
src-tauri/src/lib.rs Tauri builder, generate_handler! (command registry), lifecycle
src-tauri/src/main.rs Binary entry; fix_path_env; deps-analyzer hook
src-tauri/src/core/cli/mod.rs CLI adapter (headless core logic, model discovery, HF download)
src-tauri/Cargo.toml Crate config, features, binaries, deps
web-app/src/lib/model-factory.ts Cloud provider clients + key rotation
web-app/src/constants/providers.ts Built-in provider catalog
web-app/src/constants/routes.ts Frontend route map
<data>/llamacpp/router.preset.ini Per-model inference settings

Default Ports

Service Port
Local API Server 1337
CLI server 6767
Docs dev 3001

Data Folders

OS Path
Windows %APPDATA%\Jan\data
macOS ~/Library/Application Support/Jan/data
Linux ~/.local/share/Jan/data

Build Commands

make dev      # full dev setup + launch
make build    # production build
make test     # tests + lint
yarn build:tauri:darwin   # universal macOS build (incl. mlx-server + cli)
yarn build:cli            # build the jan-cli binary