Application Bootstrap and Embedding Guide

Application Bootstrap and Embedding Guide

This document explains when and in what order LevPRO AI runtime components are initialized, and how to embed the runtime into another application. For class-level API details, see docs/api/interfaces.md.

Overview: Two Phases

Phase When What happens
Construction Sync, before async work create_app() wires objects; planner/autotune; dirs created; mcp.json merged
Runtime startup Async, before agent runs ctx.start_supervisors() then ctx.startup()
Operation Async session_runner.run(...) or agent_loop.run(...)
Shutdown Async, always in finally ctx.shutdown()

Do not call agent_loop.run() until llama-server is healthy. Do not skip ctx.shutdown() — it stops the memory index watcher (when enabled), terminates llama-server process(es), closes MCP sessions, and closes the HTTP client pool.

sequenceDiagram participant Host as HostApplication participant App as create_app participant Sup as ProcessSupervisor(s) participant Stack as MemoryStack participant Runner as SessionRunner Host->>App: create_app(config) [sync] Note over App: mcp.json merge; ModelPlanner/Autotune may raise Host->>Sup: await ctx.start_supervisors() Host->>App: await ctx.startup() Note over Stack: memory index + MCP discover-all when needed Host->>Runner: await session_runner.run(...) Host->>App: await ctx.shutdown()

Chat-only vs project mode

Mode Config Tools
Default Omit workspace (or workspace: null) Native recovery tools + mcp_* from mcp.json
Project workspace: "/path/to/project" Same tools; optional root for skills storage (not a Core FS index/sandbox)

Capability tools (filesystem, shell, git, HTTP, …) always come from MCP — not from built-in Core packages. Runtime state (sessions, L0–L3, RAG, mcp.json) uses runtime_data_dir (default ~/.local-ai/).


Canonical pattern from packages/local-ai-core/interfaces/cli.py:

import asyncio

from agents.registry import get_agent_config
from app import create_app, default_agent_name
from config.loader import load_config
from core.shutdown import install_signal_handlers


async def main() -> None:
    config = load_config("config.yaml")
    ctx = create_app(config=config)
    install_signal_handlers(ctx)  # optional
    await ctx.start_supervisors()
    await ctx.startup()
    try:
        agent_name = default_agent_name(ctx.config)
        agent_config = get_agent_config(ctx.config, agent_name)
        result = await ctx.session_runner.run(
            user_id="user1",
            session_id="session_123",
            user_input="Hello",
            agent_config=agent_config,
            agent_name=agent_name,
        )
        print(result.response)
    finally:
        await ctx.shutdown()


asyncio.run(main())

Prerequisites:

  • Valid config.yaml with llama.binary_path, llama.models.main.path, and at least one agent
  • MCP servers in {runtime_data_dir}/mcp.json (or --mcp-config / LOCAL_AI_MCP_CONFIG) when the agent needs tools
  • For RAG/L0–L3: pip install -e "packages/local-ai-core[memory]" and memory.enabled: true
  • create_app() raises ModelPlanError if the model cannot fit in available RAM/VRAM

Optional extras (install + enable in config):

Extra Install Config gate
[memory] pip install -e "packages/local-ai-core[memory]" memory.enabled: true
[ensemble] pip install -e "packages/local-ai-core[ensemble]" ensemble.enabled: true
[skills] pip install -e "packages/local-ai-core[skills]" skills.enabled: true
[monitor] pip install -e "packages/local-ai-core[monitor]" monitor.enabled: true
[autotune] pip install -e "packages/local-ai-core[autotune]" llama.autotune.enabled: true
all pip install -e "packages/local-ai-core[all]" enable each section as needed

local-ai-mcp is a hard dependency of local-ai-core (not an optional extra). Enable via mcp.json / --mcp-config / LOCAL_AI_MCP_CONFIG.

Host injection of MCP: host_integrations.md.


Construction: create_app() Dependency Order

packages/local-ai-core/app.py remains the public composition root and wires dependencies synchronously in this order. Focused construction logic lives under core/bootstrap/: inference creation, model planning, resource/degradation wiring, and tool-registration validation. Runtime startup/shutdown ownership lives in core/app_context.py.

  1. load_config() (or use passed config)
  2. apply_env_overrides() (LOCAL_AI_MCP_CONFIG)
  3. setup_logging()
  4. merge_mcp_external() — load {runtime_data_dir}/mcp.json (or mcp.external_path) as sole server source
  5. DirectoryGuard(runtime_root) — mkdir sessions/; optional workspace DirectoryGuard
  6. Optional ensemble (when local-ai-ensemble installed and ensemble.enabled: true):
  • build_ensemble_stack()EnsembleRouter + one ProcessSupervisor per llama.models entry
  • llm_client becomes the router; supervisors list holds all model supervisors
  1. Single model (default): planner and/or autotuneResolvedLlamaModelConfig
  • ProcessSupervisor(binary_path, resolved model, …)base_url
  • LocalLLMClient(base_url, timeouts, model_name)
  1. TokenCounter(llm_client)ContextBuilder
  2. SessionStore(guard, max_session_size)
  3. Optional memory (when local-ai-memory installed and memory.enabled: true):
  • build_memory_stack()MemoryStack (store, retriever, manager, compressor, index_service, tools)
  • mkdir memory_data/knowledge/
  • memory_retriever = stack retriever; else NullMemoryRetriever
  1. MCP (when mcp.enabled and servers from mcp.json):
  • build_mcp_stack() → lazy mcp_* tools (stdio and/or HTTP)
  1. Optional skills (skills.enabled) → SkillsStack (retriever + capture); else NullSkillsRetriever
  2. Message archive tools when message_archive_enabled
  3. build_tool_executor(config, …, extra_tools=..., auto_enable=mcp names)
  4. PromptManager(base_dir=config directory)
  5. AgentLoop(...) with retriever protocols (mcp_* + recovery tools auto-granted)
  6. SessionRunner(agent_loop, max_concurrent_sessions)
  7. Optional monitor stack when monitor.enabled
  8. AppContext(...)

