Architecture Documentation
Architecture Documentation
Overview
LevPRO AI runtime is a production-grade local agent runtime built on llama-server. It provides a ReAct agent loop with tool calling, persistent memory, and a CLI interface while keeping all processing local.
Architecture Layers
The system follows a 4-layer architecture with clear separation of concerns:
┌─────────────────────────────────────────────────────────────┐
│ [ APP ] │
│ • AgentLoop (ReAct loop) │
│ • MemoryRetrieverProtocol → NullMemoryRetriever (default) │
│ or MemoryRetriever when [memory] + memory.enabled │
│ • SkillsRetrieverProtocol → NullSkillsRetriever (default) │
│ or SkillsRetriever when [skills] + skills.enabled │
│ • ToolExecutor (+ optional extra tools) │
├─────────────────────────────────────────────────────────────┤
│ [ CORE ] │
│ • LocalLLMClient or EnsembleRouter ([ensemble]) │
│ • ContextBuilder (prompt assembly) │
│ • TokenCounter │
│ • ProcessSupervisor(s) (llama-server watchdog) │
│ • ModelPlanner (launch parameter planning, llama-tools) │
├─────────────────────────────────────────────────────────────┤
│ [ INFRA ] │
│ • DirectoryGuard (sandbox security) │
│ • SessionStore (JSON persistence) │
│ • (optional [memory]) MemoryStore + MemoryManager (L0–L3) │
└─────────────────────────────────────────────────────────────┘
Optional pip extras (wired in app.py when installed + enabled):
[memory] [ensemble] [skills] [monitor] [autotune]
MCP tools: local-ai-mcp hard dep; servers from mcp.json only
Layer Responsibilities
[INFRA] Layer
Provides foundational services that other layers depend on.
Modules:
security/guard.py- DirectoryGuard: path sandbox for Core runtime (and optional workspace skills root)session/store.py- SessionStore: JSON-based chat history storagepackages/local-ai-memory/memory/(optional[memory]) — MemoryStore, MemoryManager (L0–L3), WorkingMemoryCompressor
Purpose:
- Enforce security boundaries
- Provide persistent storage
- Handle concurrent access safely
Dependencies:
- Python standard library (pathlib, asyncio)
- No external dependencies
[CORE] Layer
Core business logic and infrastructure services.
Modules:
core/llm_client.py- LocalLLMClient: HTTP client for llama-servercore/supervisor.py- ProcessSupervisor: llama-server process watchdogcore/context_builder.py- ContextBuilder: prompt assembly with token budgetingcore/token_counter.py- TokenCounter: token counting via llama-servercore/logging_config.py- logging configuration and event tagging
Planner (separate package llama-tools):
llama_tools/planner/- ModelPlanner: launch parameter planning from GGUF metadata
Purpose:
- Manage llama-server lifecycle and health
- Compute optimal launch parameters for models
- Build prompts within token budgets
- Communicate with llama-server API
Dependencies:
- httpx (async HTTP client)
- psutil (system probing)
- gguf (GGUF metadata parsing)
- cpuinfo (CPU feature detection)
[APP] Layer
Application-specific logic for the agent loop and tool execution.
Modules:
agents/loop.py- AgentLoop: ReAct agent loop orchestrationcore/protocols.py-MemoryRetrieverProtocol/NullMemoryRetriever(default); realMemoryRetrieverfrom[memory]core/protocols.py-SkillsRetrieverProtocol/NullSkillsRetriever(default); realSkillsRetrieverfrom[skills]agents/tool_parser.py- ToolParser: strict JSON tool call parsingtools/executor.py- ToolExecutor: tool validation and executiontools/meta_tools.py/ session tools — native recovery tools (report_inability,load_message, …)- MCP capability tools via
local-ai-mcp(mcp_*from mcp.json)
Purpose:
- Execute the agent ReAct loop
- Retrieve relevant memory/context
- Parse and execute tool calls
- Build message context for LLM
Dependencies:
- All CORE layer modules
- DirectoryGuard for Core runtime path I/O (not MCP tools)
- JSON Schema validation (jsonschema)
Cross-Cutting Concerns
Security
- DirectoryGuard sandboxes Core-owned paths under
runtime_data_dir(sessions, memory, archives) and, when set, the optionalworkspaceroot used for skills storage - MCP filesystem/shell tools are not constrained by DirectoryGuard — hosts configure MCP server roots and
requires_approval - Do not treat optional
workspaceas a general project file sandbox for the agent
Logging
- Structured logging with event tags
- Two loggers:
app(application) andllama(llama-server) - Event tags:
LLM_REQUEST,LLM_RESPONSE,TOOL_CALL,TOOL_RESULT,TOOL_RESULT_STORED,
MEMORY_RETRIEVAL_STAGE1, MEMORY_RETRIEVAL_STAGE2, MCP_CONNECT, MCP_TOOL_CALL, SKILL_STORED, SKILL_RETRIEVAL,
SUPERVISOR_RESTART, ERROR_STACKTRACE
Concurrency
- All file I/O uses
asyncio.to_thread()for non-blocking execution - SessionStore uses per-file
asyncio.Lockfor concurrent access - ProcessSupervisor watchdog runs as asyncio task
Bootstrap and Embedding
LevPRO AI runtime separates sync construction (create_app()) from async runtime (supervisor.start() → ctx.startup() → agent work → ctx.shutdown()). All interfaces share a single AgentLoop from AppContext.
See docs/bootstrap.md for the full dependency graph, lifecycle checklist, selective component embedding, and manual wiring cookbook.
Data Flow
Agent Request Flow
1. User Input
↓
2. SessionStore.append() - Add user message to history
↓
3. memory_retriever.retrieve() — no-op when NullMemoryRetriever; indexed RAG when [memory] enabled
↓
4. Load L1 working memory (WorkingMemoryCompressor / MemoryManager)
↓
5. ContextBuilder.build() - Assemble prompt with token budgeting
↓
6. AgentLoop.run() - Main loop
├─ Build messages (system + tools + working memory + RAG + history + input)
├─ LocalLLMClient.chat() or EnsembleRouter.chat() ([ensemble]) → response
├─ ToolParser.parse_tool_call() → tool call or final answer
├─ ToolExecutor.execute() → ToolResult
│ └─ large success? → MemoryManager L3 + compact reference JSON
└─ Update history with tool observation (inline or reference)
↓
7. SessionStore.append() - Add assistant response
↓
8. WorkingMemoryCompressor.update_summary() - Compress run into L1
└─ SessionStore.compact() when session exceeds history_limit
↓
9. Return final response
Model Launch Planning Flow
1. ModelPlanner.plan()
↓
2. GgufMetadataReader.read() - Parse GGUF file
↓
3. SystemProbe.detect() - Detect hardware (RAM, CPU, GPU)
↓
4. Compute optimal parameters:
├─ ctx_size (context window that fits)
├─ gpu_layers (max layers for VRAM)
├─ n_batch / n_ubatch (batch sizes)
├─ threads (CPU allocation)
├─ flash_attn (flash attention setting)
└─ cache_type_k/v (cache dtype)
↓
5. fits_in_memory() validation
↓
6. Return PlanResult with estimates
File Organization
packages/local-ai-core/
├── core/ # llm_client, supervisor, token_counter, context_builder, mcp_json
├── agents/ # loop, tool_parser, registry
├── session/ # SessionStore, SessionRunner
├── security/guard.py # DirectoryGuard
├── tools/ # executor, meta/session tools, lazy_tool
├── interfaces/ # cli, stdio_server
├── config/loader.py
├── app.py # AppContext factory
└── main.py # local-ai entry
packages/local-ai-memory/ # [memory] — store, retriever, manager, compressor
packages/local-ai-mcp/ # hard dep — lazy stdio + HTTP MCP bridge
packages/local-ai-ensemble/ # [ensemble] — multi-model EnsembleRouter
packages/local-ai-skills/ # [skills] — skill capture + retrieval
packages/local-ai-monitor/ # [monitor] — RAM/VRAM polling + graceful degradation
packages/local-ai-autotune/ # [autotune] — launch-param calibration
packages/llama-tools/llama_tools/ # planner, doctor, plan, inspect, benchmark CLI
Key Design Decisions
1. Single AgentLoop Instance
All interfaces share a single AgentLoop instance from AppContext. This ensures:
- Consistent state management
- No duplication of agent logic
- Proper session tracking
2. Strict JSON Tool Parsing
Tool calls must be strict JSON (no regex fallback). This ensures:
- Predictable parsing behavior
- Security (no ambiguity)
- Compatibility with all models
3. 3-Stage Memory Retrieval
Memory retrieval uses three stages:
- Stage 1: BM25 or hybrid index search (no LLM, top 5)
- Stage 2: LLM selects from filtered candidates
- Stage 3: Budgeted content load
This balances speed and relevance.
4. Token Budgeting
ContextBuilder enforces a token budget to prevent:
- LLM context overflow
- Excessive memory usage
- Slow responses
5. Process Supervision
ProcessSupervisor watches llama-server with:
- 5-second health check interval
- Restart on crash or 3 consecutive failures
- Exponential backoff (1s → 30s max)
6. Path sandbox (DirectoryGuard)
Core-owned file I/O goes through DirectoryGuard, which:
- Resolves paths to absolute
- Validates paths stay under its base (
runtime_data_dir, or optionalworkspacefor skills) - Raises PermissionError for escapes
Agent capability tools (read/write project files, shell, etc.) come from host MCP servers and are outside this guard.
Performance Considerations
- Async-first design: All I/O is async, blocking operations use
asyncio.to_thread() - Token budget: Prevents unbounded context growth
- Index-based memory retrieval: Fast initial filtering
- Health checks: Non-blocking supervisor checks
- Exponential backoff: Prevents resource exhaustion on restarts
Scalability
The architecture supports scaling through:
- Multiple sessions: Each session is isolated in JSON
- Multiple users: User ID separation in storage
- Memory categories: Organized by category for retrieval
- Configurable tools: Tools are registered and enabled per agent
Security Boundaries
- File System: DirectoryGuard sandboxes Core runtime/skills paths; MCP servers own their own roots (not Core isolation)
- MCP approval:
ApprovalGatefor MCP tools withrequires_approvalinmcp.json - Network: llama-server for the agent runtime; MCP HTTP servers use host-configured URLs/headers
- Memory: User/session isolation
- Configuration: Typed config with validation
- Tool Execution: JSON Schema validation before execution
Security model
| Layer | Protects against | Does NOT protect against |
|---|---|---|
| DirectoryGuard | Accidental path escape under runtime_data_dir / optional skills workspace |
Paths used inside an MCP server |
| ApprovalGate | Unapproved MCP tools marked requires_approval |
User approving malicious MCP tools |
| Host mcp.json | Which servers/tools are exposed | Misconfigured MCP server processes |
Capability tools (filesystem, shell, etc.) are provided by host MCP servers — Core does not ship a process sandbox package.
Logging and Observability
Log Locations
logs/app.log- Application eventslogs/llama.log- llama-server stdout/stderr
Event Tags
Use core/logging_config.log_event() for structured logging:
LLM_REQUEST- Before LLM callLLM_RESPONSE- After LLM responseTOOL_CALL- Tool invocationTOOL_RESULT- Tool resultTOOL_RESULT_STORED- Large tool output persisted to L3MEMORY_RETRIEVAL_STAGE1- Index filteringMEMORY_RETRIEVAL_STAGE2- LLM selectionWORKING_MEMORY_COMPRESSED- L1 working memory updated after agent runSCRIPT_EXEC_START/SCRIPT_EXEC_DONE- Sandboxed script executionSCRIPT_EXEC_DENIED- User or policy denied script executionSUPERVISOR_RESTART- Supervisor restart eventERROR_STACKTRACE- Error details
Configuration
Configuration is loaded from YAML and typed with dataclasses:
llama:
binary_path: "..."
models:
main:
path: "..."
port: 8080
optimization_mode: balanced
os_reserve_mb: 4096
disk_reserve_mb: 4096
# workspace: "./workspace" # optional; skills storage root (not a Core FS index)
agents:
assistant:
tools: [] # mcp_* from mcp.json are auto-granted
mcp:
enabled: false
external_path: null
memory:
enabled: true
categories: ["knowledge"]
search_mode: bm25
working_memory_target_tokens: 768
working_memory_enabled: true
working_memory_compact_keep_last: 6
app:
history_limit: 12
max_steps: 20
context_token_budget: 6000
tool_result_inline_limit: 6144
max_parse_errors: 5
See config.yaml for full reference.
Testing Strategy
All modules have comprehensive tests:
- Unit tests with mocked LLM
- Security sandbox tests
- Session concurrency tests
- Tool executor validation tests
- Tool result store / L3 externalization tests
- Model planner tests
- Memory retriever tests
- App bootstrap tests
Run tests with: python -m pytest tests/ -v