# Architecture Source: https://docs.openlegion.ai/concepts/architecture How agents, the mesh host, the browser service, and trust zones work together OpenLegion is a **container-isolated multi-agent runtime**. Agents run in isolated Docker containers; a single FastAPI **mesh host** on port 8420 holds credentials, routes inter-agent messages, enforces permissions, proxies LLM/API calls, reverse-proxies VNC, and serves the dashboard SPA. A separate **browser service container** on port 8500 hosts one Camoufox instance per agent on KasmVNC display slots. **Fleet model, not hierarchy. No CEO agent.** Users talk to agents directly. Agents coordinate through a SQLite blackboard, PubSub, lanes, and a structured handoff protocol — never through a master LLM that routes work. ## Overview ``` User (CLI REPL / Telegram / Discord / Slack / WhatsApp / Webhook / Dashboard) -> Mesh Host (FastAPI :8420) — routes messages, enforces permissions, proxies APIs -> Agent Containers (FastAPI :8400 each) — isolated execution, private memory -> Browser Service Container (FastAPI :8500) — per-agent Camoufox on KasmVNC 6100..6163 ``` ``` ┌──────────────────────────────────────────────────────────────────────────┐ │ User Interface │ │ │ │ CLI (click) Webhooks Cron Scheduler │ │ - start (REPL) - POST /webhook/ - "0 9 * * 1-5" (5-field) │ │ - chat / status hook/{id} - "every 15m" (interval) │ │ - projects / tasks - dashboard-only - tick interval 5s │ │ - pending / confirm creation - heartbeat mode w/ skip-LLM │ └──────────────┬──────────────────┬──────────────────┬─────────────────────┘ │ │ │ ▼ ▼ ▼ ┌──────────────────────────────────────────────────────────────────────────┐ │ Mesh Host (FastAPI :8420) │ │ │ │ ┌────────────┐ ┌─────────┐ ┌────────────┐ ┌────────────────────────┐ │ │ │ Blackboard │ │ PubSub │ │ Lanes │ │ Credential Vault │ │ │ │ SQLite WAL │ │ topics, │ │ followup / │ │ (API proxy) │ │ │ │ atomic CAS │ │ subs, │ │ steer / │ │ │ │ │ │ via │ │ fan-out │ │ collect │ │ Two-tier prefixes: │ │ │ │ write_if_ │ │ notify │ │ FIFO per │ │ OPENLEGION_SYSTEM_* │ │ │ │ version, │ │ │ │ agent │ │ OPENLEGION_CRED_* │ │ │ │ audit/undo │ │ │ │ │ │ Opaque $CRED{name} │ │ │ └────────────┘ └─────────┘ └────────────┘ └────────────────────────┘ │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ MessageRouter│ │ Permission │ │ Container │ │ Cost │ │ │ │ resolves │ │ Matrix │ │ Manager │ │ Tracker │ │ │ │ agent ID or │ │ │ │ │ │ │ │ │ │ capability:* │ │ Per-agent │ │ Docker life- │ │ Per-agent │ │ │ │ cross- │ │ ACLs, globs, │ │ cycle, nets, │ │ + per- │ │ │ │ project │ │ default deny │ │ volumes │ │ project │ │ │ │ block │ │ │ │ │ │ ledger │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ │ └──────────────────────────────────────────────────────────────────────────┘ │ │ Docker bridge network │ ┌─────────┼──────────┬──────────────────────┬─────────────────┐ ▼ ▼ ▼ ▼ ▼ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌──────────────┐ │ Agent A │ │ Agent B │ │ Agent C │ ... │ Agent N │ │ Browser │ │ :8400 │ │ :8400 │ │ :8400 │ │ :8400 │ ───▶ │ service │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ :8500 │ (per-container port; mesh bridges across Docker IPs) │ Camoufox per │ │ agent on │ │ displays │ │ :100..:163 │ │ paired KasmVNC│ │ 6100..6163 │ └──────────────┘ ``` ## Trust Zones OpenLegion defines four trust zones plus a 2.5 operator-or-internal tier. Defense-in-depth: each zone has its own credential set, network policy, and entry conditions. | Level | Zone | Allowed actions | | ----- | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 0 | **Untrusted** | External input — webhook bodies (1MB cap), channel messages, user prompts. Sanitized via `sanitize_for_prompt()` before reaching agents. | | 1 | **Sandboxed (agents)** | Agent containers. UID 1000, `cap_drop=ALL`, `no-new-privileges`, read-only root FS, `tmpfs=/tmp` 100m noexec/nosuid. No LLM API keys — all provider calls proxy through mesh. | | 2 | **Trusted (mesh)** | Mesh host process. Holds credentials, manages containers, routes inter-agent messages. | | 2.5 | **Operator-or-internal** | `_require_operator_or_internal` gate. Fleet-wide metrics, per-agent metrics, stale-tasks, audit/archive endpoints. | | 3 | **Loopback-only** | Requires **both** `x-mesh-internal: 1` header **and** loopback IP. Caddy strips `x-mesh-internal` from inbound public traffic. | ## Mesh Host The mesh host is the central coordination layer — a single FastAPI process running on the host machine. ### Blackboard SQLite WAL key-value store with **atomic CAS via `write_if_version`** and an audit log that supports undo/archive. Keys are **auto-namespaced under `projects/{name}/`** so fleets in different projects can't collide. | Namespace | Purpose | Example | | ----------- | ----------------------------- | --------------------------- | | `tasks/*` | Task assignments and handoffs | `tasks/researcher/h_abc123` | | `context/*` | Shared agent context | `context/prospect_acme` | | `signals/*` | Inter-agent signals | `signals/research_complete` | | `history/*` | Append-only audit log | `history/action_xyz` | ### PubSub & Lanes PubSub fan-outs events to subscribers; both publish and subscribe are gated by `can_publish` / `can_subscribe`. **Lanes** are per-agent FIFO queues with three modes: * **`followup`** (default) — append-and-wake. * **`steer`** — inject into a busy agent's loop (rate-limited 10 wakeups / 3600s). * **`collect`** — batch messages while busy; drain on idle. `MessageRouter` resolves either a literal agent ID or `capability:` to a container URL and blocks cross-project routing when `OPENLEGION_PROJECT_SCOPE_MODE=enforce`. ### Credential Vault Agents never hold API keys. All external API calls route through the mesh. The vault uses a two-tier prefix system: * `OPENLEGION_SYSTEM_*` — LLM provider keys and other mesh-only secrets. Never agent-accessible. * `OPENLEGION_CRED_*` — agent-tier credentials. Access controlled per-agent via `allowed_credentials` (fnmatch glob, case-insensitive). At call time the agent receives an opaque handle (`$CRED{name}`) — the real value is substituted by the mesh proxy. The handle is the only thing visible inside the agent container. ### Model Failover Configurable failover chains cascade across LLM providers transparently. `ModelHealthTracker` applies exponential cooldown per model (transient errors: 60s -> 300s -> 1500s, billing/auth errors: 1h). Permanent errors (400, 404) don't cascade. Streaming failover is supported — if a connection fails mid-stream, the next model in the chain picks up. ### Permission Matrix Every inter-agent operation is checked against per-agent ACLs in `config/permissions.json`. **Default policy: deny**; missing file means deny-all. ```json theme={null} { "researcher": { "can_message": ["analyst"], "can_publish": ["research_complete"], "can_subscribe": ["new_lead"], "blackboard_read": ["tasks/*", "context/*"], "blackboard_write": ["context/prospect_*"], "allowed_apis": ["llm", "brave_search"], "allowed_credentials": ["brightdata_*"], "can_use_browser": true } } ``` Beyond messaging/blackboard/pub-sub, ACLs include `can_use_browser`, `browser_actions` (allowlist or `["*"]`), `can_spawn`, `can_manage_cron`, `can_use_wallet`, `wallet_allowed_chains`, `wallet_spend_limit_per_tx_usd`, `wallet_spend_limit_daily_usd`, `wallet_rate_limit_per_hour`, `wallet_allowed_contracts`, plus six control-plane flags (`can_manage_fleet`, `can_manage_projects`, `can_edit_agent_config`, `can_view_fleet_metrics`, `can_route_tasks`, `can_request_user_credentials`). ### Operator (reserved role) A reserved agent ID, `operator`, is auto-created at startup. In managed hosting it is the user's primary chat partner — the agent that can manage your fleet on your behalf (apply templates, edit agents, archive projects, confirm hard edits). Resources are lighter than a worker: **128MB RAM, 0.05 CPU** (workers default to 384MB / 0.15 CPU). The operator has the control-plane flags listed above set to `true` by default. **Operator ceiling:** the operator cannot grant `can_spawn=true` or `can_use_wallet=true` to any agent. Those capabilities must be set by a human operator outside the chat surface. ### Container Manager Each agent runs in an isolated Docker container with: * **Image**: `openlegion-agent:latest` (Python 3.12, slim, no Node.js). * **Network**: Docker bridge with port mapping (macOS/Windows) or host network (Linux). * **Volume**: `openlegion_data_{agent_id}` mounted at `/data` (names with spaces/special chars are sanitized). * **Resources (worker)**: 384MB RAM, 0.15 CPU quota, `pids_limit=256`. * **Resources (operator)**: 128MB RAM, 0.05 CPU quota. * **Security**: `no-new-privileges`, runs as UID 1000, read-only root filesystem, `cap_drop=ALL`, `tmpfs=/tmp` 100MB noexec/nosuid. * **Port**: every agent container listens on `:8400` internally. The mesh bridges across Docker IPs — there is no incrementing port-per-agent. A separate **browser service container** runs on `:8500` and hosts **one Camoufox per agent** on Xvnc displays `:100..:163` (64 slots) paired with **KasmVNC ports 6100..6163**. Resources scale with `OPENLEGION_MAX_AGENTS`: Basic (≤1 agent) 2GB/512MB/1.0 CPU; Growth (2–5) 4GB/1GB/1.5 CPU; Pro (6–15) 8GB/2GB/2.0 CPU; Pro Max (>15) 16GB/4GB/4.0 CPU. Note: `OPENLEGION_MAX_AGENTS=0` (the default for "unlimited") counter-intuitively maps to the Basic tier. The browser container has its own egress filter (iptables OUTPUT REJECTs RFC1918, loopback, link-local, CGNAT, and IANA-reserved IPv4+IPv6; allows in-container loopback; fail-closed). See [Security](/concepts/security). ### Projects Multi-project namespaces let you run separate agent fleets with isolated configuration. Each project has its own `agents.yaml`, `permissions.json`, and auto-scoped blackboard — keys are namespaced under `projects/{name}/` so fleets in different projects can't collide. `OPENLEGION_MAX_PROJECTS` (default unlimited) caps the number of projects. ## Design Principles | Principle | Rationale | | ----------------------------------------- | ----------------------------------------------------------------------------------------- | | Messages, not method calls | Agents communicate through HTTP/JSON. Never shared memory or direct invocation. | | The mesh is the only door | No agent has network access except through the mesh. No agent holds credentials. | | Private by default, shared by promotion | Agents keep knowledge private. Facts are explicitly promoted to the blackboard. | | Default deny, fail-closed | Permissions deny by default; SSRF and egress filters reject on DNS error or missing rule. | | Fleet model, not hierarchy. No CEO agent. | Coordination is blackboard + pub/sub + handoffs, not a master agent routing tasks. | | Small enough to audit | \~77,000 lines in `src/`. The runtime is auditable in a day. | | Skills over features | New capabilities are agent skills, not mesh-level code. | | SQLite for all state | Single-file databases. No Redis, no external services. WAL mode for concurrent reads. | | Zero vendor lock-in | LiteLLM supports 100+ providers. Markdown workspace files. No proprietary formats. | # Security Model Source: https://docs.openlegion.ai/concepts/security Defense-in-depth across trust zones, hardening, vault, validation, and egress OpenLegion was designed assuming agents will be compromised. The security model is **defense-in-depth**: trust zones gate what each component can touch, agent containers are hardened, credentials are isolated, every cross-boundary call is permission-checked, and the browser service has its own egress filter. **Default deny. Fail-closed.** ## Trust Zones Four zones plus a 2.5 operator-or-internal tier (mirrors the table in [Architecture](/concepts/architecture)): | Level | Zone | Notes | | ----- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ | | 0 | **Untrusted** | External input. Webhook bodies 1MB cap, optional HMAC-SHA256, all messages sanitized via `sanitize_for_prompt()`. | | 1 | **Sandboxed (agents)** | Hardened agent containers (see below). Hold no LLM keys — proxy through mesh. | | 2 | **Trusted (mesh)** | Mesh host. Holds credentials, manages containers, routes messages. | | 2.5 | **Operator-or-internal** | `_require_operator_or_internal` gate on fleet-wide metrics, per-agent metrics, stale tasks, audit/archive endpoints. | | 3 | **Loopback-only** | Requires both `x-mesh-internal: 1` header **and** loopback IP. Caddy strips the header from inbound public traffic on managed VPSes. | ## Defense-in-Depth Layers | Layer | Mechanism | What it prevents | | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | | Runtime isolation | **Docker containers** (default) or **Docker Sandbox microVMs** (opt-in via `--sandbox`) | Agent escape, kernel exploits | | Container hardening | UID 1000, `cap_drop=ALL`, `no-new-privileges`, read-only root FS, `tmpfs=/tmp` 100m noexec/nosuid, `pids_limit=256`, 384MB / 0.15 CPU (worker) | Privilege escalation, resource abuse | | Browser-container egress | iptables OUTPUT REJECT for RFC1918, loopback, link-local, CGNAT, IANA-reserved IPv4+IPv6; allows in-container loopback; fail-closed | SSRF/data exfil from the browser tier | | Credential separation | Two-tier vault: `OPENLEGION_SYSTEM_*` mesh-only; `OPENLEGION_CRED_*` agent-tier gated by glob. Opaque `$CRED{name}` handle substitution at call time. | Key leakage, unauthorized API use | | Permission enforcement | Per-agent ACLs (default deny) for messaging, pub/sub, blackboard globs, allowed\_apis, browser\_actions, wallet, control-plane flags | Unauthorized data access, capability escalation | | Input validation | Path traversal (4-stage check), AST validation for `create_skill`, SSRF protection, safe condition eval (no `eval()`), bounded execution caps | Injection, runaway loops | | Unicode sanitization | Invisible-character stripping at 56 call sites across user input, tool results, and system-prompt context | Prompt injection via hidden Unicode | ## Dual Runtime Backend | | Docker Containers (default) | Docker Sandbox microVMs | | ---------------- | ----------------------------------- | ---------------------------------------- | | **Isolation** | Shared kernel, namespace separation | Own kernel per agent (hypervisor) | | **Escape risk** | Kernel exploit could escape | Hypervisor boundary — much harder | | **Performance** | Native speed | Near-native (Rosetta 2 on Apple Silicon) | | **Requirements** | Any Docker install | Docker Desktop 4.58+ | | **Enable** | `openlegion start` | `openlegion start --sandbox` | `SandboxBackend` initialization can fail (Docker Desktop too old, hypervisor disabled, etc.). On failure it falls back to `DockerBackend` automatically and logs the reason. ## Container Hardening (full set) Worker containers run with the following enforced flags (`engine/src/host/runtime.py`): * UID 1000 (non-root). * 384MB RAM, 0.15 CPU quota. * `pids_limit=256`. * `cap_drop=ALL`, `no-new-privileges`. * `read_only=True` root filesystem. * `tmpfs=/tmp` 100MB, `noexec`/`nosuid`. Operator containers run with the same hardening but **128MB RAM / 0.05 CPU**. The browser service container is hardened the same way except it must add `NET_ADMIN`, `SETUID`, `SETGID` capabilities to run iptables and `gosu` to UID 1000. Its egress is locked down by the iptables filter described above. ## Credential Vault Agents never hold API keys. The vault uses a two-tier prefix system: * **`OPENLEGION_SYSTEM_*`** — LLM provider keys, channel tokens, master secrets. Never agent-accessible. * **`OPENLEGION_CRED_*`** — agent-tier tool/service keys. Access controlled per-agent via `allowed_credentials` (fnmatch glob, case-insensitive; `["*"]` = all agent-tier; system creds always blocked). How it works: 1. The vault loads credentials from both prefix sets at startup. 2. Agents call APIs through the mesh proxy. Their tool args may reference `$CRED{name}` — an opaque handle, not the secret value. 3. The mesh substitutes the real value server-side before forwarding to the provider. 4. Budget limits are checked before dispatching LLM calls; usage is recorded after. A fully compromised agent never sees the raw key — only the handle. **CAPTCHA solver credentials** (`CAPTCHA_SOLVER_KEY`, `_SECONDARY`, `CAPTCHA_SOLVER_PROXY_LOGIN`, `_PASSWORD`) intentionally bypass the standard vault. They live in env only and are stripped from `config/settings.json` at load. ## Permission Matrix Per-agent ACLs in `config/permissions.json`. **Default policy: deny**; missing file means deny-all. Permissions cover: * **Messaging**: `can_message` (list of target agent IDs). * **Pub/Sub**: `can_publish`, `can_subscribe` (topic lists). * **Blackboard**: `blackboard_read`, `blackboard_write` (glob patterns). * **APIs**: `allowed_apis`, `allowed_credentials` (glob over agent-tier creds). * **Browser**: `can_use_browser`, `browser_actions` (`None`/`["*"]` = all known actions; `[]` = deny; specific list = allowlist). * **Cron**: `can_manage_cron`. * **Spawning**: `can_spawn`. * **Wallet**: `can_use_wallet`, `wallet_allowed_chains`, `wallet_spend_limit_per_tx_usd`, `wallet_spend_limit_daily_usd`, `wallet_rate_limit_per_hour`, `wallet_allowed_contracts`. * **Control-plane** (six flags, operator-default `true`, workers `false`): `can_manage_fleet`, `can_manage_projects`, `can_edit_agent_config`, `can_view_fleet_metrics`, `can_route_tasks`, `can_request_user_credentials`. **Operator ceiling:** the operator can adjust most permissions but **cannot grant `can_spawn=true` or `can_use_wallet=true`**. Those capabilities require a human operator outside the chat surface. ## Input Validation * **Path traversal protection** — agent file operations are confined to `/data` via a 4-stage check (strip `/data/` prefix, reject `..` lexically, `lstat()` symlink-safe walk, final `is_relative_to("/data")`). * **AST validation for skill self-authoring** — `create_skill` runs the candidate through an AST validator with **23 forbidden imports, 16 forbidden calls, 11 forbidden attribute accesses**, plus a 10,000-char size cap (`_MAX_SKILL_SIZE`). * **Safe condition evaluation** — workflow/condition parsing uses a regex-based safe parser, never `eval()`. * **Bounded execution** — task loops cap at `MAX_ITERATIONS=20`; chat at `CHAT_MAX_TOOL_ROUNDS=30` per turn / `CHAT_MAX_TOTAL_ROUNDS=200` per session / `_MAX_SESSION_CONTINUES=5`; heartbeats at `HEARTBEAT_MAX_ITERATIONS=12`. * **Token budgets** — per-agent daily ($0.01–$1000) and monthly ($0.10–$30000) caps enforced before dispatch. * **Tool-loop detection** — warn @ 2 repeats, block @ 4, terminate @ 9. ## SSRF Protection The HTTP tool runs every outbound request through a multi-layer SSRF filter: * **DNS pinning** — resolve once, reuse the same IP for the actual connection. * **Blocklist** — RFC1918, loopback, link-local, CGNAT (`100.64.0.0/10`), 6to4 (`2002::/16`), Teredo, IPv4-mapped IPv6. * **Fail-closed on DNS error** — any resolution failure rejects the request. * **Max 5 redirects**, re-validated at each hop. * **Cross-origin auth strip** — Authorization headers are removed when redirecting to a new origin. The browser service container enforces its own iptables egress filter on top of this. ## Dashboard & VNC * **CSRF** — state-changing dashboard endpoints require an `X-Requested-With` header. * **CSP** — the dashboard sets a CSP, but it allows `unsafe-inline` and `unsafe-eval` (Alpine.js requirement). Jinja `autoescape=True` is the primary XSS defense, not CSP. * **VNC proxy** at `/agent-vnc/{agent_id}/{path}` — **rejects agent Bearer tokens** and **requires the `ol_session` cookie on both HTTP and WebSocket** upgrade. Agent credentials cannot leak through a browser session. * **Authentication** — dev/self-hosted is open if `/opt/openlegion/.access_token` is absent. Hosted requires an `ol_session` cookie verified via HMAC (24h max age + 5-min skew). The SSO callback `/__auth/callback` and HMAC verification live in the upstream Caddy auth-gate sidecar — **the engine itself only consumes the cookie**. ## Wallet Seed Reveal The master mnemonic is generated by `POST /api/wallet/init` and returned **once** with `Cache-Control: no-store`. Every subsequent call to `GET /api/wallet/seed` returns **HTTP 410 Gone** — there is no second-chance reveal. Private keys derived from the seed (BIP-44 for EVM, HMAC-SHA512 over PBKDF2 for Solana) never leave the mesh process. ## What This Does NOT Do OpenLegion is small and honest about its limits. The following are **not** part of the threat model: * **No "zero-trust" claim.** We use trust zones + defense-in-depth. * **No compliance certifications** (SOC 2, ISO 27001, HIPAA). The engine is v0.1.0. * **Engine SQLite databases are not encrypted at rest.** Only `.env` is `chmod 0o600`; the provisioner separately encrypts SSH keys at rest with Fernet. * **JWT sessions are non-revocable** (NextAuth JWT strategy is stateless). * **App rate limiter is in-memory, single-region** — no global rate limits. * **`_blackboard_xproject_count` and `tool_denials_24h` are observability-only**, not enforcement. * **No air-gapped mode.** All agents need mesh egress for LLM proxy. * **The engine does not implement SSO end-to-end.** HMAC verification, replay protection, and cookie issuance live in the auth-gate sidecar shipped via cloud-init on managed VPSes — not in engine code. # Agents Source: https://docs.openlegion.ai/features/agents How agents work inside their containers — workers, the operator, subagents, and self-extending skills Each agent runs inside its own Docker container, serving a FastAPI app on `:8400` with endpoints for task assignment, chat, status, capabilities, and results. Every agent container listens on the same port — the mesh bridges across Docker IPs. ## Workers vs the Operator Every agent is either a **worker** or **the operator** — the latter is a reserved agent ID auto-created at startup. | | Worker | Operator | | ------------------- | ----------------------- | ---------------------------------------------------------------------------------------- | | Resources | 384MB RAM / 0.15 CPU | 128MB RAM / 0.05 CPU | | Tools | Granted via permissions | Operator-only tool surface (`fleet_tool`, `operator_tools`) plus standard tools | | Heartbeat | Configurable | Force-locked to `every 15m` | | Control-plane flags | Defaults `false` | Defaults `true` (manage fleet/projects/agents, view metrics, route tasks, request creds) | | Ceiling | n/a | Cannot grant `can_spawn=true` or `can_use_wallet=true` | In managed hosting the operator is your primary chat partner — the agent you talk to when you say "spin up a new researcher" or "what's my fleet doing today". ## Agent Container ``` ┌─────────────────────────────────────────────────────────────┐ │ Agent Container │ │ │ │ FastAPI Server (:8400) │ │ POST /task POST /chat POST /chat/reset │ │ GET /status GET /result GET /capabilities │ │ GET /workspace GET|PUT /workspace/{file} │ │ GET /heartbeat-context │ │ │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ AgentLoop │ │ │ │ │ │ │ │ Task Mode: MAX_ITERATIONS=20 │ │ │ │ Chat Mode: CHAT_MAX_TOOL_ROUNDS=30 per turn, │ │ │ │ CHAT_MAX_TOTAL_ROUNDS=200 per session, │ │ │ │ _MAX_SESSION_CONTINUES=5 │ │ │ │ Heartbeat: HEARTBEAT_MAX_ITERATIONS=12 │ │ │ │ │ │ │ │ All modes: LLM call -> tool execution -> context mgmt│ │ │ └──┬──────────┬──────────┬──────────┬──────────┬───────┘ │ │ │ │ │ │ │ │ │ ┌──▼───┐ ┌──▼───┐ ┌──▼──────┐ ┌─▼──────┐ ┌─▼─────────┐ │ │ │ LLM │ │ Mesh │ │ Skill │ │Work- │ │ Context │ │ │ │Client│ │Client│ │Registry │ │space │ │ Manager │ │ │ │(mesh │ │(HTTP)│ │(builtins│ │Manager │ │(token │ │ │ │proxy)│ │ │ │+custom) │ │(/data/ │ │tracking, │ │ │ └──────┘ └──────┘ └─────────┘ │workspace│ │compact) │ │ │ └─────────┘ └───────────┘ │ └─────────────────────────────────────────────────────────────┘ ``` ## Task Mode Accepts a `TaskAssignment` from another agent or the operator. Runs a bounded loop (max 20 iterations, clamp 1–100 via `OPENLEGION_MAX_ITERATIONS`) of **decide -> act -> learn**. Returns a `TaskResult` with structured output and optional blackboard promotions. Task mode is used when an agent is given a specific objective with expected output — typically via the [coordination tool's](/features/coordination) `hand_off`. ## Chat Mode Accepts a user message. On the first message, loads workspace context — **SOUL.md, INSTRUCTIONS.md, USER.md, MEMORY.md, HEARTBEAT.md, INTERFACE.md, AGENTS.md**, plus read-only **PROJECT.md** and **SYSTEM.md** — into the system prompt (total bootstrap injection cap **48K chars**), injects a live Runtime Context block (permissions, budget, fleet, cron), and searches memory for relevant facts. Per-turn cap: `CHAT_MAX_TOOL_ROUNDS=30` (clamp 1–200). Session-total cap: `CHAT_MAX_TOTAL_ROUNDS=200` (clamp 1–1000). Continuation prompts after a clean stop: `_MAX_SESSION_CONTINUES=5`. Chat mode is used for interactive conversations via CLI, Telegram, Discord, Slack, WhatsApp, or Webhook channels. ## Heartbeat Mode When a cron with `heartbeat=true` fires, the agent runs at most `HEARTBEAT_MAX_ITERATIONS=12` iterations against an enriched context: HEARTBEAT.md rules, recent daily logs, probe alerts, and pending signal/task content. If the dispatch satisfies the **skip-LLM optimization**, the LLM is never called. See [Triggering & Automation](/features/triggering). ## Self-Extending Skills Agents can write their own Python skills at runtime using the `create_skill` tool and hot-reload them via `reload_skills`. Candidates run through an AST validator with 23 forbidden imports, 16 forbidden calls, and 11 forbidden attribute accesses (size cap 10,000 chars). ```python theme={null} @skill( name="your_tool", description="What this does and when to use it", parameters={ "param1": {"type": "string", "description": "What this param is for"}, }, ) async def your_tool(param1: str, *, mesh_client=None) -> dict: return {"result": "value"} ``` Custom skills are Python functions decorated with `@skill`, auto-discovered from the agent's `skills_dir` at startup. ## Self-Improving via Learnings Agents track tool failures in `learnings/errors.md` and user corrections in `learnings/corrections.md`. These are automatically injected into the system prompt each session, so agents avoid repeating past mistakes. ## Tool Loop Detection Both task and chat modes include automatic detection of stuck tool-call loops. A sliding window tracks recent `(tool_name, params_hash, result_hash)` tuples and escalates through three levels: | Level | Trigger | Action | | ------------- | ---------- | ------------------------------------------ | | **Warn** | 2nd repeat | System message: "Try a different approach" | | **Block** | 4th repeat | Tool call skipped, error returned to agent | | **Terminate** | 9th repeat | Loop terminated with failure status | Memory retrieval tools (`memory_search`) are exempt — repeated searches are legitimate. ## Spawning Other Agents Agents have two paths to create helpers: * **`spawn_fleet_agent`** (from `skill_tool`) — creates a fully isolated container agent through the mesh host. Requires `can_spawn=true`, which **the operator cannot grant**. Useful for tasks that need their own tools, memory, and security boundary. * **`subagent_tool`** (`spawn` / `list` / `wait`) — creates a lightweight in-process subagent. Faster startup but shares the parent's process. **Subagent limits:** `MAX_CONCURRENT=3` per parent, `MAX_DEPTH=2` (parent → subagent → sub-subagent, then stop), default TTL 300s (max 600s), `DEFAULT_MAX_ITERATIONS=10`. **Subagents cannot recurse beyond depth 2, cannot create skills, and cannot run browser tasks concurrently** (the browser tool holds module-level per-agent state). ## Workspace Files Agents persist state at `/data/workspace/`. Caps and purpose are documented in detail in [Memory System](/features/memory) — the scaffold set is **SOUL.md** (4K), **INSTRUCTIONS.md** (12K), **USER.md** (4K), **MEMORY.md** (16K), **HEARTBEAT.md** (uncapped), **INTERFACE.md** (4K), with **AGENTS.md** (12K) at the engine root. **PROJECT.md** and **SYSTEM.md** are read-only bootstrap inclusions (SYSTEM.md 6K, auto-generated, refreshed every 5 min). # Browser Automation Source: https://docs.openlegion.ai/features/browser Per-agent stealth Firefox, 24 browser actions, CAPTCHA solving, sessions, and fingerprint burn OpenLegion ships a per-agent stealth browser stack: a separate **browser service container** on `:8500` hosts one **Camoufox** (stealth Firefox fork) instance per agent, lazy-spawned on Xvnc displays `:100..:163` (64 slots) paired with KasmVNC ports 6100..6163. Agents drive the browser through `browser_tool` over HTTP. ## 24 browser actions `browser_tool` exposes 24 `@skill` tools. All actions are `parallel_safe=False` per agent — the browser tool holds module-level per-agent state, so a single agent's actions are serialized. | Category | Actions | | --------------- | --------------------------------------------------------------------------------------------------------------- | | Navigation | `navigate`, `warmup`, `go_back`, `go_forward`, `reset`, `wait_for` | | Inspection | `get_elements` (a11y snapshot, 200-element cap, iframe nesting cap 3), `screenshot` (WebP default), `find_text` | | Interaction | `click`, `click_xy`, `type`, `hover`, `scroll`, `press_key`, `fill_form` (max 50 fields) | | Tabs | `open_tab`, `switch_tab` | | Network | `inspect_requests` (200-event buffer) | | Files | `upload_file` (max 5 files, 50MB each), `download` | | CAPTCHA / Login | `detect_captcha`, `solve_captcha`, `request_captcha_help`, `request_browser_login` | Permissions are gated by `can_use_browser` and an optional `browser_actions` allowlist (`None`/`["*"]` = all known actions, `[]` = deny, specific list = allowlist of these names). ## CAPTCHA solving CAPTCHA solving is integrated into the browser tool: * **Providers**: 2captcha and capsolver (configured via env-only flags — these credentials bypass the standard vault). * **Supported types**: reCAPTCHA v2/v3/enterprise, hCaptcha, Cloudflare Turnstile + interstitial, PerimeterX press-hold, DataDome, JavaScript challenges. * **Behavioral CAPTCHAs** (drag puzzles, image selection) are rejected — they must route to `request_captcha_help` for human-in-the-loop solving. * **Costs** are tracked in **millicents (1/100,000 USD)** in `data/captcha_costs.json`. See [Cost Tracking](/features/cost-tracking). * **Rate limit**: 20/hour per agent (default). * **Caps**: per-agent + per-tenant monthly USD caps with 50% / 80% / 100% alerts. * **Kill switch**: `CAPTCHA_DISABLED` halts all solving fleet-wide. * **Provider circuit breaker**: 3 failures in 5 minutes opens the breaker for 10 minutes. ## Session persistence **Opt-in.** Disabled by default — set `BROWSER_SESSION_PERSISTENCE_ENABLED=true` to enable. When on, the browser service snapshots cookies, storage, and IndexedDB on an interval (default 300s, range 60–3600s). On agent restart the session resumes from the latest snapshot. ## Device profiles Four built-in profiles: * `desktop-windows` (default) * `desktop-macos` * `mobile-ios` * `mobile-android` **Mobile profiles spoof the User-Agent string only.** The underlying Camoufox Firefox engine is unchanged — TLS/JA3 fingerprints remain desktop, and Firefox does not send `Sec-CH-UA-*` Client Hints. Sites that fingerprint at the TLS layer can still detect a desktop client. ## Fingerprint burn detection The browser tool maintains a rolling window of the last 10 navigation outcomes. If **≥50% of recent navigations get rejected** (challenge pages, bans, anti-bot redirects), the per-agent flag `fingerprint_burn=True` is set. **There is no automatic rotation.** The operator must explicitly rotate the device profile and reset the session. ## Canary `canary-probe` is a reserved agent ID that, when enabled (`BROWSER_CANARY_ENABLED=true`), sweeps test surfaces for fingerprint detectability. Useful as an early-warning signal for fingerprint burn across the fleet. ## Operator kill switches The operator can flip any of these env-driven flags to halt risky browser surfaces fleet-wide: | Flag | Effect | | ---------------------------------- | ------------------------- | | `BROWSER_DOWNLOADS_DISABLED` | Block `download` action | | `BROWSER_NETWORK_INSPECT_DISABLED` | Block `inspect_requests` | | `BROWSER_COOKIE_IMPORT_DISABLED` | Block cookie import flows | | `CAPTCHA_DISABLED` | Halt all CAPTCHA solving | ## Browser service container Resources scale with `OPENLEGION_MAX_AGENTS`: | Tier | Max agents | RAM | Shared mem | CPU | | ------- | ---------- | ----- | ---------- | --- | | Basic | ≤1 | 2 GB | 512 MB | 1.0 | | Growth | 2–5 | 4 GB | 1 GB | 1.5 | | Pro | 6–15 | 8 GB | 2 GB | 2.0 | | Pro Max | >15 | 16 GB | 4 GB | 4.0 | `OPENLEGION_MAX_AGENTS=0` (the default "unlimited") falls into the **Basic** tier. If you intend to run many agents, set the variable explicitly. The container has its own iptables egress filter: REJECTs RFC1918, loopback (except in-container), link-local, CGNAT, and IANA-reserved IPv4+IPv6. Fail-closed. See [Security](/concepts/security). ## Live browser viewer Every browser session has a KasmVNC view at port `6100 + display_offset`. The dashboard proxies these through `/agent-vnc/{agent_id}/{path}` — the proxy rejects agent Bearer tokens and requires `ol_session` on HTTP and WebSocket, so an agent's credentials can't leak through the browser viewer. # Channels Source: https://docs.openlegion.ai/features/channels Connect agents to Telegram, Discord, Slack, WhatsApp, Webhook, and CLI Chat with your agent fleet via **CLI REPL**, **Telegram**, **Discord**, **Slack**, **WhatsApp**, or **Webhook**. Channels provide a unified interface for interacting with agents from any platform. ## Activation: token-presence, not a flag Channels auto-start as soon as the mesh resolves a usable token. There is no separate `enabled: true` flag — drop a token in and the channel comes up on next `openlegion start`. The mesh looks for each channel's bot token in this order: 1. `OPENLEGION_SYSTEM_` (mesh-tier env) 2. `OPENLEGION_CRED_` (agent-tier env) 3. Bare env (e.g., `TELEGRAM_BOT_TOKEN`) 4. `mesh.yaml` `channels..bot_token` ## Pairing On startup, each active channel prints a one-time pairing code to the console. Send `/start ` from your account in that channel to claim the channel — the first user to pair becomes the channel owner. After pairing, the channel persists the owner mapping (e.g., `config/telegram_paired.json` for Telegram). ## Supported Channels | Channel | Auth | Notes | | ------------ | ----------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **CLI REPL** | Built-in | Default channel. Full slash command set (see [CLI Reference](/reference/cli)). | | **Telegram** | `OPENLEGION_CRED_TELEGRAM_BOT_TOKEN` | Bot API. **4000-char chunks**. Optional `allowed_users` list. | | **Discord** | `OPENLEGION_CRED_DISCORD_BOT_TOKEN` | **1900-char chunks**. Native slash commands + `!` prefix fallback. **`/addkey` is intentionally NOT a slash command** — slash-command arguments are visible in the channel transcript, so `!addkey` (bang-prefix) is used instead. Requires Message Content Intent + `bot` and `applications.commands` OAuth scopes. | | **Slack** | `OPENLEGION_CRED_SLACK_BOT_TOKEN` (`xoxb-`) **and** `OPENLEGION_CRED_SLACK_APP_TOKEN` (`xapp-`) | Socket Mode only (via `slack-bolt`). No public URL needed. **3000-char chunks**. Per-user routing keyed on `user_id:thread_ts`. | | **WhatsApp** | `OPENLEGION_CRED_WHATSAPP_ACCESS_TOKEN` + `_PHONE_NUMBER_ID` | Cloud API on Graph v21.0. **Text only — non-text content (images, audio, documents) is dropped silently.** See caveats below. | | **Webhook** | Dashboard-created hook | `POST /webhook/hook/`. Body cap 1MB; payload truncated to 3000 chars on dispatch. Optional HMAC-SHA256 via `x-webhook-signature`. **Creation is dashboard-only** — there is no `/mesh/webhooks` endpoint. | ## Channel Configuration API tokens are stored as environment variables: ```bash theme={null} # Telegram OPENLEGION_CRED_TELEGRAM_BOT_TOKEN=123456:ABC... # Discord OPENLEGION_CRED_DISCORD_BOT_TOKEN=MTIz... # Slack (both required) OPENLEGION_CRED_SLACK_BOT_TOKEN=xoxb-... OPENLEGION_CRED_SLACK_APP_TOKEN=xapp-... # WhatsApp OPENLEGION_CRED_WHATSAPP_ACCESS_TOKEN=EAAx... OPENLEGION_CRED_WHATSAPP_PHONE_NUMBER_ID=1234... WHATSAPP_APP_SECRET=... # REQUIRED in production OPENLEGION_SYSTEM_WHATSAPP_VERIFY_TOKEN=... # strongly recommended ``` Per-channel options (default agent, allowed users, etc.) live under `channels.` in `config/mesh.yaml`: ```yaml theme={null} channels: telegram: default_agent: assistant allowed_users: [12345678] discord: default_agent: assistant ``` ## WhatsApp caveats WhatsApp is the channel with the most operational gotchas: * **Graph v21.0** Cloud API only. * **Text-only.** Inbound non-text messages (images, audio, documents, stickers, locations) are dropped — the agent never sees them. * **`WHATSAPP_APP_SECRET` is required in production.** The webhook handler verifies inbound payloads with HMAC-SHA256. **If `MESH_AUTH_TOKEN` is set and `WHATSAPP_APP_SECRET` is missing, the channel raises `RuntimeError` at startup** — it will not silently run unauthenticated. * The verify token **regenerates on every restart** unless you set `OPENLEGION_SYSTEM_WHATSAPP_VERIFY_TOKEN` explicitly. Set it so your Meta-side webhook config stays stable. ## Channel Commands These commands work across all channels (CLI commands are documented in the [CLI Reference](/reference/cli)): | Command | Description | | --------------------- | ------------------------------------------------------------------- | | `@agent ` | Send message to a specific agent | | `/use ` | Switch your active agent for this channel/user | | `/status` | Show fleet status | | `/costs` | Show today's LLM spend | | `/reset` | Clear conversation with active agent | | `/broadcast ` | Send message to all agents (project-scoped in dashboard) | | `/steer ` | Inject a message into a busy agent's context (rate-limited) | | `/addkey [key]` | Add an API credential to the vault (Discord: bang-prefix `!addkey`) | | `/help` | Show available commands | | `/quit`, `/exit` | (CLI only) leave the REPL | CLI-only commands (not available in messaging channels): `/add`, `/agent`, `/remove`, `/restart`, `/history`, `/blackboard`, `/queue`, `/cron`, `/project`, `/credential`, `/removekey`, `/logs`, `/traces` (alias `/debug`). ## Per-channel routing semantics Routing semantics differ slightly per channel: * **Telegram / Discord**: per-user. Each user's messages route to the agent they've `/use`-d. * **Slack**: composite `user_id:thread_ts`. The same user gets a separate active-agent per thread. * **WhatsApp**: per-user, keyed on phone number. * **CLI**: single-user (the REPL session itself). # Coordination Source: https://docs.openlegion.ai/features/coordination How agents collaborate without a CEO agent — blackboard, pub/sub, lanes, and handoffs OpenLegion is a **fleet model — blackboard + pub/sub + handoff (no CEO agent)**. There is no LLM in the middle deciding what runs next. Agents coordinate through four explicit primitives, all running on the mesh host: * **Blackboard** — durable shared state in SQLite WAL. * **PubSub** — fan-out events. * **Lanes** — per-agent FIFO queues. * **Handoffs** — durable task records routed through `MessageRouter`. Templates assemble agents into ready-made rosters. There are no DAG YAML files and no deterministic orchestrator. ## Blackboard A SQLite WAL key-value store with **atomic CAS via `write_if_version`** and an audit log that supports undo and archive. Keys are auto-namespaced under `projects/{name}/` so fleets in different projects can't collide. Tools on `mesh_tool`: | Tool | Purpose | | ------------------ | --------------------------------------------- | | `write_blackboard` | CAS-aware write (`version` arg) | | `read_blackboard` | Read a key (returns value + version) | | `list_blackboard` | List keys under a glob | | `watch_blackboard` | Long-poll for changes | | `claim_task` | CAS-based claim — used internally by handoffs | | `save_artifact` | Promote a tool result to a durable artifact | Typical namespaces: `tasks/*` (handoffs), `context/*` (shared context), `signals/*` (one-shot signals), `history/*` (append-only audit log). ## Pub/Sub Fan-out topic bus. Both publishing and subscribing are permission-gated by `can_publish` / `can_subscribe`. | Tool | Purpose | | ----------------- | ---------------------------- | | `publish_event` | Publish a payload to a topic | | `subscribe_event` | Long-poll the topic queue | Use pub/sub for fire-and-forget signals ("research is done", "new lead came in") rather than work assignment — for work, use handoffs. ## Lanes Each agent has a per-agent FIFO lane on the mesh. Messages routed into a lane wake the agent up; results are forwarded with a `MessageOrigin` carrying `kind` / `channel` / `user` so the reply can flow back to the requester. Lanes support three modes: * **`followup`** (default) — append and wake. * **`steer`** — inject into a busy agent's loop. Rate-limited to **10 wakeups / 3600s** to avoid thrashing. * **`collect`** — batch messages while the agent is busy; deliver the whole batch when it goes idle. ## Handoffs (`coordination_tool`) Structured task assignment between agents. All four functions are part of the `coordination_tool`: | Tool | Purpose | | --------------- | ---------------------------------------------------------------------------- | | `hand_off` | Send a task to another agent (or `capability:`). TTL **86400s** (24h). | | `check_inbox` | List inbound handoffs | | `update_status` | Update task status (`working`, `blocked`, `done`) | | `complete_task` | Mark a task complete and deliver the result | `MessageRouter` resolves the target — either a literal agent ID or `capability:` (routes to whichever agent declares that capability). Cross-project routing is blocked when `OPENLEGION_PROJECT_SCOPE_MODE=enforce`. ### V1 vs V2 task records Handoffs run in one of two backends: * **V1** — handoff is stored as a blackboard entry at `tasks/{agent}/{handoff_id}`. Lightweight, no durable record outside the blackboard. * **V2** — durable task records in a separate table; surface via the `openlegion tasks` CLI command. Enabled when `OPENLEGION_ORCHESTRATION_TASKS_V2=1` — **default ON in v0.1.0**. V2 is the recommended mode. Set the env var to `0` only if you specifically need the old blackboard-backed behavior. ## Fleet Templates Templates are agent rosters — YAML manifests in `src/templates/` that the operator can apply to materialize a fleet. There are **13 templates** in the engine today: | Template | Composition | | -------------------- | ---------------------------------------------------------------------- | | `starter` | Single general-purpose assistant | | `content` | researcher + writer (blog / social / email from briefs) | | `deep-research` | scout + analyst (multi-source synthesis with citations) | | `devteam` | PM, engineer, reviewer | | `monitor` | watcher + analyst (always-on) | | `sales` | researcher, qualifier, outreach | | `competitive-intel` | competitor pricing/product tracking | | `lead-enrichment` | lead list research | | `price-intelligence` | crawler + analyst with anti-bot browser | | `review-ops` | G2 / Trustpilot / Capterra / App Store / Google reviews + reply drafts | | `social-listening` | Reddit / HN / X competitor pain-point monitor | | `research` | general-purpose researcher | | `opportunity-finder` | gap-scout + evaluator + modeler | Templates use a `"{default_model}"` placeholder which is substituted at apply time. ### `apply_template` is per-slot, not atomic `fleet_tool` (operator-only) exposes `list_templates` and `apply_template`. `apply_template` accepts `agent_overrides` per-slot — `model`, `instructions` (≤12K), `soul` (≤4K), `heartbeat`, `interface` (≤4K). The `role` is template-fixed and cannot be overridden. **Important: `apply_template` is per-slot and NOT atomic.** If the template has 4 slots and slot 3 fails, agents from slots 1 and 2 stay created. You must clean up partially-applied fleets yourself. ## MessageOrigin Every message — chat input from a channel, a handoff, a lane delivery — carries a `MessageOrigin` Pydantic model with `kind` / `channel` / `user`. Results flow back along the same path: a Telegram message routed to a researcher and then handed off to a writer eventually returns to the original Telegram user, with no extra plumbing required from the agent code. ## Coordination at a Glance | Need | Use | | ----------------------------------------- | ------------------------------------------- | | Durable shared state | Blackboard (`write_blackboard` with CAS) | | One-shot broadcast | Pub/Sub (`publish_event`) | | Append-and-wake another agent | Lane via `followup` | | Interrupt a busy agent | Lane via `steer` (rate-limited) | | Hand off a task with a result expectation | `hand_off` (V2 durable record) | | Spin up a multi-agent fleet | `fleet_tool.apply_template` (operator-only) | # Cost Tracking & Budgets Source: https://docs.openlegion.ai/features/cost-tracking Per-agent and per-project LLM spend tracking with budget enforcement Every LLM call is proxied through the mesh; usage is recorded in a SQLite WAL cost ledger at `data/costs.db`. Costs are computed via `estimate_cost()` against LiteLLM's model registry, then enforced against per-agent budgets. ## How It Works 1. Agent makes an LLM call through the mesh proxy. 2. The mesh checks the agent's remaining budget **before** forwarding to the provider. 3. If the agent has exceeded its daily or monthly budget, the call is rejected. 4. After a successful call, token usage and dollar cost are recorded in `data/costs.db`. Because agent containers hold no provider keys and all calls proxy through the mesh, an agent cannot bypass the budget check. ## Configuring Budgets Set per-agent budgets in `config/agents.yaml`: ```yaml theme={null} agents: researcher: budget: daily_usd: 5.00 monthly_usd: 100.00 ``` **Defaults:** $10/day, $200/month per agent. **Allowed range:** daily $0.01–$1000, monthly $0.10–$30000. Values outside this range are rejected at config-load time. When an agent exceeds its daily or monthly budget, subsequent LLM calls are rejected until the budget resets. ## Per-project rollups Costs are tracked **per-agent and per-project**. The cost ledger keys on both, so you can see what each project is spending in aggregate across all its agents. Per-project rollups surface in the dashboard at **Settings → Costs**. ## Viewing Costs From the interactive REPL: ``` /costs ``` Shows per-agent spend for today, this week, and this month, including token counts and dollar amounts. In the dashboard, **Settings → Costs** shows per-agent and per-project breakdowns with a period selector and per-agent budget bars. ## CAPTCHA Costs CAPTCHA solver spend is tracked **separately** from LLM cost, in **millicents (1/100,000 USD)**, in `data/captcha_costs.json` (`chmod 0o600`). Both per-agent and per-tenant monthly USD caps apply, with alerts at **50% / 80% / 100%** of the cap. The fleet-wide kill switch `CAPTCHA_DISABLED` stops all CAPTCHA solving regardless of budget. ## Model Failover and Costs When using [failover chains](/concepts/architecture#model-failover), cost tracking follows the call to whichever model actually handles it. If the primary model fails and the request cascades to a cheaper fallback, only the fallback cost is recorded against the agent's budget. # Dashboard Source: https://docs.openlegion.ai/features/dashboard Real-time web UI for monitoring and managing your agent fleet This page documents the **engine dashboard** — the Alpine.js SPA served by the mesh host on `:8420`. For the managed-hosting account dashboard (Next.js, Vercel), see the Managed Hosting docs. OpenLegion includes a built-in web dashboard for real-time monitoring, debugging, and management of your agent fleet. The dashboard is served by the mesh host at `http://localhost:8420/`. ## Top-nav tabs There are **four top-level tabs**. Internal IDs are frozen for URL stability; user-facing labels diverged: | Internal ID | Label | Purpose | | ----------- | ------------ | ------------------------------------------------------------------------------------------------------ | | `chat` | **Chat** | Multi-agent chat surface streamed via the chat endpoint | | `workplace` | **Work** | Kanban board, Needs-You panel, activity feed, recently delivered | | `fleet` | **Team** | Agent grid — health, model, token usage, cost. Click for detail view + workspace editor + embedded VNC | | `system` | **Settings** | 11 sub-tabs (see below) | The Work tab's Kanban surface is sometimes described as a "Board" in prose. That's a description, not a tab label — the tab is **Work**. ### Chat Multi-agent chat. Talk to one agent or fan a message out to several at once. Token-level streaming via the chat endpoint; events bypass the WS event bus for low-latency delivery. ### Work Operational workspace. Surfaces include: * **Kanban** — Pending / Working / Blocked / Done columns. * **Needs-You** panel — pending operator actions (soft/hard edits awaiting confirm). * **Activity feed** with pinned blockers. * **Recently delivered** — inline previews of recent agent outputs. ### Team Agent grid showing health, current model, token usage, and cost — organized by project. Click an agent to open a detail view with workspace editor and embedded VNC viewer for live browser sessions. Slide-over chat panels let you chat with any agent directly via SSE streaming. ### Settings (System) — 11 sub-tabs The Settings tab is the operational control surface. It has 11 sub-tabs: | Sub-tab | Purpose | | ---------------- | ------------------------------------------------------------------ | | **Activity** | Traces, live events, blackboard browser | | **Costs** | Per-agent + per-project cost breakdowns with period selector | | **Automation** | Cron jobs, heartbeats, file watchers | | **Integrations** | Webhooks, channel pairing, MCP servers | | **API Keys** | Engine API keys (salted SHA-256 hashes) for programmatic access | | **Wallet** | Wallet addresses, balances, spend limits (per chain) | | **Network** | Egress allowlist, browser proxy config, no-proxy list | | **Storage** | Data volumes, project archives, exports | | **Operator** | Operator agent settings — system prompts, instruction overrides | | **Browser** | Browser service flags (device profile, locale, UA, CAPTCHA solver) | | **Settings** | Misc runtime overrides, model defaults | #### Activity sub-tab specifics * **Traces** — grouped request traces showing the full lifecycle of each agent interaction. LLM prompt/response previews surfaced inline for quick debugging. * **Live Events** — WebSocket feed at `/ws/events`. `DashboardEvent.type` is a Literal of **50 event names** (task assignments, pub/sub fan-outs, blackboard updates, cron fires, channel messages, browser events, etc.). The event bus is a **500-event ring buffer**. Per-token `text_delta` events bypass the WS bus and are delivered via the streaming chat endpoint instead. * **Blackboard** — browse, search, write, and delete entries in the shared blackboard. See current inter-agent shared data. ## Real-time updates * WebSocket: `/ws/events` (500-event ring buffer; 50 typed event names). * SSE: per-agent chat streams. * Token-level streaming bypasses the WS bus for low latency. ## Live browser viewers Click any agent with browser activity to see a live VNC view. The dashboard proxies through `/agent-vnc/{agent_id}/{path}`. The proxy **rejects agent Bearer tokens** and **requires the `ol_session` cookie on both HTTP and WebSocket upgrade** — agent credentials can't leak through a browser session. ## Accessing the Dashboard The dashboard starts automatically with `openlegion start`. No additional configuration needed on a self-hosted install. ``` http://localhost:8420/ ``` **Authentication:** * **Dev / self-hosted** — open by default if `/opt/openlegion/.access_token` is absent. * **Managed hosting** — gated by an `ol_session` cookie (HMAC-verified, 24h max age + 5-min skew). The SSO callback `/__auth/callback` lives in the upstream Caddy auth-gate sidecar, **not** in engine code — the engine only consumes the cookie. **CSRF:** state-changing dashboard endpoints require the `X-Requested-With` header. If you're calling dashboard APIs programmatically, set it. ## Tech stack Alpine.js SPA + Tailwind CSS via CDN — **no build step**. Jinja templates with `autoescape=True` (the primary XSS defense, since the CSP allows `unsafe-inline`). Real-time updates over WebSocket and SSE. # MCP Tool Support Source: https://docs.openlegion.ai/features/mcp Plug in Model Context Protocol servers over stdio OpenLegion supports the **[Model Context Protocol (MCP)](https://modelcontextprotocol.io)** — the emerging standard for LLM tool interoperability — over **stdio transport**. **stdio transport only.** HTTP and SSE transports are not currently wired up in OpenLegion. MCP servers that require those transports won't work. **Default agent image is Python-only.** The Python `mcp` SDK is pre-installed, so Python-based MCP servers work out of the box. **Node.js is NOT installed.** npm-based servers (`@modelcontextprotocol/server-filesystem`, `-github`, `-playwright`, etc.) require a custom agent Dockerfile that installs Node.js. ## Configuration MCP servers are configured **per-agent**. Add `mcp_servers` to any agent definition: ```yaml theme={null} agents: researcher: role: "research" model: "anthropic/claude-haiku-4" mcp_servers: - name: sqlite command: python args: ["-m", "mcp_server_sqlite", "--db", "/data/research.db"] - name: fetch command: python args: ["-m", "mcp_server_fetch"] ``` Each server is launched as a subprocess inside the agent container using stdio transport. Tools are discovered automatically via the MCP protocol and appear in the LLM's tool list alongside built-in skills. **Per-call timeout: 60s.** A handshake or tool call that exceeds 60s raises an error. Built-in skills are unaffected. If one MCP server fails to start, other servers continue normally. Built-in skills are always available regardless of MCP server status. ## How It Works 1. Agent container reads `MCP_SERVERS` from environment (set by the runtime). 2. `MCPClient` launches each server subprocess via stdio transport. 3. MCP protocol handshake discovers available tools and their schemas. 4. Tools are registered in `SkillRegistry` with OpenAI function-calling format. 5. LLM tool calls route through `MCPClient.call_tool()` to the correct server. 6. If an MCP tool's name collides with a built-in skill or another MCP server's tool, the colliding tool is registered as **`mcp_{server_name}_{tool_name}`** at agent boot. Built-in skills always keep the original name. ## Known limitations * **stdio only** — HTTP and SSE transports not wired. * **Image and binary content silently dropped.** Only text blocks from `CallToolResult` are forwarded to the agent (concatenated under the `"result"` key). MCP servers that return images won't surface those to the LLM. * **60s per-call timeout.** * **Python-only default image** — see warning above. ## Server Config Options | Field | Type | Description | | --------- | ------ | ---------------------------------------------------------- | | `name` | string | Server identifier (used for logging and conflict prefixes) | | `command` | string | Command to launch the server | | `args` | list | Command-line arguments (optional) | | `env` | dict | Environment variables for the server process (optional) | ## Example: SQLite (Python) ```yaml theme={null} mcp_servers: - name: db command: python args: ["-m", "mcp_server_sqlite", "--db", "/data/mydb.sqlite"] ``` The agent can now query the database — the MCP server translates tool calls into SQL. ## Example: filesystem (requires custom image) The `@modelcontextprotocol/server-filesystem` reference server is npm-based. To use it, build a custom agent image that installs Node.js on top of the default `openlegion-agent:latest`: ```dockerfile theme={null} # Dockerfile.agent-with-node FROM openlegion-agent:latest USER root RUN apt-get update && apt-get install -y nodejs npm \ && rm -rf /var/lib/apt/lists/* USER agent ``` Then point your agent at it and reference the npm package: ```yaml theme={null} agents: archivist: image: "openlegion-agent-with-node:latest" mcp_servers: - name: fs command: npx args: ["-y", "@modelcontextprotocol/server-filesystem", "/data/workspace"] ``` If you're staying on the stock image, prefer a Python-based filesystem server or use the built-in `file_tool`. # Memory System Source: https://docs.openlegion.ai/features/memory Five layers of persistent, self-improving memory Five layers give agents persistent, self-improving memory across sessions. ## Memory Layers ``` Layer 5: Context Manager <- Manages the LLM's context window | Monitors token usage (tiktoken for OpenAI, 3.5 chars/token Anthropic, | 4 chars/token fallback) | Proactive flush facts at 60% capacity | Auto-compact summarize at 70% capacity | Warning at 80% capacity | Extracts facts before discarding messages | Layer 4: Learnings <- Self-improvement through failure tracking | learnings/errors.md (tool failures with context) | learnings/corrections.md (user corrections and preferences) | Auto-injected into system prompt each session | Layer 3: Workspace Files <- Durable, human-readable storage | SOUL.md (4K cap) (personality + behavioral instructions) | INSTRUCTIONS.md (12K cap) (loaded into system prompt) | USER.md (4K cap) (user preferences and context) | MEMORY.md (16K cap) (curated long-term facts) | INTERFACE.md (4K cap) (cross-agent interface contract) | AGENTS.md (12K cap) (engine-root agent descriptions) | HEARTBEAT.md (uncapped) (autonomous monitoring rules) | PROJECT.md (read-only) (optional project context, bootstrap-only) | SYSTEM.md (6K cap, read-only, auto-generated, 5-min refresh) | memory/YYYY-MM-DD.md (daily session logs) | Total bootstrap injection cap: 48K chars | BM25 search (k1=1.5, b=0.75) across markdown files | Layer 2: Structured Memory DB <- Hybrid vector + keyword | SQLite + sqlite-vec + FTS5 | Facts with embeddings — text-embedding-3-small (1536 dims) | Auto-categorization with category-scoped search | 3-tier retrieval: categories -> scoped facts -> flat fallback | Hybrid scoring: (0.7 * vector + 0.3 * keyword) * decay_score | Layer 1: Salience Tracking <- Prioritizes important facts SALIENCE_DECAY_RATE=0.95 Access-count boost capped at 10.0 High-salience facts auto-surface in initial context ``` ## Embedding & Vector Search The structured memory store uses **OpenAI `text-embedding-3-small` (1536 dimensions)** for vector search. Two important behaviors: * **Non-OpenAI providers degrade to keyword-only.** If no OpenAI key is configured the embedding provider defaults to `"none"` and the store falls back to FTS5 keyword search. * **Auto-disable on consecutive failures.** After **3 consecutive embedding failures** the store silently disables vectors for the process lifetime (keyword search continues). Restart to retry. ## Write-Then-Compact Pattern Before the context manager discards messages, it: 1. Asks the LLM to extract important facts from the conversation 2. Stores facts in both `MEMORY.md` and the structured memory DB 3. Summarizes the conversation 4. Replaces message history with: summary + last 4 messages Nothing is permanently lost during compaction. ## Cross-Session Memory Facts saved with `memory_save` are stored in both the workspace (daily log) and the structured SQLite database. After a reset or restart, `memory_search` retrieves them via hybrid search: ``` Session 1: User says "My cat's name is Whiskerino" Agent saves to daily log + structured DB === Chat Reset === Session 2: User asks "What is my cat's name?" Agent recalls "Whiskerino" via memory_search ``` ## Memory Tools | Tool | Purpose | | --------------- | ----------------------------------------------------------------------------- | | `memory_search` | Hybrid search across workspace files (BM25) and structured DB (vector + FTS5) | | `memory_save` | Save fact to daily log + structured memory DB | ## Workspace Files Each agent has a persistent workspace at `/data/workspace/`. The scaffold set (`_SCAFFOLD_FILES`) is six files: SOUL, INSTRUCTIONS, USER, MEMORY, INTERFACE, HEARTBEAT. AGENTS.md is symlinked from the engine root. | File | Cap | Purpose | | -------------------------- | -------------- | ------------------------------------------------------------------------------- | | `SOUL.md` | 4K | Agent personality and behavioral instructions | | `INSTRUCTIONS.md` | 12K | Operator-edited fleet instructions — loaded into system prompt | | `USER.md` | 4K | User preferences and context | | `MEMORY.md` | 16K | Curated long-term facts | | `INTERFACE.md` | 4K | Cross-agent interface contract (capabilities, calling conventions) | | `AGENTS.md` | 12K | Engine-root agent descriptions (CLAUDE.md symlink) | | `HEARTBEAT.md` | uncapped | Autonomous monitoring rules | | `PROJECT.md` | read-only | Optional project context (bootstrap-only) | | `SYSTEM.md` | 6K (read-only) | Auto-generated architecture guide + runtime snapshot, refreshed every 5 minutes | | `memory/YYYY-MM-DD.md` | — | Daily session logs | | `learnings/errors.md` | — | Tool-failure history | | `learnings/corrections.md` | — | User-correction history | Direct writes to SOUL / INSTRUCTIONS / USER / MEMORY / INTERFACE / HEARTBEAT / AGENTS are **blocked** — agents must go through the `update_workspace` tool, which enforces caps and emits HTTP 413 when exceeded. Workspace bootstrap injection is capped at **48K total chars** across all files. # The Operator Source: https://docs.openlegion.ai/features/operator The reserved agent that manages your fleet on your behalf `operator` is a reserved agent ID auto-created at startup. In managed hosting it is your primary chat partner — the agent you talk to when you say "spin up a new researcher", "what's my fleet doing today", or "apply the sales template". ## What makes the operator different | | Worker agent | Operator | | ------------------- | -------------------------------------- | --------------------------------------------------------------------------------------- | | Resources | 384 MB RAM / 0.15 CPU | **128 MB RAM / 0.05 CPU** | | Heartbeat | Configurable | **Force-locked to `every 15m`** | | Control-plane flags | Default `false` | Default `true` (manage fleet/projects/agents, view metrics, route tasks, request creds) | | Tool surface | Standard tools + per-agent permissions | Standard tools + operator-only tools (`fleet_tool`, `operator_tools`) | | Auto-created | No | Yes — at engine startup, reserved ID | ## Operator-only tools ### `fleet_tool` * `list_templates` — see the 13 [fleet templates](/features/coordination#fleet-templates). * `apply_template` — materialize a template (per-slot, **not atomic** — mid-loop failures leave partial fleets). ### `operator_tools` A broad control-plane surface: * **Inspection**: `inspect_agents`, `inspect_projects`, `list_pending`, `list_agent_queue`, `get_team_outputs`, `summarize_project_progress`. * **Project management**: `manage_project`, `create_project`, `add_agents_to_project`, `remove_agents_from_project`, `update_project_context`, `set_project_goal`. * **Agent management**: `manage_agent`, `create_agent`, `edit_agent` (soft/hard split with 5 min / 30 min TTL), `manage_task`. * **Edit lifecycle** (legacy): `propose_edit`, `confirm_edit`, `undo_change`, `cancel_pending_action`, `archive_audit_before`. * **Observation**: `save_observations` (operator-owned `OBSERVATIONS.md`). ## Soft vs hard edits Operator edits to other agents split by risk: | Class | Fields | TTL | Confirmation | | -------- | ------------------------------------------------------------------------------ | ------ | ----------------------------------------------------------- | | **Soft** | `instructions`, `soul`, `heartbeat`, `heartbeat_schedule`, `interface`, `role` | 5 min | Auto-applied with Undo window | | **Hard** | `model`, `permissions`, `budget`, `thinking` | 30 min | Requires `confirm_edit` (CLI: `openlegion confirm `) | Both soft and hard edits go through the **Pending actions** surface — visible in the dashboard's Needs-You panel and via `openlegion pending` on the CLI. ## Operator ceiling The operator can do a lot, but **cannot**: * Grant `can_spawn=true` to any agent (subagent/agent spawning requires explicit human approval outside chat). * Grant `can_use_wallet=true` to any agent (wallet access requires the same). These ceilings are enforced in the permission matrix — even if the operator's own permissions are maxed out, those two fields are off-limits. ## Where you'll see the operator * **CLI**: `openlegion start` opens the REPL with the operator selected by default. `/use operator` switches back. * **Channels**: Once paired, the operator is the default agent for new conversations. * **Dashboard**: The **Chat** tab targets the operator by default. **Settings → Operator** lets you edit its system prompts and instruction overrides. ## Reserved IDs `operator` is one of three reserved agent IDs (`RESERVED_AGENT_IDS = {"mesh", "operator", "canary-probe"}`). You can't create a worker agent named `operator`, and the operator can't be deleted. # Triggering & Automation Source: https://docs.openlegion.ai/features/triggering Cron, heartbeats, webhooks, and file watchers Agents act autonomously through trigger mechanisms running in the mesh host (not inside containers, so they survive container restarts). ## Cron Scheduler Persistent cron jobs that dispatch to agents on a schedule. Agents can schedule their own jobs using `set_cron` (subject to `can_manage_cron`); `list_cron` and `remove_cron` round out the cron surface in `mesh_tool`. Supported formats: * **5-field cron expressions**: `minute hour dom month dow` (e.g., `0 9 * * 1-5` for weekdays at 9am). **Minute granularity only — 6-field cron with seconds is NOT supported.** * **Interval shorthand**: `every Ns`, `every Nm`, `every Nh`, `every Nd`. State is persisted to `config/cron.json` and auto-managed. The scheduler ticks every **5 seconds** (`TICK_INTERVAL=5s`). Intervals smaller than 5s won't fire faster than that. ### Cron modes Every cron has one of three modes, set by which fields are present: * **Message mode** — dispatches a chat message to the agent. The agent runs an LLM turn to handle it. * **Tool mode** — fires `tool_name` with `tool_params` directly. **No LLM involvement**, useful for deterministic side-effects. * **Heartbeat mode** — runs the agent in heartbeat mode (`heartbeat=true`). See below. Updatable fields on an existing cron: `schedule`, `message`, `enabled`, `suppress_empty`, `tool_name`, `tool_params`. Other fields are immutable — remove and recreate. ## Heartbeat System Cost-efficient autonomous monitoring. A heartbeat cron runs built-in probes first — cheap, deterministic checks — and only burns LLM tokens when something is actionable. Built-in probes: * `disk_usage` (fires when disk usage > **85%**) * `pending_signals` (any unread signals) * `pending_tasks` (any open handoffs) Default schedule: **`every 15m`**. The operator's heartbeat is **force-locked to `every 15m`** and cannot be changed. When a heartbeat fires, the agent receives an enriched context: its `HEARTBEAT.md` rules, recent daily logs, probe alerts, and actual pending signal/task content — all in a single message. Iterations are capped at `HEARTBEAT_MAX_ITERATIONS=12`. ### Skip-LLM optimization The dispatcher will skip the LLM entirely when **all four** of these conditions hold: 1. Not a manual trigger (manual `/cron run` always dispatches). 2. `HEARTBEAT.md` is the default scaffold (unmodified). 3. No recent activity exists. 4. No probes triggered. When all four hold, the heartbeat returns immediately at zero LLM cost. Any single condition flips and the LLM is called. ## Webhook Endpoints Named webhook URLs dispatch payloads to agents: ```bash theme={null} curl -X POST http://localhost:8420/webhook/hook/hook_a1b2c3d4 \ -H "Content-Type: application/json" \ -d '{"event": "push", "repo": "myproject"}' ``` Notes: * **Webhook creation is dashboard-only.** There is no `/mesh/webhooks` endpoint — open the dashboard (**Settings → Integrations**) to create a hook and get its `hook_id`. * **Body cap 1 MB** (Content-Length pre-check + post-read check). Payload is **truncated to 3000 chars** when forwarded to the agent, and run through `sanitize_for_prompt()` first. * **Optional HMAC-SHA256** via `x-webhook-signature`. If a secret is configured on the hook, the dispatcher verifies via `hmac.compare_digest`. ## File Watchers Poll directories for new or modified files matching glob patterns. **Polling, not inotify** — required for Docker volume compatibility. ```yaml theme={null} # config/watchers.yaml watchers: - path: "/data/inbox" pattern: "*.csv" agent: "researcher" message: "New prospect list uploaded: {filename} at {filepath}. Begin research." ``` * Poll interval: **5s** (`POLL_INTERVAL=5s`, not user-configurable). * **First scan is silent.** Files present at watcher startup do **not** trigger the agent — only files added or modified after the watcher comes up. This avoids replaying every existing file on restart. # Wallet Source: https://docs.openlegion.ai/features/wallet Per-agent EVM and Solana keys with spend caps, rate limits, and one-time seed reveal OpenLegion's wallet system gives each agent its own deterministically-derived blockchain keypair on **5 EVM chains and 2 Solana networks**. Keys are derived from a single master mnemonic stored in the mesh-tier vault — agents never see the seed or raw private keys. ## Supported chains | Family | Networks | | ------ | ------------------------------------------------------------------------ | | EVM | `evm:ethereum`, `evm:base`, `evm:arbitrum`, `evm:polygon`, `evm:sepolia` | | Solana | `solana:mainnet`, `solana:devnet` | ## Per-agent key derivation * **EVM**: BIP-44 path `m/44'/60'/{agent_index}'/0/0` from the master seed. * **Solana**: HMAC-SHA512 over PBKDF2 of the seed, keyed by `agent_index`. * **Private keys never leave the mesh process.** The wallet tool calls into the mesh to sign — agents only see signed transactions and addresses. ## Operations `wallet_tool` exposes five functions (subject to `can_use_wallet` ACL): | Tool | Purpose | | --------------- | ------------------------------------------------- | | `get_address` | Get the agent's address for a given chain | | `get_balance` | Get native or token balance | | `read_contract` | View call (no signing, no spend) | | `transfer` | Native or ERC-20/SPL token transfer | | `execute` | Sign and submit an arbitrary protocol transaction | ## Master seed: one-time reveal The master mnemonic (24-word BIP-39) is generated once and stored in `OPENLEGION_SYSTEM_WALLET_MASTER_SEED`. You see it **exactly once**: * `POST /api/wallet/init` returns the seed with `Cache-Control: no-store`. * Every subsequent call to `GET /api/wallet/seed` returns **HTTP 410 Gone**. There is no second-chance reveal. * The CLI command `openlegion wallet init` shows the seed once on stdout. Back up the seed when you initialize. The engine intentionally does not let you re-read it. ## Spend limits and rate limits ACLs enforce four wallet-specific caps per agent: | Field | Default | Description | | ------------------------------- | ------------------------ | ------------------------------------------------------ | | `wallet_spend_limit_per_tx_usd` | `LIMIT_PER_TX_USD=10` | Max USD value per transaction | | `wallet_spend_limit_daily_usd` | `LIMIT_DAILY_USD=100` | Max USD per agent per UTC day | | `wallet_rate_limit_per_hour` | `RATE_LIMIT_PER_HOUR=10` | Max transactions per agent per hour | | `wallet_allowed_chains` | (per-agent allowlist) | Restrict which chains an agent can sign on | | `wallet_allowed_contracts` | (per-agent allowlist) | Restrict which contract addresses `execute` can target | USD values are estimated from oracle prices at submit time and counted against the daily cap. ## Operator ceiling The operator agent **cannot grant `can_use_wallet=true`**. Wallet access for an agent must be set explicitly by a human operator outside the chat surface — usually by editing `config/permissions.json` directly. ## CLI ```bash theme={null} # Initialize the master seed (shown ONCE) openlegion wallet init # Show addresses for an agent across all chains openlegion wallet show researcher ``` ## Dashboard The **Settings → Wallet** sub-tab lists every agent's addresses on every chain along with current balances and the per-agent caps configured in permissions. # OpenLegion Source: https://docs.openlegion.ai/index Container-isolated multi-agent runtime — managed or self-hosted ## What is OpenLegion? OpenLegion is a **container-isolated multi-agent runtime**. Every agent runs in its own Docker container with private memory, tools, schedule, and budget — coordinated through a SQLite-backed blackboard, pub/sub events, and a structured handoff protocol. Fleet model — no CEO agent. API keys never leave the mesh — agents proxy LLM calls through a credential vault and never see secrets. Chat with your fleet via **Telegram**, **Discord**, **Slack**, **WhatsApp**, **Webhook**, or the CLI REPL. Monitor everything from the engine dashboard (`Chat / Work / Team / Settings`). No LangChain. No Redis. No Kubernetes. No CEO agent. 100+ LLM providers via LiteLLM. ## Get started Sign up at app.openlegion.ai and get a dedicated agent fleet running in \~5-12 minutes. No infrastructure to manage. Clone the repo and run on your own machine. Full control, no account needed. ## Why OpenLegion? Agents run as UID 1000 in cap-dropped, read-only Docker containers with 384 MB RAM / 0.15 CPU defaults. API keys live in a mesh-tier credential vault — agents never see them. Defense-in-depth across container isolation, vault proxy, SSRF guard, AST-validated skills, per-agent budgets, and rate-limit categories. Agents coordinate through a SQLite-backed blackboard (with atomic CAS), pub/sub events, per-agent FIFO lanes, and a structured handoff protocol. No LLM "router" decides what runs next. Per-agent and per-project LLM cost ledger enforces daily ($10 default) and monthly ($200 default) USD caps before each request. CAPTCHA spend ledger tracked separately in millicents. Agents learn from tool failures and user corrections — `errors.md` and `corrections.md` auto-inject each session. Agents can write their own Python skills at runtime (AST-validated against 23 forbidden imports / 16 forbidden calls / 11 forbidden attrs, max 10K chars) and hot-reload them. Connect agents to Telegram, Discord, Slack, WhatsApp, Webhooks, and the CLI REPL. Engine dashboard provides chat, kanban, agent grid, and settings surfaces. Routed via LiteLLM (100+ providers). 15 system-tier providers are natively supported with mesh-managed keys; failover chains with per-model health tracking and exponential cooldown. ## Explore the docs Dedicated VPS, custom subdomain, automatic provisioning, and subscription management. Trust zones, mesh host, agent containers, and the browser service. Blackboard, pub/sub, lanes, handoffs, and fleet templates. Agents, permissions, mesh settings, and environment variables. # App Dashboard Source: https://docs.openlegion.ai/managed/dashboard Monitor your instance and manage your subscription The app dashboard at [app.openlegion.ai](https://app.openlegion.ai) is where you monitor your instance status and manage your subscription. For agent configuration and fleet management, use the engine dashboard on your subdomain (covered at the bottom of this page). ## Fleet status When your instance is running, the dashboard shows: * **Status badge** — Online, Starting up, Resizing, Migrating, or Paused * **Subdomain** — your fleet URL (`{subdomain}.engine.openlegion.ai`) with a copy button * **Plan summary** — current plan, agents, browsers, projects, and credit balance * **Open Dashboard button** — the primary CTA; single-sign-on into your engine The page auto-refreshes every 10s in transitional states (`starting up`, `resizing`). **Migrating** is a defined status that the UI renders but is not reachable from current code paths — it's staged for a future feature. ### Open Dashboard This button is the **only supported way** to enter your engine from the app. The app generates a short-lived HMAC-SHA256 token (5-minute TTL, one-time use), 302-redirects you through the auth-gate sidecar on your VPS, which then sets a 24h `ol_session` cookie. Direct navigation to your subdomain (e.g., from a bookmark) requires an existing session cookie. If your cookie has expired, return to the app and click **Open Dashboard** again. ### Notifications and banners The fleet dashboard surfaces banners for: past-due payment, cancelled subscription (countdown), paused instance, resizing, migrating, and unhealthy. ## Instance states The fleet view condenses 15 internal status values into the user-facing badges above. The most common transitional and failure states you'll encounter: | State | Meaning | | -------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | | **Online** (`running`) | Your fleet is running normally | | **Starting up** (`queued` → `provisioning` → `bootstrapping` → `awaiting_configuration` → `configuring`) | Initial provisioning or post-restart warm-up — auto-refreshes | | **Resizing** | Server is being resized for a plan change (\~30-60s downtime) | | **Paused** | Instance paused due to a past-due payment (resumes automatically when payment succeeds) | | **Unhealthy** | Engine not responding to health checks. Auto-restart attempts in progress (max 3). | | **Provisioning Failed** (`failed` / `bootstrap_failed`) | Server setup didn't complete — retry runs from scratch (VPS is cleaned up first) | | **Configuration Failed** (`configuration_failed`) | Server is healthy but configuration didn't complete — retry just re-runs `/configure` (VPS is kept) | | **Decommissioned** (`deprovisioned`) | Subscription ended or unrecoverable failure — the app shows a Decommissioned card with a new-subscription CTA | If provisioning or configuration fails, the app renders a **Retry** button next to a **Contact Support** link ([admin@openlegion.ai](mailto:admin@openlegion.ai)). ## Settings The **Settings** page shows your current plan and lets you: * **Change plan** — select a different tier or switch between monthly and yearly billing * **Resume** — reactivate a cancelled subscription before the period ends * **Cancel** — cancel your subscription (access kept until end of billing period) * **Keep current plan** — cancel a pending downgrade so you stay on your current tier at renewal See [Plans & Billing](/managed/plans) for details on upgrades, downgrades, and payment handling. ## Credits The **Credits** page (`/credits` in the app) shows your current OpenLegion credit balance and lets you top up. Every managed instance ships with the `openlegion/openai/gpt-5.4` credit-backed default model; welcome credits are granted on first subscription activation. ## What the app dashboard does not do The app dashboard is intentionally narrow: * No user-initiated pause / resume (pause only triggers after 3 days past-due) * No region picker (server location chosen by provisioner fallback chain) * No "rotate access token" UI * No re-configure API keys UI (manage those from the engine's **Settings → API Keys** tab) * No multi-instance support — one VPS per subscription * No log streaming, per-agent metrics, or backup/restore UX * No direct agent control, chat UI, or project management — all of that lives on the engine dashboard ## Engine dashboard The engine dashboard at `{subdomain}.engine.openlegion.ai` is where you do the actual work with your agents. Open it via the app's **Open Dashboard** button (see SSO note above). The engine SPA is built on Alpine.js + Tailwind with no build step. It serves 143 dashboard API endpoints and streams real-time updates over a `/ws/events` WebSocket (50 event types, 500-event ring buffer). ### Four top-nav tabs | Tab | Internal id | What you do here | | ------------ | ----------- | -------------------------------------------------------------------------------------------------------------------------------- | | **Chat** | `chat` | Multi-agent chat. Your primary conversation surface. Per-token streaming chat over a dedicated endpoint. | | **Work** | `workplace` | Kanban (Pending / Working / Blocked / Done), "Needs You" panel, activity feed with pinned blockers, recently delivered previews. | | **Team** | `fleet` | Agent grid — inspect, edit, and manage individual agents (model, budget, soul, instructions, permissions). | | **Settings** | `system` | 11 sub-tabs (below). | Internal ids are frozen for URL stability — the user-facing labels diverged. ### Settings sub-tabs (11) Activity, Costs, Automation, Integrations, API Keys, Wallet, Network, Storage, Operator, Browser, Settings. Highlights: * **Costs** — per-agent / per-project LLM cost ledger with daily and monthly budgets. * **Automation** — cron jobs and heartbeats (5-field cron or `every N[s/m/h/d]` interval). * **Integrations** — channel setup (Telegram / Discord / Slack / WhatsApp / Webhook) and webhook creation (webhooks are dashboard-only). * **API Keys** — add or replace LLM provider keys (BYOK overlay on top of the OpenLegion credit proxy). * **Wallet** — seed reveal is **once-only** (HTTP 410 thereafter); per-agent EVM + Solana addresses; spend caps and rate limits. * **Browser** — Camoufox flags, CAPTCHA solver config, device profiles, fingerprint controls. (Fingerprint burn detection does not auto-rotate — operator action required.) * **Operator** — operator-only control plane: edit agents, project management, pending actions. ### Browser viewer Each agent gets its own Camoufox (stealth Firefox) instance in the shared browser-service container, lazy-spawned on first use. The dashboard provides a per-agent live VNC view via the engine's `/agent-vnc/{agent_id}/{path}` proxy. The VNC proxy **only accepts the `ol_session` cookie** — agent Bearer tokens are rejected. # Managed Hosting Source: https://docs.openlegion.ai/managed/overview A dedicated OpenLegion server provisioned and managed for you OpenLegion managed hosting gives you a dedicated VPS running your own agent fleet — no infrastructure to set up or maintain. Sign up at [app.openlegion.ai](https://app.openlegion.ai), pick a plan, and your fleet is live in roughly **5-15 minutes**. ## The journey end-to-end ``` 1. Sign in (OAuth: Google / GitHub / Discord) 2. Pick a plan and check out via Polar (no free trial — card charged immediately) 3. Choose a subdomain → my-fleet.engine.openlegion.ai 4. Wait for provisioning (~5-12 min) — server, install, configure, go live 5. Click "Open Dashboard" to single-sign-on into the engine 6. Work with your fleet in the engine UI (Chat / Work / Team / Settings) ``` The app at [app.openlegion.ai](https://app.openlegion.ai) handles **account, billing, and instance lifecycle**. Your dedicated server at `{subdomain}.engine.openlegion.ai` runs the **full OpenLegion engine** — the same software available to self-hosted users. ## 1. Sign up Sign in at [app.openlegion.ai/signin](https://app.openlegion.ai/signin) with **Google**, **GitHub**, or **Discord** (whichever your operator has enabled). There is **no email / password option**, no magic link, no anonymous mode. The first OAuth callback creates your account; subsequent sign-ins reuse it. Sessions use a stateless JWT strategy — there is no server-side session revocation. ## 2. Pick a plan Four self-serve tiers are available; Enterprise is contact-sales only. | Plan | Monthly | Yearly | Agents | Browsers | Projects | | ----------- | -------- | ---------- | ------ | -------- | -------- | | **Basic** | \$19/mo | \$170/yr | 1 | 1 | 0 | | **Growth** | \$59/mo | \$530/yr | 5 | 5 | 2 | | **Pro** | \$149/mo | \$1,340/yr | 15 | 10 | 5 | | **Pro Max** | \$279/mo | \$2,510/yr | 30 | 30 | 10 | Yearly billing saves approximately 25%. See [Plans & Billing](/managed/plans) for upgrade / downgrade / cancellation rules. **No free trial.** Your card is charged at checkout. Payments are processed by **Polar** (the checkout, webhooks, and billing portal are Polar-hosted). For higher limits, dedicated infra, or a custom contract, email [admin@openlegion.ai](mailto:admin@openlegion.ai). ## 3. Choose a subdomain After checkout you land on the setup wizard. Pick a subdomain — this becomes your permanent fleet URL: ``` https://my-fleet.engine.openlegion.ai ``` Rules: 3-20 chars, must start with a lowercase letter, must end with a lowercase letter or digit, only lowercase letters / digits / hyphens, no consecutive hyphens. About 30 reserved names (`api`, `admin`, `dashboard`, `app`, `auth`, `www`, etc.) are blocked. The page live-checks availability as you type (500ms debounce). Subdomains held by deprovisioned or failed instances are released back into the pool. ## 4. Provisioning Your dedicated server is being set up. The setup page polls every 10s and shows live progress through four stages: 1. **Creating your dedicated server** — a [Hetzner Cloud](https://www.hetzner.com/cloud) VPS is provisioned. ARM is preferred (cax11/cax21/cax31/cax41) with x86 fallback (cpx21/cpx31/ccx23/ccx43). Provisioner tries the fallback chain up to 3 times with 30s / 120s / 300s backoff. 2. **Installing OpenLegion** — cloud-init installs the ufw firewall, hardens SSH, installs Docker (GPG-verified), Caddy (auto-SSL via Let's Encrypt), and a tiny Python auth-gate sidecar on `127.0.0.1:9401`. The engine repo is cloned and the systemd `openlegion` service is enabled (but not yet started). 3. **Configuring your instance** — your selected LLM API keys (11 allowlisted providers, 256-char per value) are written, the auth-gate's access token is provisioned, and the engine starts. 4. **Go live** — DNS (Cloudflare A record, TTL 60s, `dns_only=True`) propagates, Caddy issues your TLS cert, and the engine becomes reachable on your subdomain. Typical total: **5-12 minutes**. After `running` the app polls `/api/instance/ready` for up to 5 minutes waiting for the public URL. **Every configured managed instance** is provisioned with OpenLegion's **credit-backed proxy** as the default model (`openlegion/openai/gpt-5.4`). You don't need to bring your own API key to start chatting; BYOK keys you supply at configure time **overlay** on top of the credit proxy. ### If provisioning fails The setup wizard renders an error card with a **Retry** button. Three failure modes: * **`failed`** (generic) — full VPS cleanup, retry runs from scratch * **`bootstrap_failed`** — SSH never came up or cloud-init failed — VPS is cleaned up * **`configuration_failed`** — VPS is healthy but the configure step failed — the VPS is **kept** and retry just re-runs `/configure` A stuck-instance scheduler also recovers anything that hangs: >45 min in `queued / provisioning / bootstrapping` → `failed`; >10 min in `configuring` → `configuration_failed`; >45 min in `deprovisioning` → `deprovisioned`. ## 5. Open Dashboard (SSO into the engine) Once your instance is `running`, the app dashboard surfaces an **Open Dashboard** button. **This is the only way to enter the engine from the app** — direct navigation to your subdomain requires an active engine session. Click it and: 1. The app calls `/api/auth/engine-login?subdomain=...` (rate-limited 10/60s). 2. The app decrypts your stored access token (AES-256-GCM) and signs a short-lived token: `{expiry}.HMAC-SHA256(raw_token, "{subdomain}:{expiry}")` with a **5-minute TTL**. 3. Your browser is 302-redirected to `https://{subdomain}.engine.openlegion.ai/__auth/callback?token=...`. 4. The auth-gate sidecar (running on the VPS, separate from the engine) verifies the HMAC, enforces TTL, **rejects replays**, and sets an `ol_session` cookie (HttpOnly, Secure, SameSite=Lax, 24h max-age). 5. You're redirected to your engine root. Subsequent requests inside the engine go through Caddy's `forward_auth` checking that cookie. The cookie auto-renews if its remaining life is below half the max age. ## 6. The engine dashboard You land on the engine SPA at `{subdomain}.engine.openlegion.ai`. Four top-nav tabs: | Tab | What you do here | | ------------ | -------------------------------------------------------------------------------------------------------------------------------- | | **Chat** | Multi-agent chat. Your primary conversation surface. | | **Work** | Kanban (Pending / Working / Blocked / Done), "Needs You" panel, activity feed with pinned blockers, recently delivered previews. | | **Team** | Agent grid. Inspect, edit, and manage individual agents. | | **Settings** | 11 sub-tabs: Activity, Costs, Automation, Integrations, API Keys, Wallet, Network, Storage, Operator, Browser, Settings. | The engine SPA streams real-time updates via a `/ws/events` WebSocket (50 event types, 500-event ring buffer). ## What you get with every plan * **Dedicated single-tenant VPS** — one VPS per subscription. No shared engine processes between users. * **Custom subdomain** — `{your-name}.engine.openlegion.ai` * **Container-isolated agent execution** — each agent in its own Docker container with private memory, tools, schedule, and budget * **OpenLegion-managed LLM credits** — every instance ships with the credit-backed `openlegion/openai/gpt-5.4` default model. BYOK keys overlay on top to add more providers. * **100+ LLM providers via LiteLLM** * **Multi-channel support** — Telegram, Discord, Slack, WhatsApp, and Webhook * **Real-time monitoring dashboard** with cost tracking and budgets * **Browser automation** — plan-dependent concurrent browsers (1, 5, 10, or 30) * **Automatic health monitoring** — provisioner pings every 5 minutes and auto-restarts unhealthy instances up to 3 times ## Self-hosting vs managed | | Managed Hosting | Self-Hosted | | ------------------ | ----------------------------------------------------------- | ------------------------------------------- | | **Setup** | Sign up and go live in \~5-12 minutes | Clone repo, install dependencies, configure | | **Infrastructure** | Hetzner Cloud VPS provisioned + maintained by us | You manage the server | | **Updates** | Pushed by the OpenLegion team; no user action required | You pull and restart | | **Monitoring** | Built-in health checks + auto-recovery | You set up monitoring | | **LLM credits** | OpenLegion credit proxy included; BYOK overlays | BYOK only | | **Cost** | $19-$279/mo + your LLM API costs (or use managed credits) | Your VPS + LLM API costs | | **Control** | Full engine via SSO; some operations user-gated (see below) | Full source + filesystem access | ### What the app dashboard does NOT do The app at `app.openlegion.ai` is intentionally narrow. It handles signup, billing, provisioning, and SSO — it does **not** offer: * User-initiated pause / resume (pause happens automatically after 3 days past-due) * Region picker (server location chosen by provisioner fallback chain) * "Rotate access token" UI * Re-configure-API-keys UI (configure is fire-once; change keys from the engine's Settings → API Keys tab) * Multi-instance support (one VPS per subscription) * Log streaming or per-agent metrics dashboards ## Billing problems If a payment fails, your subscription enters **past\_due** and a banner appears on the app dashboard. You keep service for a 3-day grace period to update your payment method. **After 3 days the instance is paused** (`systemctl stop`); **after 14 days without payment the VPS is permanently deprovisioned** (and all data with it). See [Plans & Billing](/managed/plans) for details. Sign up and launch your fleet in roughly 5-12 minutes. # Plans & Billing Source: https://docs.openlegion.ai/managed/plans Compare plans, manage your subscription, and understand billing ## Plans Four self-serve tiers. Every plan includes container-isolated agent execution, 100+ LLM providers via LiteLLM, the OpenLegion credit-backed default model, monitoring dashboard, custom subdomain, multi-channel support (Telegram / Discord / Slack / WhatsApp / Webhook), and a dedicated single-tenant Hetzner Cloud VPS (ARM preferred, x86 fallback). | | Basic | Growth | Pro | Pro Max | | ------------ | --------- | --------- | ---------- | ---------- | | **Monthly** | \$19/mo | \$59/mo | \$149/mo | \$279/mo | | **Yearly** | \$170/yr | \$530/yr | \$1,340/yr | \$2,510/yr | | **Agents** | 1 | 5 | 15 | 30 | | **Browsers** | 1 | 5 | 10 | 30 | | **Projects** | 0 | 2 | 5 | 10 | | **Support** | Community | Community | Community | Community | Yearly billing saves approximately 25%. **Pro** is the most popular plan — 15 agents and 5 projects covers most production use cases. **No free trial.** Your card is charged at checkout. Payments are processed by **Polar**. ### Enterprise For higher limits, dedicated infrastructure, custom contracts, or contractual support, contact [admin@openlegion.ai](mailto:admin@openlegion.ai). Enterprise is not a self-serve tier. ### LLM usage and credits Every managed instance is provisioned with OpenLegion's credit-backed proxy as its default model (`openlegion/openai/gpt-5.4`). When your subscription first becomes active, welcome credits are granted automatically. You can also: * **Bring your own API keys** (BYOK) for any of the 11 supported providers — they overlay on top of the credit proxy, giving you access to additional models. * **Top up credits** from the `/credits` page in the app. Engine-side, every LLM call is charged to a per-agent + per-project ledger (`data/costs.db`). Defaults: `$10/day` and `$200/month` per agent. The operator agent can raise these up to `$1,000/day` and `$30,000/month`. CAPTCHA-solving spend is tracked in a separate ledger in millicents (1/100,000 USD). ## Changing plans Manage your plan from the **Settings** page at [app.openlegion.ai](https://app.openlegion.ai). ### Upgrades Upgrades take effect **immediately**. You're charged a prorated amount for the remainder of your billing period. Your agent, project, and browser limits increase right away. If the upgrade requires a larger server, an automatic resize happens. Resize sequence: pre-scale limits → power off → `change_server_type` → power on → wait for SSH → re-apply limits → wait for engine health (up to 240s). Total downtime: **\~30-60 seconds**. ### Downgrades Downgrades are **scheduled for the end of your current billing period**. You keep your current plan's limits until then. At renewal, your plan switches and limits adjust. You can cancel a pending downgrade from the Settings page by clicking **Keep current plan**. ### Billing interval changes Switching between monthly and yearly billing takes effect at your next renewal. ## Cancellation Cancel from the **Settings** page. Your instance stays active until the end of your current billing period (`currentPeriodEnd`). After that, the server is deprovisioned and data is deleted. You can **resume** a cancelled subscription at any time before the billing period ends. There is no data export UI — back up anything you need from the engine dashboard before the period ends. ## Payment issues If a payment fails, your subscription enters a **past\_due** state. The app considers your subscription active during the grace period. * **Day 0-3** — banner shown in the app; instance keeps running. Update payment to clear. * **Day 3** — instance is **paused** (`systemctl stop` via provisioner). The VPS is preserved; resume is automatic once payment succeeds. * **Day 14** — instance is **permanently deprovisioned**. The VPS and all data are deleted. Authentication for the past-due cron uses a timing-safe bearer check; the provisioner is pinged first and the action is skipped if it's unreachable. # Quick Start Source: https://docs.openlegion.ai/quickstart Get a managed OpenLegion fleet running in minutes Sign up at [app.openlegion.ai](https://app.openlegion.ai) and get a dedicated agent fleet with zero infrastructure setup. ## Sign up and subscribe Go to [app.openlegion.ai](https://app.openlegion.ai) and sign in with **Google**, **GitHub**, or **Discord**. There is no email/password option. After signing in, you'll see the pricing page. Pick the plan that fits your needs: | Plan | Monthly | Yearly | Agents | Browsers | Projects | | ----------- | -------- | ---------- | ------ | -------- | -------- | | **Basic** | \$19/mo | \$170/yr | 1 | 1 | 0 | | **Growth** | \$59/mo | \$530/yr | 5 | 5 | 2 | | **Pro** | \$149/mo | \$1,340/yr | 15 | 10 | 5 | | **Pro Max** | \$279/mo | \$2,510/yr | 30 | 30 | 10 | All plans include container-isolated execution, 100+ LLM providers via LiteLLM, a monitoring dashboard, a custom subdomain, and a built-in OpenLegion credit-backed default model. Yearly billing saves approximately 25%. **No free trial** — your card is charged immediately at checkout. Need higher limits, dedicated infra, or a custom contract? Email [admin@openlegion.ai](mailto:admin@openlegion.ai) for Enterprise pricing. Click **Get Started** on your chosen plan to complete checkout. Payments are processed by Polar. After payment, you'll land on the setup page. Pick a subdomain for your fleet — this becomes your permanent URL: ``` https://my-fleet.engine.openlegion.ai ``` Subdomains must be 3-20 characters, start with a letter, end with a letter or number, contain only lowercase letters/numbers/hyphens, and cannot include consecutive hyphens. Reserved names (`api`, `admin`, `dashboard`, `www`, etc.) are blocked. Click **Launch My Server**. Your dedicated server is being set up. The setup page shows live progress through four stages: 1. **Creating your dedicated server** — a Hetzner Cloud VPS is provisioned (ARM preferred, x86 fallback) 2. **Installing OpenLegion** — Docker, Caddy (auto-SSL via Let's Encrypt), the auth-gate sidecar, and the engine are installed 3. **Configuring your instance** — your API keys are written and the agent fleet is started 4. **Go live** — your instance is reachable on your subdomain Typical total: **\~5-12 minutes**. The page polls every 10s and updates automatically. Once provisioning completes, click **Open Dashboard** to single-sign-on into your fleet's engine dashboard. (Direct navigation to your subdomain requires an active engine session — always start from the app's "Open Dashboard" button.) From the engine dashboard you can: * Add or replace LLM provider keys (BYOK overlay on top of the OpenLegion-managed default) * Configure agents (instructions, soul, model, budget, thinking mode) * Connect messaging channels (Telegram, Discord, Slack, WhatsApp, Webhook) * Schedule cron jobs and heartbeats * Monitor costs and activity in real time The dashboard has four top-nav tabs: **Chat**, **Work**, **Team**, and **Settings** (with 11 sub-tabs). ## What happens next Your fleet runs on a dedicated VPS at `{your-subdomain}.engine.openlegion.ai`. You manage it through the engine dashboard — the same interface available to self-hosted users. The app at [app.openlegion.ai](https://app.openlegion.ai) handles your subscription, billing, and server lifecycle. Every configured managed instance is provisioned with OpenLegion's credit-backed proxy as the default model (`openlegion/openai/gpt-5.4`) so you can start chatting without bringing your own API key. ## Next steps Understand what's included and how managed hosting works end-to-end. Compare plans, upgrade, downgrade, and manage billing. Browser automation, memory, coordination, wallet, and more. How agents collaborate: blackboard, pub/sub, handoffs, lanes. # Agent Tools Source: https://docs.openlegion.ai/reference/agent-tools All built-in tools available to agents OpenLegion ships a set of `@skill`-decorated built-in tools organized into modules under `engine/src/agent/builtins/`. Worker agents see a curated subset based on their `permissions.json` ACL; the operator agent sees additional control-plane tools. Agents can also [create custom skills](/features/agents) at runtime (AST-validated) and use [MCP tool servers](/features/mcp). Every tool call has a default `OPENLEGION_TOOL_TIMEOUT` of 300s. ## Execution | Tool | Purpose | | ------------- | -------------------------------------------------------- | | `run_command` | Shell command execution, scoped to `/data`, 300s ceiling | ## File Operations All file operations are scoped to `/data` with 4-stage path-traversal protection. Limits: `_MAX_READ=500_000` bytes, `_MAX_LIST_ENTRIES=500`. Direct writes to workspace files (SOUL/INSTRUCTIONS/USER/MEMORY/HEARTBEAT/AGENTS/INTERFACE) are blocked at this layer — use `update_workspace` instead. | Tool | Purpose | | ------------ | ------------------------------- | | `read_file` | Read file contents from `/data` | | `write_file` | Write / append file in `/data` | | `list_files` | List / glob files in `/data` | ## HTTP | Tool | Purpose | | -------------- | ---------------------------------------------------------------------- | | `http_request` | HTTP GET / POST / PUT / DELETE / PATCH with `$CRED{name}` substitution | SSRF protection is enforced at the mesh: DNS pinning, RFC1918 / loopback / link-local / CGNAT / 6to4 / Teredo / IPv4-mapped-IPv6 blocking, fail-closed on DNS error, max 5 redirects (re-validated at each hop), cross-origin auth strip. ## Browser Automation Camoufox (stealth Firefox fork) **per agent**, lazy-spawned on X displays `:100..:163` (64 slots) paired with KasmVNC ports `6100..6163`. The browser-service **container** is shared (FastAPI `:8500`); the Camoufox instance is per-agent. All browser actions are `parallel_safe=False` per `agent_id`. | Tool | Purpose | | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `browser_navigate` | Open URL, extract page text | | `browser_warmup` | Pre-spawn the browser for an agent without navigating | | `browser_get_elements` | A11y-tree snapshot (200-element cap, iframe nesting cap 3) with element refs (`e1, e2, …`) | | `browser_screenshot` | Capture page screenshot (WebP by default) | | `browser_click` | Click element by ref or CSS selector | | `browser_click_xy` | Click absolute (x, y) coordinates | | `browser_type` | Fill input by ref or CSS selector | | `browser_hover` | Hover over element by ref or CSS selector | | `browser_scroll` | Scroll page or scroll element into view | | `browser_wait_for` | Wait for element, navigation, or network idle | | `browser_find_text` | Locate visible text on page | | `browser_fill_form` | Fill a form (max 50 fields per call) | | `browser_press_key` | Press keyboard key (Enter, Escape, Tab, …) | | `browser_open_tab` | Open a new tab | | `browser_switch_tab` | Switch between open tabs | | `browser_go_back` | Navigate back in history | | `browser_go_forward` | Navigate forward in history | | `browser_reset` | Force-close browser and reconnect fresh | | `browser_inspect_requests` | Inspect network requests (200-entry buffer) | | `browser_upload_file` | Upload (max 5 files, 50 MB each; 60s stage TTL) | | `browser_download` | Download a file | | `browser_detect_captcha` | Detect captcha presence and kind | | `browser_solve_captcha` | Auto-solve via 2captcha / capsolver (reCAPTCHA v2/v3/enterprise, hCaptcha, Cloudflare Turnstile + interstitial, PerimeterX press-hold, DataDome, JS-challenges) | | `browser_request_captcha_help` | Route a captcha to the human operator (behavioral kinds) | | `browser_request_browser_login` | Route a login challenge to the human operator | **Costs** for CAPTCHA solving are tracked in **millicents** (1/100,000 USD) with per-agent + per-tenant monthly caps and a fleet-wide `CAPTCHA_DISABLED` kill switch. Solver-provider circuit breaker: 3 failures / 5 min → open 10 min. **Session persistence** is opt-in (`BROWSER_SESSION_PERSISTENCE_ENABLED`, default FALSE; snapshot interval 60-3600s, default 300s). **Device profiles**: `desktop-windows` (default), `desktop-macos`, `mobile-ios`, `mobile-android`. Mobile profiles spoof UA strings only — the underlying Camoufox / Firefox engine and TLS/JA3 fingerprints remain desktop. **Fingerprint burn detection** uses a rolling window of 10; ≥50% rejection sets `fingerprint_burn=True`. **There is no auto-rotation** — the operator must rotate the profile and reset. ## Memory | Tool | Purpose | | --------------- | --------------------------------------------------------------------------------------------------------------------------------- | | `memory_search` | Hybrid search across workspace files and the structured memory DB (BM25 + vector with `(0.7*vector + 0.3*keyword) * decay_score`) | | `memory_save` | Save a fact to workspace and structured memory DB | Vector search uses `text-embedding-3-small` (OpenAI, 1536 dims). Non-OpenAI providers default to `"none"` and **degrade to keyword-only**; after 3 consecutive embedding failures the store silently disables vectors for the process lifetime. See the [Memory System](/features/memory) for full details on the 5-layer architecture. ## Web Search | Tool | Purpose | | ------------ | -------------------------------------------------------------- | | `web_search` | Search via DuckDuckGo HTML, no API key required (1-10 results) | ## Image Generation | Tool | Purpose | | ---------------- | ----------------------------------------------------------------------------------------------- | | `generate_image` | Generate a PNG via Gemini (default) or OpenAI DALL-E 3; written to `/data/workspace/artifacts/` | ## Fleet Awareness | Tool | Purpose | | -------------------- | ------------------------------------------------------- | | `notify_user` | Send a notification to the user via the active channel | | `list_agents` | Discover other agents in the fleet | | `get_agent_profile` | Get role / status / capabilities of another agent | | `read_agent_history` | Read another agent's conversation logs (subject to ACL) | ## Workspace | Tool | Purpose | | ------------------ | ------------------------------------------------------------------------------------------------------------------- | | `update_workspace` | Update identity / state files (SOUL.md, INSTRUCTIONS.md, USER.md, MEMORY.md, INTERFACE.md, AGENTS.md, HEARTBEAT.md) | Workspace caps (returned 413 on overflow): SOUL 4K, INSTRUCTIONS 12K, USER 4K, MEMORY 16K, INTERFACE 4K, AGENTS 12K, HEARTBEAT uncapped. `PROJECT.md` and `SYSTEM.md` are bootstrap-only and **read-only** at runtime. ## Introspection | Tool | Purpose | | ------------------- | ------------------------------------------------------------------------------- | | `get_system_status` | Query own runtime state: permissions, budget, fleet, cron, health (5-min cache) | ## Shared State (Blackboard) All blackboard operations are subject to per-agent `blackboard_read` / `blackboard_write` glob patterns. Writes are CAS-based via `write_if_version`. | Tool | Purpose | | ------------------ | --------------------------------------------------------- | | `read_blackboard` | Read a blackboard key | | `write_blackboard` | Write to a blackboard key (atomic CAS) | | `list_blackboard` | Browse blackboard entries by prefix | | `watch_blackboard` | Watch for blackboard key changes (pattern-based) | | `publish_event` | Publish event to mesh pub/sub | | `subscribe_event` | Subscribe to mesh pub/sub events at runtime | | `claim_task` | Claim a task from the agent's lane queue (CAS) | | `save_artifact` | Save a deliverable file and register it on the blackboard | ## Coordination | Tool | Purpose | | --------------- | --------------------------------------------------------------------- | | `hand_off` | Hand a task off to another agent with structured payload (TTL 86400s) | | `check_inbox` | Check pending handoffs / messages | | `update_status` | Update own task status | | `complete_task` | Mark a claimed task complete | Coordination uses the durable V2 task ledger when `OPENLEGION_ORCHESTRATION_TASKS_V2=1` (default ON); V1 falls back to blackboard `tasks/{agent}/{handoff_id}` records. ## Scheduling | Tool | Purpose | | ------------- | ------------------------ | | `set_cron` | Schedule a recurring job | | `list_cron` | List scheduled jobs | | `remove_cron` | Remove a scheduled job | 5-field cron (minute granularity) **or** `every N[s/m/h/d]` interval syntax. No 6-field / seconds support. Three modes: message-mode (LLM dispatch), tool-mode (`tool_name` / `tool_params` direct invoke — no LLM), and heartbeat-mode (`heartbeat=true`). Heartbeats default to `every 15m` (the operator's is force-locked to `every 15m`) and have a skip-LLM optimization that activates only when (not manual) + (HEARTBEAT.md is default) + (no recent activity) + (no probes triggered). See [Triggering & Automation](/features/triggering) for details. ## Skills Management | Tool | Purpose | | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | `create_skill` | Write a new Python skill at runtime (AST-validated against 23 forbidden imports, 16 forbidden calls, 11 forbidden attrs; `_MAX_SKILL_SIZE=10_000` chars) | | `reload_skills` | Hot-reload all skills | | `spawn_fleet_agent` | **Operator-only.** Spawn a new ephemeral fleet agent via the mesh. The operator cannot grant `can_spawn=true` to other agents. | ## Subagent Spawning | Tool | Purpose | | ------------------- | --------------------------------------------------------------- | | `spawn_subagent` | Spawn a lightweight in-process subagent (no container overhead) | | `list_subagents` | List active subagents | | `wait_for_subagent` | Wait for a subagent to complete and return its result | Subagents have `MAX_DEPTH=2`, `MAX_CONCURRENT=3` per parent, default TTL 300s (max 600s), `DEFAULT_MAX_ITERATIONS=10`. **Subagents cannot recurse, cannot create skills, and cannot use the browser concurrently** (browser module is parallel-unsafe per agent). ## Credential Vault | Tool | Purpose | | ----------------------- | -------------------------------------------------------------------------------------------------- | | `vault_generate_secret` | Generate a secure random secret; returns a `$CRED{name}` handle only | | `vault_list` | List available credentials in the vault | | `request_credential` | Request the user provide a credential interactively (the only path for runtime credential capture) | Agents never see raw secret values — only opaque `$CRED{name}` handles that the mesh substitutes at call time. System-tier credentials (`OPENLEGION_SYSTEM_*`) are always blocked from agents regardless of `allowed_credentials`. ## Wallet EVM chains (`evm:ethereum`, `evm:base`, `evm:arbitrum`, `evm:polygon`, `evm:sepolia`) and Solana (`solana:mainnet`, `solana:devnet`). Per-agent EVM keys derive via BIP-44 (`m/44'/60'/{agent_index}'/0/0`); per-agent Solana keys via HMAC-SHA512 over a PBKDF2 of the master seed. **Private keys never leave the mesh process.** The master seed is returned **once** at `POST /api/wallet/init` with `Cache-Control: no-store`; `GET /api/wallet/seed` returns HTTP 410 Gone afterward. | Tool | Purpose | | ---------------------- | ----------------------------------------------- | | `wallet_get_address` | Get the agent's wallet address for a chain | | `wallet_get_balance` | Get native / token balance | | `wallet_read_contract` | Read from a smart contract | | `wallet_transfer` | Transfer native or token (rate-limited, capped) | | `wallet_execute` | Sign and submit a protocol transaction | Defaults: `LIMIT_PER_TX_USD=10`, `LIMIT_DAILY_USD=100`, `RATE_LIMIT_PER_HOUR=10`. Agents need `can_use_wallet=true` — **and the operator cannot grant this flag**; it must be edited directly in `permissions.json`. ## Operator-Only Tools The operator agent has additional control-plane tools for fleet management. These are not available to worker agents. | Tool | Purpose | | ------------------------------------------------------ | -------------------------------------------------------------------------------------------------- | | `inspect_agents` | Inspect agents' configs / state | | `inspect_projects` | Inspect project metadata | | `manage_project` / `create_project` | Project lifecycle | | `add_agents_to_project` / `remove_agents_from_project` | Project membership | | `update_project_context` / `set_project_goal` | Project content | | `manage_agent` / `create_agent` | Agent lifecycle | | `edit_agent` | Edit agent config (soft fields = 5-min Undo TTL; hard fields = 30-min, require `confirm_edit`) | | `propose_edit` / `confirm_edit` | Legacy two-step edit flow | | `manage_task` | Manage durable tasks | | `save_observations` | Persist operator observations | | `undo_change` | Revert a recent soft edit | | `list_pending` / `cancel_pending_action` | Pending-action queue | | `archive_audit_before` | Archive audit log entries | | `list_agent_queue` | Inspect another agent's lane queue | | `get_team_outputs` / `summarize_project_progress` | Team reporting | | `apply_template` | Apply a fleet template (per-slot, **not atomic** — mid-loop failures leave earlier-created agents) | | `list_templates` | List available templates | **Hard / soft edit fields.** SOFT (5-min Undo): `instructions`, `soul`, `heartbeat`, `heartbeat_schedule`, `interface`, `role`. HARD (30-min confirmation): `model`, `permissions`, `budget`, `thinking`. # CLI Reference Source: https://docs.openlegion.ai/reference/cli All CLI commands and interactive REPL commands ## CLI Commands ``` openlegion [--version] [--verbose | --quiet] ├── start [--config PATH] [-d] [--sandbox] [-p PORT] # Start runtime + interactive REPL ├── stop # Stop runtime + all openlegion_* containers ├── chat [NAME] [--port PORT] # Connect to a running agent ├── status [--port PORT] [--wide] [--watch N] [--json] # Show agent status ├── projects [--port PORT] [--json] # List active projects ├── project [--port PORT] [--json] # Single project details ├── tasks [--agent NAME] [--project ID] [--status S] # List/filter durable task records (V2) │ [--port PORT] [--json] ├── pending [--port PORT] [--json] # List open pending operator actions ├── confirm # Confirm a pending action ├── cancel # Cancel a pending action ├── reset [--yes/-y] # Stop + wipe config/data/skills/volumes ├── wallet # Wallet management │ ├── init # Generate 24-word BIP-39 seed (once) │ └── show [AGENT_ID] # Show wallet addresses on all chains └── version [-v] # Show version info ``` Agent management, credential management, channel setup, blackboard / queue inspection, cron management, and other per-agent operations are available as **interactive REPL commands** inside `openlegion start`. ## Command Details ### `openlegion start` Launches the mesh host, spins up agent containers, and starts the interactive REPL. On first run (no credentials configured), runs inline setup — API key entry, model selection, and optional agent creation — right in the terminal. | Flag | Description | | ------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `--config PATH` | Path to mesh config YAML file (default: `config/mesh.yaml`) | | `-d` | Run in background (daemon mode); logs to `.openlegion.log`, PID in `.openlegion.pid` | | `--sandbox` | Use Docker Sandbox microVMs for maximum isolation (requires Docker Desktop 4.58+; falls back to standard Docker on init failure) | | `-p`, `--port PORT` | Override the mesh / dashboard port (default: 8420) | ### `openlegion stop` Cleanly shuts down all `openlegion_*` Docker containers and the mesh host. Sends SIGTERM to the background host process if running, then cleans up any remaining containers. ### `openlegion chat [NAME]` Connect to a running agent from a separate terminal. Useful when running in daemon mode (`-d`). If no name is given, prompts to select from running agents. | Flag | Description | | ------------- | ------------------------------ | | `--port PORT` | Mesh host port (default: 8420) | ### `openlegion status` Show health and status of all configured and running agents. | Flag | Description | | -------------- | ------------------------------------ | | `--port PORT` | Mesh host port (default: 8420) | | `--wide`, `-w` | Show additional columns (role, cost) | | `--watch N` | Auto-refresh every N seconds | | `--json` | Output as JSON | ### `openlegion projects` List active projects in the running fleet. | Flag | Description | | ------------- | ------------------------------ | | `--port PORT` | Mesh host port (default: 8420) | | `--json` | Output as JSON | ### `openlegion project ` Show details for a single project (members, goal, context, recent activity). | Flag | Description | | ------------- | ------------------------------ | | `--port PORT` | Mesh host port (default: 8420) | | `--json` | Output as JSON | ### `openlegion tasks` List durable task records from the V2 orchestration ledger (`OPENLEGION_ORCHESTRATION_TASKS_V2=1`, default ON). | Flag | Description | | -------------- | ------------------------------ | | `--agent NAME` | Filter by assigned agent | | `--project ID` | Filter by project | | `--status S` | Filter by task status | | `--port PORT` | Mesh host port (default: 8420) | | `--json` | Output as JSON | ### `openlegion pending` List open pending operator actions. Some operator-initiated edits (model, permissions, budget, thinking) require explicit confirmation via `confirm` within 30 minutes; soft edits (instructions, soul, heartbeat, interface, role) have a 5-minute undo window. | Flag | Description | | ------------- | ------------------------------ | | `--port PORT` | Mesh host port (default: 8420) | | `--json` | Output as JSON | ### `openlegion confirm ` Confirm a pending operator action. Use `openlegion pending` to find the nonce. ### `openlegion cancel ` Cancel a pending operator action without applying it. ### `openlegion reset` Stop the runtime and wipe `config/`, `data/`, agent skills, and Docker volumes. **Keeps `.env`** so your API keys survive. Pass `--yes/-y` to skip the confirmation prompt. ### `openlegion wallet init` Generate a 24-word BIP-39 mnemonic and store it in `.env` as `OPENLEGION_SYSTEM_WALLET_MASTER_SEED`. The seed is shown **once** — there is no second-chance reveal. Per-agent EVM keys derive via BIP-44 (`m/44'/60'/{agent_index}'/0/0`); per-agent Solana keys via HMAC-SHA512 over a PBKDF2 of the seed. ### `openlegion wallet show [AGENT_ID]` Show wallet addresses on all supported chains (EVM: ethereum, base, arbitrum, polygon, sepolia; Solana: mainnet, devnet). Pass an agent ID to scope to that agent. ### `openlegion version` Show version and environment information. | Flag | Description | | ---- | --------------------------------------------------------------------------------- | | `-v` | Show extended info (Python version, Docker version, OS, config path, agent count) | ## Interactive REPL Commands When running `openlegion start`, you're in the interactive REPL. These commands are only available inside the REPL session: ### Chat & Navigation | Command | Description | | ------------------ | ----------------------------------------------------------------------------- | | `@agent ` | Send message to a specific agent | | `/use ` | Switch active agent | | `/status` | Show agent health (also aliased as `/agents`) | | `/broadcast ` | Send message to all agents | | `/steer ` | Inject message into a busy agent's context (rate-limited: 10 wakeups / 3600s) | | `/reset` | Clear conversation with active agent | | `/history` | View conversation history with active agent | | `/help` | Show available commands | | `/quit` | Exit and stop runtime | ### Agent Management | Command | Description | | ----------------- | -------------------------------------------- | | `/add` | Add a new agent (hot-adds to running system) | | `/agent [cmd]` | Inspect or edit a specific agent | | `/remove [name]` | Remove an agent | | `/restart [name]` | Restart a running agent | ### Monitoring & Debugging | Command | Description | | ------------------- | --------------------------------------------------------- | | `/costs` | Show today's spend, context usage, and model health | | `/traces` | Show recent request traces (aliased as `/debug`) | | `/logs` | View agent logs | | `/blackboard [cmd]` | View/edit shared blackboard entries (list, get, set, del) | | `/queue` | Show agent task queue / lane status | ### Automation | Command | Description | | ------------- | --------------------------------------------------------------- | | `/cron [cmd]` | Manage cron jobs and heartbeats (list, del, pause, resume, run) | 5-field cron (minute granularity) or `every N[s/m/h/d]` interval syntax. No 6-field / seconds support. Pass `heartbeat=true` for autonomous monitoring with built-in probes. ### Credentials & Channels | Command | Description | | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | `/addkey [key]` | Add an API credential to the vault at runtime (intentionally not a slash-command in chat-channel UIs — the value would be visible) | | `/removekey [name]` | Remove a credential from the vault | | `/credential [cmd]` | Manage vault credentials (add, list, remove) | ### Projects | Command | Description | | ---------------- | ------------------------------------ | | `/project [cmd]` | Project management (list, use, info) | **Aliases:** `/exit` = `/quit`, `/traces` = `/debug`, `/agents` = `/status`. ## Team Templates Templates offered during first-run setup (via `openlegion start`) and applied via the operator's `apply_template` tool (per-slot — not atomic; mid-loop failures leave earlier-created agents in place). | Template | Agents | Description | | -------------------- | ------------------------------- | ---------------------------------------------------------------------- | | `starter` | assistant | Single general-purpose agent | | `content` | researcher, writer | Blog / social / email from briefs | | `deep-research` | scout, analyst | Multi-source synthesis with citations | | `devteam` | pm, engineer, reviewer | Software development team | | `monitor` | watcher, analyst | Always-on monitoring | | `sales` | researcher, qualifier, outreach | Sales pipeline | | `competitive-intel` | tracker, analyst | Competitor pricing/product tracking | | `lead-enrichment` | enricher, verifier | Lead list research | | `price-intelligence` | crawler, analyst | Price monitoring with anti-bot browser | | `review-ops` | collector, responder | G2 / Trustpilot / Capterra / App Store / Google reviews + reply drafts | | `social-listening` | listener, reporter | Reddit / HN / X competitor pain-point monitor | | `research` | researcher | General-purpose researcher | | `opportunity-finder` | scout, evaluator, modeler | Gap-scout + evaluation + modeling | Each template references models via the `"{default_model}"` placeholder, substituted at apply time. `apply_template` accepts `agent_overrides` per slot (model, instructions ≤12K chars, soul ≤4K, heartbeat, interface ≤4K; role is template-fixed). # Configuration Source: https://docs.openlegion.ai/reference/configuration Agents, permissions, mesh settings, and environment variables ## `PROJECT.md` — Fleet-Wide Context Shared across all agents. Loaded into every agent's system prompt as part of the 48K-char bootstrap injection. ```markdown theme={null} # PROJECT.md ## What We're Building SaaS platform for automated lead qualification ## Current Priority Ship the email personalization pipeline this week ## Hard Constraints - Budget: $50/day total across all agents - No cold outreach to .edu or .gov domains ``` `PROJECT.md` is **read-only at runtime** — agents cannot modify it. Update it from the engine dashboard or via the `/project` REPL command. (The operator agent also has a `update_project_context` tool that proposes changes for confirmation.) ## `config/mesh.yaml` — Framework Settings ```yaml theme={null} mesh: host: "0.0.0.0" port: 8420 llm: default_model: "openai/gpt-4.1-mini" # wizard suggestion; engine fallback is "openai/gpt-4o-mini" embedding_model: "text-embedding-3-small" # 1536 dims (OpenAI); non-OpenAI providers degrade to keyword-only max_tokens: 4096 temperature: 0.7 # Per-model failover chain — agents retry the next entry on per-model health failure failover_chains: "anthropic/claude-sonnet-4-6": - "openai/gpt-4.1-mini" - "openai/gpt-4o-mini" channels: telegram: default_agent: assistant discord: default_agent: assistant slack: {} whatsapp: {} ``` **Channels use token-presence activation** — there is no `enabled: true/false` flag. A channel auto-starts when its bot token resolves through the credential vault. Resolution order: `OPENLEGION_SYSTEM_` → `OPENLEGION_CRED_` → bare env → `mesh.yaml channels..bot_token`. Remove the token to disable the channel. ## Agent Definitions There is **no top-level `config/agents.yaml` shipped** with the engine. Agents are defined one of three ways: 1. **Templates** in `src/templates/*.yaml` (the 13 fleet templates). 2. **Dashboard / operator tools** at runtime (the operator agent's `create_agent`, `edit_agent`, and `apply_template` tools, or the engine dashboard's Team tab). 3. **REPL** via `/add` while `openlegion start` is running. Each agent record carries: | Field | Type | Description | | ------------------------ | ------ | ----------------------------------------------------------------------------- | | `role` | string | Agent's role identifier (template-fixed when applied from a template) | | `model` | string | LiteLLM model id (e.g., `openai/gpt-4.1-mini`, `anthropic/claude-sonnet-4-6`) | | `skills_dir` | string | Path to custom skills directory | | `system_prompt` | string | Custom system prompt override (replaces the default) | | `initial_instructions` | string | Initial INSTRUCTIONS.md content (≤12K chars) | | `soul` | string | Initial SOUL.md content / persona (≤4K chars) | | `initial_interface` | string | Initial INTERFACE.md content (≤4K chars) | | `heartbeat` | string | Initial HEARTBEAT.md content (uncapped) | | `thinking` | string | Extended thinking: `off` (default), `low`, `medium`, or `high` | | `resources.memory_limit` | string | Container memory (worker default: `384m`; operator: `128m`) | | `resources.cpu_limit` | float | CPU quota (worker default: `0.15`; operator: `0.05`) | | `budget.daily_usd` | float | Daily LLM spend cap (default `10.00`; operator clamp: `0.01-1000`) | | `budget.monthly_usd` | float | Monthly LLM spend cap (default `200.00`; operator clamp: `0.10-30000`) | | `mcp_servers` | list | [MCP tool servers](/features/mcp) — stdio transport only | Container hardening is enforced by the runtime: UID 1000, `cap_drop=ALL`, no-new-privileges, read-only filesystem, `tmpfs=/tmp 100m noexec/nosuid`, `pids_limit=256`. These are not user-tunable. ## `config/permissions.json` — Agent Permissions Per-agent access control. **Default policy: deny.** A missing file denies everything. You may include a `"default"` template that other agents inherit from. ```json theme={null} { "default": { "can_message": ["*"], "blackboard_read": ["context/*", "tasks/*"] }, "researcher": { "can_message": ["operator", "writer"], "can_publish": ["research_complete"], "can_subscribe": ["new_lead"], "blackboard_read": ["tasks/*", "context/*"], "blackboard_write": ["context/prospect_*"], "allowed_apis": ["llm", "web_search"], "allowed_credentials": ["brightdata_*"], "can_use_browser": true, "browser_actions": ["*"], "can_manage_cron": true } } ``` ### Permission Fields | Field | Type | Description | | ------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------ | | `can_message` | list | Agents this agent can send messages to | | `can_publish` | list | Pub/sub topics this agent can publish to | | `can_subscribe` | list | Pub/sub topics this agent can subscribe to | | `blackboard_read` | list | Glob patterns for readable blackboard keys | | `blackboard_write` | list | Glob patterns for writable blackboard keys | | `allowed_apis` | list | External APIs accessible through the vault | | `allowed_credentials` | list | fnmatch globs (case-insensitive) for agent-tier credentials; `["*"]` = all agent-tier; system creds always blocked | | `can_use_browser` | bool | Access browser service container (default `false`) | | `browser_actions` | list / null | Allowlist of browser action names; `null` or `["*"]` = all known; `[]` = deny | | `can_spawn` | bool | Spawn ephemeral fleet agents (default `false`; **operator cannot grant this**) | | `can_manage_cron` | bool | Create/update/delete cron jobs | | `can_use_wallet` | bool | Use wallet tools (default `false`; **operator cannot grant this**) | | `wallet_allowed_chains` | list | EVM / Solana chain ids the agent may use | | `wallet_spend_limit_per_tx_usd` | float | Per-transaction USD cap (default 10.0) | | `wallet_spend_limit_daily_usd` | float | Daily USD cap (default 100.0) | | `wallet_rate_limit_per_hour` | int | Wallet ops per hour (default 10) | | `wallet_allowed_contracts` | list | Contract address allowlist (empty = native transfers only) | **Control-plane flags** (six; defaults are `true` for the operator, `false` for workers): `can_manage_fleet`, `can_manage_projects`, `can_edit_agent_config`, `can_view_fleet_metrics`, `can_route_tasks`, `can_request_user_credentials`. **Operator ceiling:** the operator cannot grant `can_spawn=true` or `can_use_wallet=true` to any agent — those flags must be edited directly in `permissions.json` (or via a setting-level tool that bypasses the operator). ## `.env` — API Keys and Runtime Tunables Managed automatically by `openlegion start` and the `/addkey` REPL command. Can also be edited directly. The file is rewritten atomically with `chmod 0o600`; keys are validated against `^[A-Za-z_][A-Za-z0-9_]*$` and `\r\n` is rejected in keys/values to prevent env injection. ```bash theme={null} # System tier — LLM provider keys (mesh-only; never accessible by agents) OPENLEGION_SYSTEM_ANTHROPIC_API_KEY=sk-ant-... OPENLEGION_SYSTEM_OPENAI_API_KEY=sk-... OPENLEGION_SYSTEM_MOONSHOT_API_KEY=sk-... # OAuth blobs (Anthropic Claude CLI / OpenAI Codex CLI imports) # OPENLEGION_SYSTEM_ANTHROPIC_OAUTH={...} # OPENLEGION_SYSTEM_OPENAI_OAUTH={...} # Wallet (per-agent EVM + Solana keys derive from this) # OPENLEGION_SYSTEM_WALLET_MASTER_SEED="word1 word2 ... word24" # OPENLEGION_SYSTEM_WALLET_LIMIT_PER_TX_USD=10 # OPENLEGION_SYSTEM_WALLET_DAILY_USD=100 # OPENLEGION_SYSTEM_WALLET_RATE_LIMIT_PER_HOUR=10 # Agent tier — tool/service keys (access controlled per-agent via allowed_credentials) OPENLEGION_CRED_WEB_SEARCH_API_KEY=... # Channel tokens (presence of the token activates the channel — no separate enable flag) OPENLEGION_CRED_TELEGRAM_BOT_TOKEN=123456:ABC... OPENLEGION_CRED_DISCORD_BOT_TOKEN=MTIz... OPENLEGION_CRED_SLACK_BOT_TOKEN=xoxb-... OPENLEGION_CRED_SLACK_APP_TOKEN=xapp-... OPENLEGION_CRED_WHATSAPP_ACCESS_TOKEN=EAAx... OPENLEGION_CRED_WHATSAPP_PHONE_NUMBER_ID=1234... WHATSAPP_APP_SECRET=... # REQUIRED in prod; otherwise channel raises RuntimeError # Runtime tunables OPENLEGION_LOG_FORMAT=text # "json" (default) or "text" OPENLEGION_TOOL_TIMEOUT=300 # tool execution timeout in seconds OPENLEGION_MAX_ITERATIONS=20 # task-mode iteration cap (clamp 1-100) OPENLEGION_CHAT_MAX_TOOL_ROUNDS=30 # chat per-turn tool rounds (clamp 1-200) OPENLEGION_CHAT_MAX_TOTAL_ROUNDS=200 # chat total rounds (clamp 1-1000) OPENLEGION_ORCHESTRATION_TASKS_V2=1 # durable task records (default ON) OPENLEGION_MAX_AGENTS=0 # 0 = unlimited (but counter-intuitively falls into Basic browser tier) OPENLEGION_MAX_PROJECTS= # unset = unlimited; 0 = disabled; N>0 = capped OPENLEGION_PROJECT_SCOPE_MODE=enforce OPENLEGION_BROWSER_MAX_CONCURRENT= # clamp 1-64; legacy alias MAX_BROWSERS ``` `OPENLEGION_SYSTEM_*` keys are reserved for mesh-tier use (LLM proxy, wallet seed, etc.) and are never accessible to agents. `OPENLEGION_CRED_*` keys are agent-tier — access is controlled per-agent via the `allowed_credentials` field in permissions. Agents reference them as opaque `$CRED{name}` handles in tool arguments; the real value is substituted in the mesh. `OPENLEGION_MAX_AGENTS=0` (default) means "unlimited agents" but **falls into the Basic browser-service tier** (2GB / 512MB / 1.0 CPU / 1 browser). Set it explicitly to scale browser-service resources: 2-5 → Growth, 6-15 → Pro, >15 → Pro Max. ## Other Config Locations | Path | Purpose | | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | `config/mesh.yaml` | Mesh host, LLM defaults, channel routing | | `config/permissions.json` | Per-agent ACL matrix | | `config/cron.json` | Cron / heartbeat state (auto-managed) | | `config/projects/{name}/` | Per-project metadata + `project.md` | | `config/settings.json` | Dashboard-managed browser flags & runtime overrides (4 CAPTCHA-solver-cred names are blacklisted from this file at load — env-only) | | `config/api_keys.json` | Named API keys for the mesh (stored as salted SHA-256 hashes) | | `config/network.yaml` | `no_proxy` exclusion list | | `data/captcha_costs.json` | CAPTCHA spend ledger in millicents (chmod 0o600) | | `data/costs.db` | Per-agent + per-project LLM cost ledger (SQLite WAL) | | `.env` | API keys / credentials | ## Browser Flags (`config/settings.json` / env) 53 entries in `KNOWN_FLAGS` group into: profile / network (`BROWSER_OS`, `_LOCALE`, `_UA_VERSION`, `_DEVICE_PROFILE`, HTTP/HTTPS proxy fields, egress allowlist), CAPTCHA solver, CAPTCHA timeouts, CAPTCHA solver proxy, CAPTCHA pacing, CAPTCHA cost caps, operator kill switches (`BROWSER_DOWNLOADS_DISABLED`, `BROWSER_NETWORK_INSPECT_DISABLED`, `BROWSER_COOKIE_IMPORT_DISABLED`, `CAPTCHA_DISABLED`), snapshot / screenshot, behavior recorder, upload staging, session continuity, browser concurrency. The four CAPTCHA-solver credential names (`CAPTCHA_SOLVER_KEY`, `_SECONDARY`, `CAPTCHA_SOLVER_PROXY_LOGIN`, `_PASSWORD`) are `_ENV_ONLY_FLAGS` — they are stripped from `config/settings.json` at load and must be supplied via environment. # Self-Hosting Source: https://docs.openlegion.ai/self-hosting Install OpenLegion and run your own agent fleet Run OpenLegion on your own machine with full control over your infrastructure. Prefer not to manage infrastructure? [Get started with managed hosting](/quickstart) — a dedicated server provisioned for you in minutes. ## Requirements * **Python 3.10+** (CI tested on 3.11 and 3.12) * **Docker** running. Docker Desktop on macOS / Windows; on Linux either Docker Desktop or docker engine with your user in the `docker` group (`sudo usermod -aG docker $USER`, then re-login) * **One LLM API key** ([Anthropic](https://console.anthropic.com/) / [OpenAI](https://platform.openai.com/api-keys) / [Moonshot](https://platform.moonshot.cn/) / any of 100+ providers via LiteLLM), or run [Ollama](https://ollama.com) locally for a keyless setup * **OS:** macOS, Linux, or Windows (PowerShell + Docker Desktop with WSL 2) ## Install and launch ```bash macOS / Linux theme={null} git clone https://github.com/openlegion-ai/openlegion.git && cd openlegion ./install.sh # checks deps, creates .venv, makes CLI global ``` ```powershell Windows theme={null} git clone https://github.com/openlegion-ai/openlegion.git cd openlegion powershell -ExecutionPolicy Bypass -File install.ps1 ``` First install downloads \~70 packages and takes 2-3 minutes. The first `openlegion start` then builds two Docker images (\~1 min for the agent image, \~3 min for the browser service). ```bash theme={null} openlegion start # inline setup on first run, then launch agents ``` On first run (no credentials configured), `openlegion start` detects this and offers three paths: 1. **Quick setup here** — pick a provider, paste your API key (validated), choose a model, then optionally create your first agent or apply a team template 2. **Open the dashboard** — configure everything via the web UI at `http://localhost:8420/` 3. **Skip** — start the runtime and use `/addkey` later in the REPL After setup, it launches the mesh host on port 8420, spins up agent containers, and drops you into the interactive REPL. Start chatting with your agents immediately. **Team Templates** (offered during first-run setup): | Template | Agents | Use Case | | -------------------- | ------------------------------- | ---------------------------------------------------------------------- | | `starter` | assistant | Single general-purpose agent | | `content` | researcher, writer | Blog / social / email from briefs | | `deep-research` | scout, analyst | Multi-source synthesis with citations | | `devteam` | pm, engineer, reviewer | Software development team | | `monitor` | watcher, analyst | Always-on monitoring | | `sales` | researcher, qualifier, outreach | Sales pipeline | | `competitive-intel` | tracker, analyst | Competitor pricing/product tracking | | `lead-enrichment` | enricher, verifier | Lead list research | | `price-intelligence` | crawler, analyst | Price monitoring with anti-bot browser | | `review-ops` | collector, responder | G2 / Trustpilot / Capterra / App Store / Google reviews + reply drafts | | `social-listening` | listener, reporter | Reddit / HN / X competitor pain-point monitor | | `research` | researcher | General-purpose researcher | | `opportunity-finder` | scout, evaluator, modeler | Gap-scout + evaluation + modeling | Once the engine is running, open `http://localhost:8420/` for the web dashboard (four top-nav tabs: **Chat / Work / Team / Settings**). ## Common operations ```bash theme={null} # Check status / health of agents openlegion status # --wide for cost, --watch N to auto-refresh, --json for machine output # Run in background (daemon mode) openlegion start -d # Log: .openlegion.log | PID: .openlegion.pid openlegion chat researcher # Connect to a running agent from another terminal openlegion stop # Clean shutdown of mesh + all openlegion_* containers # microVM mode (Docker Desktop 4.58+; falls back to standard Docker on failure) openlegion start --sandbox # Projects, tasks, pending actions openlegion projects # List active projects openlegion project # Single project details openlegion tasks # List durable task records (V2 orchestration) openlegion pending # List open operator actions awaiting confirmation openlegion confirm # Confirm a pending action openlegion cancel # Cancel a pending action # Wallet (EVM + Solana) openlegion wallet init # Generate a 24-word BIP-39 seed (shown ONCE; stored in .env) openlegion wallet show # Show wallet addresses on all chains # Reset (stops + wipes config/, data/, agent skills, Docker volumes; keeps .env) openlegion reset ``` Inside the REPL, `/add` hot-adds a new agent to the running fleet, `/edit` modifies one, and `/remove` removes one. See the [CLI Reference](/reference/cli) for the full REPL command surface. ## Connect a channel Channels auto-start when their token resolves — there is no separate enable flag. Add the credential to `.env` (or use `/addkey` inside the REPL): ```bash theme={null} # Telegram — bot token from @BotFather OPENLEGION_CRED_TELEGRAM_BOT_TOKEN=123456:ABC... # Discord — bot token; requires Message Content Intent + bot/applications.commands scopes OPENLEGION_CRED_DISCORD_BOT_TOKEN=MTIz... # Slack — Socket Mode, both tokens required (no public URL needed) OPENLEGION_CRED_SLACK_BOT_TOKEN=xoxb-... OPENLEGION_CRED_SLACK_APP_TOKEN=xapp-... # WhatsApp — Cloud API (Graph v21.0); text-only, non-text dropped OPENLEGION_CRED_WHATSAPP_ACCESS_TOKEN=EAAx... OPENLEGION_CRED_WHATSAPP_PHONE_NUMBER_ID=1234... ``` On the next `openlegion start`, the channel prints a pairing code; send `/start ` from your account to the bot to link it as the owner. **WhatsApp in production** must set `WHATSAPP_APP_SECRET` for HMAC webhook signature verification. Without it (when `MESH_AUTH_TOKEN` is also set), the channel startup raises a `RuntimeError`. Also strongly recommended: set `OPENLEGION_SYSTEM_WHATSAPP_VERIFY_TOKEN` so the verify token stays stable across restarts. **Webhooks** are created from the engine dashboard (System tab → Integrations); there is no `openlegion webhooks` command. Each hook is reachable at `POST /webhook/hook/` with an optional HMAC-SHA256 signature; bodies are capped at 1 MB and truncated to 3000 chars when dispatched to agents. ## Next steps Understand how agents, the mesh host, and trust zones work together. All CLI commands and interactive REPL commands. Customize agents, permissions, models, and budgets. How agents collaborate: blackboard, pub/sub, handoffs, lanes.