An MCP-first launcher and management tool for llama.cpp llama-server instances. The MCP contract is the product; the HTTP Agent, llauncher CLI, and Streamlit UI are co-equal consumers of the same llauncher/operations/ service layer — three surfaces over one core, designed for both programmatic control (LLM agents, multi-node automation) and human operators.
The stateless service layer that every surface delegates to (ADR-LLNCH-008). Adding a verb here surfaces it across all four boundaries automatically.
- Verbs:
start,stop,swap,cancel,delete_model,list_orphans,validate_models - Pre-flight seams: model-health probe and VRAM estimation, attachable as optional callables on
swap() - ADR-LLNCH-010 port discipline: every verb takes
portas a required argument — no auto-allocation, no env-var fallback
Canonical surface for LLM agents and automation. Stdio transport; full read + mutate coverage of the core verbs.
- Discovery:
list_models,get_model_config - Lifecycle:
start_server,stop_server,swap_server,cancel_server,server_status,get_server_logs,list_orphans - Configuration CRUD:
add_model,update_model_config,delete_model,validate_config,validate_models - Telemetry & audit:
server_metrics,server_slots(ADR-LLNCH-019),read_audit(#64)
Same verbs over REST for multi-node setups (ADR-LLNCH-009 hub-spoke). Port-keyed routes (/start/{port}, /swap/{port}, /stop/{port}, /cancel/{port}, /footer-context/{port}) plus /status, /models, /models/validate. Always token-protected via an X-Api-Key header — including on loopback, where the token is auto-generated rather than operator-supplied (ADR-LLNCH-003). A non-loopback bind additionally refuses to start without a pre-existing token. Unlike the agent, the MCP server and local CLI are in-process and tokenless. See docs/auth.md for the full token/auth model.
Web dashboard for human operators. Four tabs: Dashboard (read-only running view), Models (config CRUD + per-model start/stop/swap with explicit port picker), Nodes (peer registry), Audit (local audit-log tail).
Typer command-line surface, co-equal with MCP and UI. Subcommand groups: model (list, info, remove, validate), server (start, stop, swap, cancel, status), orphan (list), node (add, list, remove, status), config (path, validate); plus a top-level audit command. Rich tables for human output and --json on every group for scripting.
- Config Persistence: Store configurations in
~/.llauncher/config.json(single source of truth) - Validation: Model paths verified, port conflicts detected, blacklists enforced
# Clone the repository
git clone https://github.com/shanevcantwell/llauncher
cd llauncher
# Install in development mode (includes UI)
pip install -e .
# Optional: Install test dependencies
pip install -e ".[test]"If you see warnings like WARNING: Ignoring invalid distribution ~ during install:
# Clean up corrupted site-packages and reinstall
cd github\llauncher
rmdir /s /q .venv
python -m venv .venv
\.venv\Scripts\activate
pip install -e .Set LLAMA_SERVER_PATH before your first model load. The code default
(~/.local/bin/llama-server) does not exist on Windows, so an unset
LLAMA_SERVER_PATH fails the very first /start with Server binary not found: C:\Users\...\.local\bin\llama-server. Point it at your actual
llama-server.exe:
- Dev /
scripts\run.batusage: setLLAMA_SERVER_PATHin the project-root.env(template:.env.example), e.g.LLAMA_SERVER_PATH=C:\path\to\llama-server.exe. - Service install (
scripts\windows\install.ps1): set it in%USERPROFILE%\.llauncher\agent.env(template:scripts/windows/agent.env.example) — required whenever the service runs under NSSM's default LocalSystem account, since that account's home does not resolve to your own profile.install.ps1prints a reminder on every run if this is still unset.
Use the runner scripts for easiest setup:
The dashboard requires the local agent to be running. Start the agent first (in its own terminal), then the dashboard in a second terminal. The UI deliberately does not auto-spawn the agent — see ADR-LLNCH-009 and the "Why doesn't the UI start the agent for me?" expander rendered on the dashboard when the agent is down.
Scripts live in scripts/, run from the repo root:
Linux/macOS: install first with pip install --user -e . from the repo
root (see Installation) — run.sh install is disabled, since
it populated a repo-local .venv disconnected from your global commands
(issue #154).
./scripts/run.sh agent # Terminal 1: start agent in foreground
./scripts/run.sh ui # Terminal 2: start dashboard (requires agent)
./scripts/run.sh stop # Stop running agent
./scripts/run.sh discover # List discovered launch scriptsWindows:
scripts\run.bat install :: Set up virtual environment and install
scripts\run.bat agent :: Terminal 1: start agent in foreground
scripts\run.bat ui :: Terminal 2: start dashboard (requires agent)
scripts\run.bat stop :: Stop running agent
scripts\run.bat discover :: List discovered launch scriptsThere is no
agent-bg(detached/background start) on either platform — it was removed when it started contending with the systemd/NSSM-managed agent. For a persistent agent, use the service installers under "Running as a service" below instead of a background shell command.
For a persistent install that survives reboots and restarts on crash,
the agent ships with installers for systemd (Linux, user-mode) and NSSM
(Windows). See docs/operations/run-as-a-service.md.
The UI supports two postures — pick whichever fits how you work:
- On demand:
llauncher-ui(or./scripts/run.sh ui) starts the dashboard in the foreground for the session; close the terminal and it's gone. - Per-operator
systemd --userservice (ADR-LLNCH-022): install withscripts/systemd/install-ui.shfor a unit that restarts on crash (Restart=on-failure) and logs to journald (journalctl --user -u llauncher-ui -f). Seedocs/operations/run-as-a-service.mdfor the full install steps.
Start the MCP server:
llauncher-mcpOr configure in your MCP client (e.g., Claude Code):
{
"mcpServers": {
"llauncher": {
"command": "llauncher-mcp",
"args": []
}
}
}Trust boundary (stdio only). The MCP server speaks the MCP stdio transport and has no authentication of its own — it implicitly trusts whatever process spawned it over the stdio pipe (typically your MCP client, e.g. Claude Desktop / Claude Code). There is no network listener for MCP. Vetting the MCP client you hand these tools to is the operator's responsibility; llauncher cannot distinguish a benign caller from a malicious one once the stdio pipe is open. See
docs/plans/security-hardening-plan.md§2.2 (control C5) for the threat-model rationale.
| Tool | Description |
|---|---|
list_models |
List all configured models with current status (running/stopped) |
get_model_config |
Get full configuration details for a specific model |
start_server |
Start a llama-server instance on a given port (model_name + port required; ADR-LLNCH-010) |
stop_server |
Stop a running server by port number |
swap_server |
Atomically swap models on a port with rollback guarantee (ADR-LLNCH-011) |
cancel_server |
Cancel an in-flight start/swap on a port (ADR-LLNCH-014) |
server_status |
Get status summary of all running servers |
get_server_logs |
Fetch recent log lines from a running server |
list_orphans |
List unmanaged llama-server processes on the local node (ADR-LLNCH-015) |
update_model_config |
Update an existing model's configuration |
validate_config |
Validate a configuration without applying it |
validate_models |
Read-only weight-file validation (existence, GGUF magic, advisory VRAM/lockfile) (#475, ADR-LLNCH-027) |
add_model |
Add a new model configuration to the store |
delete_model |
Delete a model configuration (refuses if running; ADR-LLNCH-008 §4.1) |
read_audit |
Read recent audit-log entries on this node (ADR-LLNCH-008, #64) |
server_metrics |
Live inference telemetry for a running server — safe tier, no prompt text (ADR-LLNCH-019) |
server_slots |
Per-slot detail including prompt text — sensitive tier, granted separately from server_metrics (ADR-LLNCH-019) |
Start the UI using the runner script (recommended):
Linux/macOS:
./scripts/run.sh uiWindows:
scripts\run.bat uiBind to loopback (no built-in auth). Streamlit binds wherever the operator launches it; the default is loopback. The runner scripts launch with
--server.address 127.0.0.1, and that is the recommended invocation for typical single-operator use. The dashboard itself has no built-in authentication — anything that can reach the port can drive every mutate path (start/stop servers, edit configs, manage nodes). Do not expose it beyond loopback without an operator-supplied gateway in front: Tailscale, an SSH tunnel, or a reverse proxy that enforces auth. Passing--server.address 0.0.0.0(or a LAN IP) without one of those is equivalent to publishing an unauthenticated admin console on your network. Seedocs/plans/security-hardening-plan.md§2.8 (control C12) for the threat-model rationale.
Read-only running view (no mutate verbs live here per M4 Slice 13 / #50). Status indicators (🟢 Running / ⚫ Stopped), uptime, and live log tail for each active server. Use the Models tab to start/stop/swap.
Config CRUD plus the per-model verb buttons. Add / edit / delete configurations and drive Start, Stop, Swap against the selected target node. Includes the explicit port picker (ui/components/port_picker.py) — ADR-LLNCH-010 requires the operator to choose the port at every call site; there is no auto-allocation or remembered default.
Peer registry for multi-node setups. Add / list / remove remote agent nodes, test connectivity, and observe status. The sidebar node_selector (ui/components/node_selector.py) chooses which node the Models tab acts against.
Tails the local audit log at LAUNCHER_AUDIT_PATH (~/.llauncher/audit.jsonl by default). Read-only view of commanded vs. observed events. Remote-node audit access is deferred per #64.
The llauncher Typer CLI is a co-equal consumer of llauncher/operations/ alongside the MCP server, HTTP Agent, and Streamlit UI. Every group supports a --json / -j flag for machine-readable output; the default is a Rich-rendered color table for human use.
A global --state-dir option (before the subcommand) points a single invocation at a config/state directory other than the default, with precedence --state-dir > LAUNCHER_STATE_DIR env > ~/.llauncher:
llauncher --state-dir /var/lib/llauncher model listThis is the mechanism for a non-login/non-interactive caller (an automation harness, a service account) to read a shared multiuser state dir without exporting LAUNCHER_STATE_DIR or symlinking ~/.llauncher.
Subcommand groups:
# Model configurations
llauncher model list
llauncher model info mistral-7b
llauncher model remove mistral-7b # config-only; refuses while running (#276)
llauncher model validate # read-only weight-file check, all models (#475, ADR-LLNCH-027)
llauncher model validate mistral-7b --no-vram
# Server lifecycle — port is required on start (ADR-LLNCH-010)
llauncher server start mistral-7b --port 8081
llauncher server stop 8081
llauncher server swap mistral-7b --port 8081 # ADR-LLNCH-011 5-phase swap with rollback
llauncher server cancel 8081 # ADR-LLNCH-014: signals an in-flight start/swap
llauncher server status --json
# Orphans — unmanaged llama-server processes (ADR-LLNCH-015, read-only)
llauncher orphan list
# Remote nodes (ADR-LLNCH-009)
llauncher node add my-server --host 192.168.1.100 --port 8765
llauncher node list
llauncher node status --all
llauncher node remove my-server
# Configuration store
llauncher config path # print path to config.json
llauncher config validate mistral-7b # schema-only round-trip, no filesystem touch
# Audit log (ADR-LLNCH-008, #338)
llauncher audit --limit 50 --action started --result successEach group also accepts --help. The runner scripts (./scripts/run.sh agent, ./scripts/run.sh ui) remain the easiest way to launch the agent and dashboard; the CLI subcommands above act against an already-running stack.
Create model configurations directly in ~/.llauncher/config.json. Configs can be managed via the UI or MCP tools.
Example config entry:
{
"mistral": {
"name": "mistral",
"model_path": "/path/to/model.gguf",
"mmproj_path": null,
"n_gpu_layers": 255,
"ctx_size": 131072,
"parallel": 1,
"metrics": true,
"slots": false,
"extra_args": "--threads 8 --flash-attn on --cache-type-k f32 --cache-type-v f32"
}
}Per ADR-LLNCH-026 (issue #477), ModelConfig is not a mirror of llama-server's argument schema: mmproj_path, n_gpu_layers, ctx_size, parallel, metrics, and slots are the only fields llauncher acts on directly. Every other llama-server flag (--threads, --flash-attn, --cache-type-k/-v, sampling params, etc.) is a verbatim extra_args passthrough, in the spelling from llama-server --help — no pydantic content validation. The llauncher-owned deny-list (--alias, -m/--model, --host/--port, --api-key, --metrics, --slots/--no-slots) is enforced once, at launch time, in core/process.py::build_command.
Per ADR-LLNCH-010, port is supplied at every call site (UI port picker, CLI --port, MCP port arg, HTTP /start/{port} route) and is not persisted in the config. Legacy default_port entries in config.json are silently dropped on load.
Per ADR-LLNCH-008, the lockfile directory and audit log are env-configurable so a container can mount host state as a volume — letting an in-container agent (e.g. pi-coding-agent) introspect the state of llauncher running on the host.
Two env-var families are both current, not a typo. This state-paths family below is single-
L(LAUNCHER_*); the agent-identity family under Multi-Node Management → Deployment (LLAUNCHER_AGENT_HOSTetc.) is double-L. The split is real — tracked open in #151; don't rename either family ahead of that issue landing.
| Env var | Default | Holds |
|---|---|---|
LAUNCHER_STATE_DIR |
~/.llauncher |
Base for every derived path below |
LAUNCHER_RUN_DIR |
$LAUNCHER_STATE_DIR/run |
Per-server lockfiles ({port}.lock) and swap markers |
LAUNCHER_AUDIT_PATH |
$LAUNCHER_STATE_DIR/audit.jsonl |
Append-only JSON Lines audit log |
LAUNCHER_LOG_DIR |
~/.llauncher/logs |
Per-server log directory (append mode, ADR-LLNCH-013) |
Precedence for each path is the explicit per-path var (LAUNCHER_RUN_DIR / LAUNCHER_AUDIT_PATH / LAUNCHER_LOG_DIR) > the LAUNCHER_STATE_DIR-derived default. With every var unset the paths are byte-identical to the legacy ~/.llauncher/* layout, so setting them is opt-in.
To let a container read the host's live llauncher state, mount the host paths in read-only and point the in-container env vars at the mount:
docker run \
-v "$HOME/.llauncher/run:/host-llauncher/run:ro" \
-v "$HOME/.llauncher/audit.jsonl:/host-llauncher/audit.jsonl:ro" \
-e LAUNCHER_RUN_DIR=/host-llauncher/run \
-e LAUNCHER_AUDIT_PATH=/host-llauncher/audit.jsonl \
my-agent-imageMount read-only (:ro) when the container only introspects; drop :ro if the containerized process is the one commanding llauncher and must write lockfiles/audit entries. The audit log is a single file, so bind-mount the file itself (not its parent dir) to avoid masking sibling state.
llauncher includes validation rules to prevent problematic actions:
- Port conflicts: Prevents starting models on ports already in use
- Blacklisted ports: Default blacklist includes port 8080 (commonly used by other services)
- Model whitelists: Optionally restrict which models can be started
- Caller blacklists: Restrict which callers (UI, MCP, etc.) can perform actions
vN (v1, v2, v3 …) denotes the architecture generation; 0.x denotes
the semver release (currently 0.4.1a0 / v0.4.1-alpha — read the live
number from pyproject.toml or the latest git tag, not this doc). They are
independent axes and do not map to each other — see docs/VERSIONING.md.
llauncher/
├── pyproject.toml
├── llauncher/
│ ├── __init__.py
│ ├── __main__.py
│ ├── cli.py # Typer CLI (model/server/orphan/node/config groups)
│ ├── state.py # Legacy LauncherState — eviction-compat hook (ADR-LLNCH-008)
│ ├── operations/ # Stateless service layer; MCP/HTTP/CLI/UI all delegate here (ADR-LLNCH-008)
│ │ ├── start.py
│ │ ├── stop.py
│ │ ├── swap.py # ADR-LLNCH-011 five-phase swap with rollback
│ │ ├── delete.py
│ │ ├── orphan.py # ADR-LLNCH-015 read-only orphan listing
│ │ └── preflight.py # Model-health + VRAM seams
│ ├── agent/ # HTTP agent (FastAPI, port-keyed routes per ADR-LLNCH-010)
│ │ ├── auth.py
│ │ ├── config.py
│ │ ├── footer_cache.py # /footer-context/{port} TTL cache (ADR-LLNCH-012)
│ │ ├── middleware.py
│ │ ├── routing.py
│ │ └── server.py # Lifespan handler reaps managed children on SIGTERM/SIGINT
│ ├── mcp_server/ # MCP server (stdio transport)
│ │ ├── server.py
│ │ └── tools/ # servers / models / config tool groups
│ ├── core/ # Primitive substrate (no LauncherState)
│ │ ├── audit_log.py # JSON Lines audit (ADR-LLNCH-008)
│ │ ├── config.py # ConfigStore — single source of truth
│ │ ├── gpu.py # GPU collector (ADR-LLNCH-006)
│ │ ├── lockfile.py # Atomic O_EXCL per-port lockfiles
│ │ ├── log_rotation.py # ADR-LLNCH-013 append + rotate
│ │ ├── marker.py # In-flight swap/start marker (ADR-LLNCH-011/014)
│ │ ├── model_health.py # Cache probe (ADR-LLNCH-005)
│ │ ├── process.py # Subprocess management
│ │ └── settings.py # LAUNCHER_* env-var family
│ ├── models/
│ │ └── config.py # Pydantic ModelConfig (no default_port; ADR-LLNCH-010)
│ ├── remote/ # Multi-node hub-spoke (ADR-LLNCH-009)
│ │ ├── node.py # RemoteNode (port-keyed ops)
│ │ ├── registry.py # NodeRegistry
│ │ └── state.py # RemoteAggregator (swap_on_node parity)
│ └── ui/ # Streamlit dashboard
│ ├── app.py
│ ├── utils.py # render_op_result, OpResultSeverity ladder
│ ├── components/
│ │ ├── node_selector.py
│ │ └── port_picker.py # Explicit port input — no auto-allocation
│ └── tabs/
│ ├── audit.py
│ ├── dashboard.py # Read-only running view
│ ├── models.py # Config CRUD + start/stop/swap verbs
│ └── nodes.py
Run the test suite:
pytest
# or with coverage
pytest --cov=llauncher --cov-report=term-missingRunning from a git worktree: the dev .venv is a shared editable
install, so its .pth entry always resolves import llauncher to
whichever checkout it was last pip install -e'd from (normally the main
checkout) — a worktree invocation whose collection order lets that .pth
win reads coverage against the main checkout's files, not the worktree's
(#361). [tool.coverage.paths] in pyproject.toml reconciles the report,
but the sanctioned invocation is still to pin the import explicitly:
PYTHONPATH="$(pwd)" pytest --cov=llauncher --cov-report=term-missingNever repoint the shared venv's .pth at a worktree to work around
this — that .venv also backs the live llauncher-agent systemd
service, and a crash mid-window leaves the live service importing
worktree code. Restore-after-use is not a substitute for not mutating it.
Test files are in tests/:
tests/unit/: Unit tests for models, config, and processtests/integration/: Integration tests for state management
For an inventory of which tests exist (file-by-file, with markers and
docstring first lines), see docs/generated/TEST_SUITE_SUMMARY.md.
Regenerate after adding or renaming tests:
python scripts/summarize_tests.pyThe coverage floor is pinned at --cov-fail-under=93 against non-UI
scope in pytest.ini; UI coverage is deferred to the AppTest harness
in #69 (v3-alpha).
llauncher supports managing llama-server instances across multiple machines (Windows and Linux) on a local network from a single dashboard.
Each managed node runs a lightweight agent that exposes an HTTP API. The "head" dashboard connects to these agents over the LAN:
┌─────────────────────────────────────┐
│ HEAD DASHBOARD │
│ - Streamlit UI with node selector │
│ - Connects to all agents via HTTP │
└─────────────┬───────────────────────┘
│ LAN (port 8765)
┌─────────┼─────────┐
▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐
│ Agent │ │ Agent │ │ Agent │
│ Linux │ │Windows │ │ Linux │
│ :8765 │ │ :8765 │ │ :8765 │
└────────┘ └────────┘ └────────┘
On every machine you want to manage (including the head):
Linux/macOS:
git clone https://github.com/shanevcantwell/llauncher
cd llauncher
pip install --user -e .Windows:
git clone https://github.com/shanevcantwell/llauncher
cd llauncher
scripts\run.bat installUsing runner scripts (recommended):
Linux/macOS:
./scripts/run.sh agent # Foreground
./scripts/run.sh stop # Stop agentWindows:
scripts\run.bat agent :: Foreground
scripts\run.bat stop :: Stop agentNeither script has a background/detached mode — see "Running as a service" above for a persistent agent that survives reboots.
With custom configuration:
# Linux/macOS
LLAUNCHER_AGENT_PORT=9000 LLAUNCHER_AGENT_NODE_NAME="my-server" ./scripts/run.sh agent
# Windows (PowerShell)
$env:LLAUNCHER_AGENT_PORT="9000"
$env:LLAUNCHER_AGENT_NODE_NAME="my-server"
scripts\run.bat agentEnvironment Variables:
LLAUNCHER_AGENT_HOST: Host to bind to (default:127.0.0.1). Set to0.0.0.0or a specific LAN IP to expose the agent to other hosts — see "Security Notes" below.LLAUNCHER_AGENT_PORT: Port to listen on (default:8765)LLAUNCHER_AGENT_NODE_NAME: Friendly name for the nodeLLAUNCHER_AGENT_TOKEN: The agent'sX-Api-Keytoken. The agent always enforces a token (auth is never off, even on loopback); this var lets you supply it explicitly and always wins over the file below. Required when binding to anything other than loopback — the agent refuses to start on a non-loopback host without it. Special value-reads the token from stdin (one line). On a loopback start with no value set (and noLLAUNCHER_AGENT_TOKEN=line inagent.env), a fresh token is auto-generated and appended into~/.llauncher/agent.env(mode 0600 if newly created). For the full token/auth model (which consumers need a token, exempt paths, resolution order), seedocs/auth.md.
Linux/macOS:
./scripts/run.sh uiWindows:
scripts\run.bat uiThe dashboard will automatically:
- Show a loading screen while initializing
- Register itself as the "local" node
In the dashboard:
- Go to the Nodes tab
- Click ➕ Add New Node
- Enter:
- Node Name: Friendly name (e.g.,
linux-box,windows-server) - Host: IP address or hostname (e.g.,
192.168.1.100) - Port: Agent port (default:
8765) - API Key: the remote agent's token (see Adding a remote node below)
- Node Name: Friendly name (e.g.,
- Click 🔍 Test Connection to verify
- Click ➕ Add Node to register
A remote agent always enforces a token (auth is never off, even on
loopback). To pair the head with a remote node you copy that token by hand —
it currently is not issued automatically (session-token issuance is
tracked under #137). The token lives in a single live file per platform —
agent.env, parsed directly by the agent and the local UI (issue #284):
| Platform | Live source (agent.env) |
Seeded (once) by |
|---|---|---|
| Linux | ~/.config/llauncher/agent.env |
scripts/systemd/install.sh |
| Windows | %USERPROFILE%\.llauncher\agent.env |
scripts/windows/install.ps1 |
Step by step:
- Get on the remote box. SSH to a Linux node, or RDP to a Windows node.
- Read the token. The portable way is the agent's own subcommand, which
resolves the token from env / stdin /
agent.envand prints it to stdout:If you prefer to read the file directly, look for thellauncher-agent print-token
LLAUNCHER_AGENT_TOKEN=line:# Linux grep LLAUNCHER_AGENT_TOKEN= ~/.config/llauncher/agent.env
Over SSH you can do both in one shot:# Windows (PowerShell) Select-String LLAUNCHER_AGENT_TOKEN= $env:USERPROFILE\.llauncher\agent.env
ssh windows-box llauncher-agent print-token. - Copy the value. It is a single
secrets.token_urlsafe(32)string on one line. - Paste it into the head's UI. Back on the head machine, paste the value into the API Key field of the Add New Node form (step 3 above).
The token is stored on the head at ~/.llauncher/node_tokens.json (mode 0600);
the local node is excluded because its token already lives in agent.env.
Ensure port 8765 is open on managed nodes:
Linux (ufw):
sudo ufw allow 8765/tcpLinux (firewalld):
sudo firewall-cmd --permanent --add-port=8765/tcp
sudo firewall-cmd --reloadWindows (PowerShell):
New-NetFirewallRule -DisplayName "llauncher Agent" -Direction Inbound -LocalPort 8765 -Protocol TCP -Action Allow- Loopback by default: The agent binds to
127.0.0.1unlessLLAUNCHER_AGENT_HOSTis set explicitly. Set it to a LAN IP (or0.0.0.0) to expose the agent to other hosts on the network. - Token required for non-loopback binds: Binding to anything other than
127.0.0.1/::1/localhostrequiresLLAUNCHER_AGENT_TOKENto be set. The agent refuses to start otherwise. On loopback first-run with no token configured, a fresh token is generated and appended into~/.llauncher/agent.env(mode 0600 if newly created) and printed once to stderr. - Trusted LAN Only: Even with a token, only expose the agent on networks you trust — the transport is plain HTTP (no TLS). Tailscale is the recommended option for cross-host trust.
- Firewall: Restrict port 8765 to your LAN subnet.
- Full auth model: For the token/auth reference — the two planes (token-bearing HTTP agent vs. tokenless local MCP/CLI), the
X-Api-Keyheader, exempt paths, resolution precedence, and file locations/modes — seedocs/auth.md.
The sidebar Node Selector (ui/components/node_selector.py) picks the target node — local plus any registered remotes. A single target is always selected; the "All Nodes" cross-node aggregate view was dropped in M4 Slice 13 (#50).
- Dashboard Tab: read-only running view across the selected node.
- Models Tab: config CRUD + per-model Start / Stop / Swap, acting on the selected node.
- Nodes Tab: registered-nodes list with Test Connection and Remove controls.
- Audit Tab: tails the local
LAUNCHER_AUDIT_PATH. Remote-node audit access is deferred per #64.
-
Verify agent is running on the remote node:
curl http://<node-ip>:8765/health
-
Check firewall rules on the remote node
-
Verify the agent is binding to the correct interface:
# Default is 127.0.0.1:8765 (loopback). For LAN access you must # have set LLAUNCHER_AGENT_HOST and LLAUNCHER_AGENT_TOKEN. netstat -tlnp | grep 8765
-
Check if port 8765 is already in use:
lsof -i :8765 # or netstat -tlnp | grep 8765
-
Use a different port:
LLAUNCHER_AGENT_PORT=9000 llauncher-agent
-
Verify network connectivity:
ping <remote-node-ip>
-
Check that the agent is not binding to loopback only:
- The default is
127.0.0.1:8765. For cross-host access setLLAUNCHER_AGENT_HOST=0.0.0.0(or a specific LAN IP) andLLAUNCHER_AGENT_TOKEN— the agent refuses to start on a non-loopback host without a token.
- The default is
When an agent is running, visit http://<node-ip>:8765/docs for interactive API documentation.
MIT