When an optional package is installed but its config flag is false, a warning is logged at startup.

Rules:

  • Do not construct ProcessSupervisor before plan/autotune resolution (single-model path).
  • Do not call memory_stack.index_service.startup() inside create_app() — use await ctx.startup() (also discovers MCP tools when allowed_tools: ["*"]).
  • Prefer create_app() over manual wiring unless injecting mocks.
  • Hosts may pass CLI overrides (--mcp-config) before create_app(config=...); env overrides are applied again inside create_app.

Runtime Lifecycle Checklist

Step Required for agent? Skip when
create_app() Yes Never for full agent
install_signal_handlers(ctx) Optional Non-interactive hosts
await ctx.start_supervisors() Yes (default) External llama-server already healthy
await ctx.startup() Yes when memory.enabled or MCP discover-all Memory disabled and no discover-all
await ctx.session_runner.run(...) Yes (preferred) Low-level tests may use agent_loop.run
await ctx.shutdown() Always in finally

start_supervisors()

Starts every supervisor in ctx.supervisors (ensemble mode) or ctx.supervisor (single-model). Use this instead of ctx.supervisor.start() directly so ensemble multi-model setups start all llama-server processes.

AppContext public surface

Field / method Purpose
config Typed configuration
supervisor Primary llama-server watchdog (first in ensemble list)
supervisors All supervisors when [ensemble] enabled; else [supervisor]
llm_client LocalLLMClient or EnsembleRouter when ensemble enabled
agent_loop ReAct orchestrator — low-level API
session_runner Concurrent sessions + memory scope — preferred embed path
runtime_guard / guard DirectoryGuard over runtime_data_dir (sessions, memory, archives)
workspace_guard Optional DirectoryGuard over workspace (skills storage root when set); else None
memory_stack MemoryStack when memory enabled; else None
mcp_stack MCP bridge when servers configured; else None
skills_stack SkillsStack when skills enabled; else None
ensemble_stack EnsembleStack when ensemble enabled; else None
monitor_stack MonitorStack when [monitor] enabled; else None
degradation GracefulDegradation when degradation enabled; else None
autotune_stack Autotune stack when [autotune] enabled; else None
background_tasks Registry for background tasks (e.g. autotune)
shutdown_event App shutdown (SIGINT); AgentLoop saves interrupted L2 checkpoint
Per-run cancel_event stdio cancel method; cancels current chat without shutting down the server
async start_supervisors() Start all llama-server processes
async startup() Memory index startup + MCP discover-all when needed
async shutdown() Stop index watcher → MCP cleanup → stop supervisors → close LLM client

What ctx.startup() does

  1. When memory_stack is wired: builds/refreshes the memory index and starts the watcher
  2. When MCP servers request discover-all (allowed_tools: ["*"] or empty): connects and registers tools

Electron / stdio embedding

local-ai serve-stdio --config config.yaml --mcp-config path/to/mcp.json

Protocol: docs/stdio_protocol.md.

For in-process Python embedding, use SessionRunner as above.


Selective Component Embedding

Goal Start from Notes
Run agent (default) create_app() Full lifecycle
Chat only, no memory index create_app() Skip ctx.startup() when memory.enabled: false and no MCP discover-all
RAG retrieval only build_memory_stack() pieces Needs running llama-server
Multi-model routing build_ensemble_stack() Requires [ensemble] + multiple llama.models
Planner preview llama-tools plan No create_app
System diagnostics llama-tools doctor No create_app
Benchmark llama-tools benchmark No create_app

External llama-server

  1. Ensure health on config.llama.models.main.port (or config.llama.main.port property)
  2. Skip await ctx.start_supervisors() if already healthy
  3. TokenCounter still requires /tokenize on the running server

Failure Modes and Pitfalls

Problem Cause Fix
ModelPlanError at startup Model cannot fit RAM/VRAM Fix model, hardware, or planner overrides
LLM timeouts on first run agent_loop.run() before start_supervisors() Call await ctx.start_supervisors() first
Stale or missing RAG Skipped ctx.startup() with memory.enabled Call await ctx.startup()
Memory features inactive Package installed but memory.enabled: false Set memory.enabled: true in config
No capability tools Empty/missing mcp.json Add servers; pass --mcp-config or set mcp.external_path
YAML mcp.servers ignored Sole source is mcp.json Put servers in the JSON file
Orphan llama-server Skipped ctx.shutdown() Always use try / finally