CLI Documentation
CLI Documentation
Overview
The CLI (Command Line Interface) provides interactive and one-shot query capabilities for LevPRO AI runtime. It supports REPL mode for conversation and one-shot mode for single queries.
Purpose
- Interactive chat - REPL mode for ongoing conversations
- One-shot queries - Single input, single response
- Session management - User and session isolation
- Agent selection - Choose which agent to use
- Configurable - Custom configuration per run
Responsibilities
- Parse arguments - Command-line options
- Create app context - Initialize app and dependencies
- Start supervisor - Launch llama-server
- Run agent - Execute agent via
SessionRunner - Manage sessions - Create and track sessions
- Handle shutdown - Clean termination
Dependencies
argparse- Argument parsingasyncio- Event loopuuid- Session ID generationapp.py- AppContext creationagents/registry.py- Agent config
Constraints
- Session isolation - Each session is separate
- User isolation - User ID separation
- Single agent - One agent per run
- Async execution - All agent calls async
- Proper shutdown - Always call ctx.shutdown()
Extension Points
Adding a New Command
- Add subparser in
packages/local-ai-core/main.py:
sub = parser.add_subparsers()
new_cmd = sub.add_parser("newcommand")
new_cmd.add_argument("--arg", default="default")
- Create
interfaces/newcommand.py:
from app import create_app
async def main_async(args):
ctx = create_app(args.config)
await ctx.supervisor.start()
try:
# Your logic
finally:
await ctx.shutdown()
- Register in
packages/local-ai-core/main.py:
elif args.mode == "newcommand":
from interfaces.newcommand import main as cmd_main
cmd_main(args)
- Add to config if needed
Module Details
interfaces/cli.py - CLI Interface
Methods:
run_repl()
async def run_repl(ctx, user_id, session_id, agent_name) -> None:
agent_config = get_agent_config(ctx.config, agent_name)
print(f"Local AI CLI — user={user_id} session={session_id} agent={agent_name}")
print("Type 'exit' or 'quit' to stop.\n")
while True:
user_input = await asyncio.to_thread(input, "You> ")
text = user_input.strip()
if not text:
continue
if text.lower() in {"exit", "quit"}:
break
result = await ctx.session_runner.run(
user_id, session_id, text, agent_config,
agent_name=agent_name,
resume=args.resume,
)
print(f"Assistant> {result.response}\n")
run_oneshot()
async def run_oneshot(ctx, user_id, session_id, agent_name, user_input) -> None:
agent_config = get_agent_config(ctx.config, agent_name)
result = await ctx.session_runner.run(
user_id, session_id, user_input, agent_config,
agent_name=agent_name,
resume=args.resume,
)
print(result.response)
for warning in result.warnings:
print(f"Warning: {warning}", file=sys.stderr)
main_async()
async def main_async(args) -> None:
config = apply_cli_overrides(load_config(args.config), args)
ctx = create_app(config=config)
await ctx.supervisor.start()
try:
session_id = args.session_id or str(uuid.uuid4())[:8]
agent_name = args.agent or default_agent_name(ctx.config)
if args.input:
await run_oneshot(ctx, args.user_id, session_id, agent_name, args.input)
else:
await run_repl(ctx, args.user_id, session_id, agent_name)
finally:
await ctx.shutdown()
Command-Line Options
cli
local-ai cli [options]
| Option | Default | Description |
|---|---|---|
--config |
None | Path to config.yaml |
--user-id |
default_user | User identifier |
--session-id |
random 8-char | Session ID |
--agent |
first agent | Agent name |
--input |
None | One-shot query |
--resume |
false | Resume from in-progress L2 checkpoint |
--thumbs |
None | Append session feedback (up or down) after run |
--approve-execution |
false | Auto-approve MCP tools with requires_approval in one-shot mode |
--mcp-config |
None | Path to LM Studio–compatible mcp.json; enables mcp |
Config overrides (replace matching config.yaml values when set):
| Option | Config key |
|---|---|
--llama-binary |
llama.binary_path |
--optimization-mode |
llama.optimization_mode |
--os-reserve-mb |
llama.os_reserve_mb |
--disk-reserve-mb |
llama.disk_reserve_mb |
--model-path |
llama.models.main.path |
--llama-port |
llama.models.main.port |
--ctx-size |
llama.models.main.ctx_size |
--gpu-layers |
llama.models.main.gpu_layers |
--n-batch |
llama.models.main.n_batch |
--n-ubatch |
llama.models.main.n_ubatch |
--threads |
llama.models.main.threads |
--flash-attn |
llama.models.main.flash_attn |
--workspace-root |
workspace |
--runtime-data-dir |
runtime_data_dir |
--history-limit |
app.history_limit |
--max-steps |
app.max_steps |
--max-tool-calls |
app.max_tool_calls |
--max-identical-tool-calls |
app.max_identical_tool_calls |
--context-token-budget |
app.context_token_budget |
--temperature |
app.temperature |
--max-tokens |
app.max_tokens |
--max-session-size |
app.max_session_size |
--max-concurrent-tools |
app.max_concurrent_tools |
--connect-timeout |
app.llm_timeouts.connect_timeout |
--read-timeout |
app.llm_timeouts.read_timeout |
--write-timeout |
app.llm_timeouts.write_timeout |
--pool-timeout |
app.llm_timeouts.pool_timeout |
--mcp-config |
mcp.external_path (+ mcp.enabled) |
Environment overrides (applied in create_app and stdio/CLI hosts):
| Env | Effect |
|---|---|
LOCAL_AI_MCP_CONFIG |
Same as --mcp-config |
See host_integrations.md.
Workspace
Optional project root via top-level workspace: in config.yaml (skills storage root when [skills] is enabled). Capability tools come from MCP (mcp.json), not built-in Core FS/git tools. Sessions, memory L0–L3, and RAG live under runtime_data_dir (default ~/.local-ai/), independent of workspace. There is no Core workspace file index.
| Method | Scope | Example |
|---|---|---|
workspace: in config |
All modes (cli, serve-stdio, embed) |
workspace: "D:/agent-data" |
--workspace-root |
local-ai cli only |
local-ai cli --workspace-root ./other |
Relative paths resolve from the process working directory, not from the config file location. serve-stdio also accepts --mcp-config (same as CLI); set workspace in the config passed to --config or via overrides.
Usage Examples
Interactive REPL:
local-ai cli
One-shot query:
local-ai cli --input "Read test.txt and tell me what it contains"
Custom user/session:
local-ai cli --user-id alice --session-id alice_session_1
Specific agent:
local-ai cli --agent coder
With custom config:
local-ai cli --config custom_config.yaml
Resume interrupted session:
local-ai cli --session-id abc12345 --resume
With feedback:
local-ai cli --input "hello" --thumbs up
Session Management
Session ID
- Auto-generated: first 8 characters of UUID
- User-specified:
--session-idoption - Persists in:
{runtime_data_dir}/sessions/{user_id}_{session_id}.json
User ID
- Isolation key for sessions and memory
- Default: "default_user"
- User-specified:
--user-idoption
Session File Format
{
"messages": [
{"role": "user", "content": "Hello"},
{"role": "assistant", "content": "Hi there!"},
{"role": "user", "content": "How are you?"},
{"role": "assistant", "content": "I'm doing well..."}
]
}
Agent Selection
Default: First agent in config.yaml
Specify: --agent <name>
Config:
agents:
assistant:
tools: []
coder:
tools: []
memory:
enabled: true
categories: ["knowledge", "work"]
Usage:
local-ai cli --agent coder
REPL Features
Commands
| Command | Action |
|---|---|
exit |
Quit REPL |
quit |
Quit REPL |
| (empty line) | Skip processing |
Output
You> Hello
Assistant> Hi there! How can I help you?
You> Read test.txt
Assistant> The file contains: Hello from test.txt
Error Handling
Errors from agent loop are printed to stderr:
Error: Connection refused - llama-server not healthy
Shutdown
Proper shutdown:
await ctx.shutdown()
- Stops memory index watcher
- Cancels watchdog task
- Terminates llama-server gracefully
- Closes HTTP client
Signal handlers (core/shutdown.py) set shutdown_event; AgentLoop saves an interrupted L2 checkpoint before exiting. The stdio cancel method sets a per-run cancel_event without triggering app shutdown.
Always in finally block:
try:
await run_agent(...)
finally:
await ctx.shutdown()
Testing
Key test cases:
- REPL mode with multiple messages
- One-shot mode
- Exit/quit commands
- Empty input handling
- Agent selection
- Session persistence
- Proper shutdown
Run: python -m pytest packages/local-ai-core/tests/test_cli.py -v
Usage Patterns
Quick Query
# Single question
local-ai cli --input "What's the weather like?"
# Read and process file
local-ai cli --input "Analyze the code in main.py"
Interactive Session
# Start REPL
local-ai cli
# Conversation
You> Tell me about Python
Assistant> Python is...
You> Now explain async/await
Assistant> Async/await is...
You> exit
Custom Agent
# Use coding agent
local-ai cli --agent coder --input "Review this code"
# Use custom config
local-ai cli --config dev_config.yaml
Security
- Session isolation - Each session independent
- User isolation - User ID separation
- Path sandbox - Core runtime I/O through DirectoryGuard (
runtime_data_dir; optionalworkspacefor skills only) - Capability tools - Filesystem/shell via host MCP (
mcp.json), not built-in Core tools - Local inference - Agent LLM traffic goes to llama-server; MCP HTTP servers use host-configured endpoints
Integration with App Context
# CLI loads config, applies overrides, then creates AppContext
config = apply_cli_overrides(load_config(args.config), args)
ctx = create_app(config=config)
await ctx.supervisor.start()
- Starts llama-server
- Initializes watchdog
try:
# Run agent
result = await ctx.session_runner.run(...)
finally:
await ctx.shutdown()
- Clean shutdown
Performance
- Async execution - No blocking I/O
- SessionRunner - Semaphore for
max_concurrent_sessions - Efficient memory - Session history limited by config; L1 working memory compression
- Fast startup - Reuse existing llama-server
Architecture Rules for CLI
- Single SessionRunner / AgentLoop - Don't duplicate agent logic
- Proper shutdown - Always call ctx.shutdown()
- Async execution - Use await for agent calls
- Session management - Isolate by user/session
- Config loading - load_config() → apply_cli_overrides() → create_app(config=...)
- Error handling - Safe error messages to user