Docs
Configuration
Last Light layers configuration at startup: the packaged
config/default.yaml (non-secret defaults), then an optional
$LASTLIGHT_OVERLAY_DIR/config.yaml overlay, then environment
variables (via .env in local dev, or instance/secrets/.env
in production). The authoritative source is src/config.ts in the repo;
this page mirrors it.
Config files & overlay
Non-secret config — managed repos, routes, models, variants, approvals, and
disabled.* — lives in YAML. The public config/default.yaml
ships safe defaults (and an empty managedRepos list).
Your deployment overrides go in an overlay selected by
LASTLIGHT_OVERLAY_DIR — the docker-compose stack mounts an
instance/ folder there as /app/instance.
| Variable | Required | Default | Description |
|---|---|---|---|
LASTLIGHT_OVERLAY_DIR | No | — | Trusted overlay root. Layers config.yaml over the defaults and overlays assets under workflows/, workflows/prompts/, skills/, agent-context/ (overlay wins by logical name). Secrets are read from its secrets/ subdir. Read at startup — docker compose restart agent to apply. |
Merge rules: maps (models, variants, routes,
approval) deep-merge over the defaults; arrays (managedRepos,
disabled.*) replace; environment variables override both. Secrets
(GitHub App key, provider API keys, Slack tokens, admin secret) stay env-only and
never appear in the dashboard Config tab, which shows the
Default / Overlay / Merged non-secret config.
GitHub App
Required if you want to manage repos (not required for messaging-only mode).
| Variable | Required | Default | Description |
|---|---|---|---|
GITHUB_APP_ID | Yes | — | Numeric GitHub App ID from the app settings page. |
GITHUB_APP_PRIVATE_KEY_PATH | Yes | — | Path to the .pem file you downloaded when creating the app. |
GITHUB_APP_INSTALLATION_ID | Yes | — | Installation ID — from the URL after installing the app on a repo. |
WEBHOOK_SECRET | Yes | — | Matches the webhook secret configured in your GitHub App. Used to verify webhook signatures. |
BOT_LOGIN | No | last-light[bot] | Bot login used to filter out the harness's own events from its own event stream. |
Models (agentic-pi)
The agent runtime is agentic-pi
(workflow phases) plus @earendil-works/pi-ai (in-process chat). It runs on any model
the pi coding agent supports —
Claude, GPT, Gemini, Llama, and more, as openai/…, anthropic/… or openrouter/<vendor>/<model> strings.
Set whichever provider API keys match your selected models — one OpenRouter
key covers most of them if you'd rather have a single billing surface.
| Variable | Required | Default | Description |
|---|---|---|---|
OPENAI_API_KEY | If using OpenAI | — | OpenAI API key. Required if LASTLIGHT_MODEL (or any entry in LASTLIGHT_MODELS) resolves to an OpenAI model. |
ANTHROPIC_API_KEY | If using Anthropic | — | Anthropic API key. Required if any active model is anthropic/…. |
OPENROUTER_API_KEY | If using OpenRouter | — | OpenRouter API key (sk-or-…). Required if any active model is openrouter/<vendor>/<model>. One key gives you Claude, GPT, Gemini, Llama, Mistral, DeepSeek, etc. through a single endpoint; OpenRouter takes a small per-token markup over going direct. Sign up at openrouter.ai/keys. |
LASTLIGHT_MODEL | No | anthropic/claude-sonnet-4-6 | Default model used when no per-task override matches. Must be a provider/model string pi-ai recognises. |
LASTLIGHT_MODELS | No | — | Per-task-type model overrides as JSON. Keys match phase names or skill types. Do not override chat with a small model — small models tend to refuse tool calls. Example: {"chat":"openai/gpt-5.1-mini","architect":"openai/gpt-5.5"} |
LASTLIGHT_THINKING | No | — | Catch-all reasoning-effort default. pi-ai translates it to the right per-provider knob (OpenAI reasoning_effort, Anthropic thinking budget, etc.). Allowed values: off | minimal | low | medium | high | xhigh. |
LASTLIGHT_THINKINGS | No | — | Per-task thinking-level overrides as JSON, same key scheme as LASTLIGHT_MODELS. Example: {"architect":"high","reviewer":"high","triage":"minimal"}. |
LASTLIGHT_SANDBOX | No | gondolin | Workflow sandbox backend. gondolin (default) runs each phase in a QEMU micro-VM (HVF on macOS, KVM on Linux — no Docker needed). docker runs each phase in a sibling Docker container (requires the lastlight-sandbox:latest image). smol (experimental) runs each phase in a smolvm micro-VM via the smolvm CLI — stronger isolation, native per-host egress; local-only, needs a host hypervisor. none runs in-process with no isolation — dev only. |
MAX_TURNS | No | 200 | Maximum agent turns per invocation. Reserved (kept for API stability). |
OPENCODE_* names from the
OpenCode era are still read as fallbacks for the matching
LASTLIGHT_* name — OPENCODE_MODEL →
LASTLIGHT_MODEL, OPENCODE_MODELS →
LASTLIGHT_MODELS, OPENCODE_VARIANT →
LASTLIGHT_THINKING, OPENCODE_VARIANTS →
LASTLIGHT_THINKINGS. Existing .env files keep
working; rename at your leisure.
Runtime
| Variable | Required | Default | Description |
|---|---|---|---|
PORT / WEBHOOK_PORT | No | 8644 | Port the webhook listener binds to. |
STATE_DIR | No | ./data | Base directory for persistent state (DB, sessions, sandboxes, logs). Mount as a Docker volume in production. |
DB_PATH | No | $STATE_DIR/lastlight.db | Override the SQLite database path. |
WORKFLOW_DIR | No | ./workflows | Directory containing YAML workflow definitions. |
LASTLIGHT_SESSIONS_DIR | No | $STATE_DIR/agent-sessions | Directory the dashboard reads sandbox + chat session JSONLs from. event-shim.ts writes Claude-SDK-style envelope jsonl under projects/<slug>/. |
SANDBOX_DATA_VOLUME | No | lastlight_agent-data | Used only when LASTLIGHT_SANDBOX=docker. Either a Docker named volume or a host path (starts with /, ./, ../, ~) bind-mounted as /data in each sandbox. |
SMOLVM_BIN | No | smolvm | Used only when LASTLIGHT_SANDBOX=smol. Path to the smolvm CLI. |
SMOLVM_IMAGE | No | lastlight-sandbox:latest | Used only when LASTLIGHT_SANDBOX=smol. An OCI image reference, or a local docker save archive (./img.tar) / rootfs dir — the archive form loads offline (no registry), so the locally-built sandbox image works under the strict egress allowlist. |
LASTLIGHT_LOCAL_DEV | No | — | Set to 1 to prevent git-auth.ts from writing to ~/.gitconfig. Used by scripts/dev-local.sh. |
BOOTSTRAP_LABEL | No | lastlight:bootstrap | Label applied to issues that exist solely to set up missing guardrails in a target repo. |
MAX_CONCURRENT_WORKFLOWS | No | 4 | Global cap on how many sandboxed workflow runs execute at once. Excess triggers are persisted as queued in workflow_runs and admitted FIFO as slots free — keeping a burst of events from swamping the host. Overlay key: concurrency.maxWorkflows. |
MAX_QUEUE_WAIT_MS | No | 3600000 (1 hr) | How long a queued run may wait before it's dropped (cancelled with a "waited too long" notice). Overlay key: concurrency.maxQueueWaitMs. |
LASTLIGHT_HOME | No | ~/lastlight | CLI-side only. Working directory for the host-local lastlight server lifecycle commands (checkout + instance/ overlay + override symlink). Resolves from --home → this env → saved serverHome → default. |
LASTLIGHT_GIT_SHA / LASTLIGHT_BUILD_DATE | No | empty → "unknown" | Core git SHA + build date baked into the agent image (Dockerfile ARGs; lastlight server update stamps them). Surfaced by GET /admin/api/server/info for the dashboard "update available" banner. |
LASTLIGHT_CORE_VERSION | No | overlay deploy.version; unset = track main | Override the overlay's core-version pin (a git tag/ref). lastlight server update/setup checks core out at it and the drift banner compares against it. main/latest/unset tracks main. See Production. |
Approval gates
| Variable | Required | Default | Description |
|---|---|---|---|
APPROVAL_GATES | No | — |
Comma-separated list of gate names to enable. Gate names match
approval_gate: fields declared in workflow YAML (e.g.
post_architect, post_reviewer). A gate only
pauses the run if its name appears here, so you can ship workflows with
gates pre-declared and enable them per environment.
|
Build assets
The per-phase build handoff docs — architect-plan.md,
status.md, executor-summary.md,
reviewer-verdict.md, guardrails-report.md, and the
explore-* docs — can live in one of two places, selected by
buildAssets.location in YAML (or the LASTLIGHT_BUILD_ASSETS
env var). With repo (the default) they're committed into the target
repo under .lastlight/<issueKey>/ and ride the working branch —
the historical behaviour. With server they're externalized to the
Last Light host instead, never committed into the target repo: injected into each
phase from outside the repo and harvested back after, PR-body links point at the
dashboard's Artifacts view rather than GitHub blob URLs, and the
admin API serves them read-only at /admin/api/artifacts.
| Variable | Required | Default | Description |
|---|---|---|---|
LASTLIGHT_BUILD_ASSETS | No | repo | Where build handoff docs live: repo commits them into the target repo under .lastlight/<issueKey>/ on the working branch; server externalizes them to the Last Light host and never commits them into the target repo. Equivalent to the buildAssets.location config key. |
BUILD_ASSETS_DIR | No | $STATE_DIR/build-assets | Server-mode store root. Docs are written under <owner>/<repo>/<issueKey>/*.md. Only used when LASTLIGHT_BUILD_ASSETS=server. |
Admin dashboard
| Variable | Required | Default | Description |
|---|---|---|---|
ADMIN_PASSWORD | No | — | If set, the dashboard requires password login. |
ADMIN_SECRET | No | random | HMAC secret used to sign session tokens. Set this to a stable value in production so sessions survive restarts. |
Slack (optional)
Two independent feature groups — see Slack integration for setup.
| Variable | Required | Default | Description |
|---|---|---|---|
SLACK_BOT_TOKEN | No | — | Bot User OAuth Token (xoxb-...). Presence of this var enables the Slack connector. |
SLACK_MODE | No | auto | Receive transport: webhook (HTTP Events API) or socket (Socket Mode). Auto-detected when unset: webhook if SLACK_SIGNING_SECRET is set, else socket. |
SLACK_SIGNING_SECRET | Yes (webhook mode) | — | Events API signing secret. Slack POSTs events to /webhooks/slack; delivery is retried (at-least-once). |
SLACK_APP_TOKEN | Yes (socket mode) | — | App-Level Token (xapp-...) for the Socket Mode dev fallback. |
SLACK_ALLOWED_USERS | No | — | Comma-separated Slack user IDs allowed to interact with the bot. |
SLACK_DELIVERY_CHANNEL | No | — | Channel ID where cron health reports are posted. |
SLACK_OAUTH_CLIENT_ID | No | — | Enables "Login with Slack" on the dashboard. |
SLACK_OAUTH_CLIENT_SECRET | Yes (if client id set) | — | OAuth client secret. |
SLACK_OAUTH_REDIRECT_URI | Yes (if client id set) | — | Must exactly match a redirect URL configured on the Slack app, typically https://your-host/admin/api/oauth/slack/callback. |
SLACK_ALLOWED_WORKSPACE | No | — | Restrict OAuth login to a single Slack workspace (team ID or domain). |
OpenTelemetry (optional)
Telemetry is disabled by default. Set LASTLIGHT_OTEL_ENABLED=true and
point the standard OTEL_* exporter vars at your collector. See
Observability for what's exported, content
redaction, and how sandbox telemetry is routed per backend.
| Variable | Required | Default | Description |
|---|---|---|---|
LASTLIGHT_OTEL_ENABLED | No | false | Master switch. Standard OTEL_* env vars alone do not enable telemetry — this must be true. |
LASTLIGHT_OTEL_SERVICE_NAME | No | lastlight | OTEL service name. Falls back to OTEL_SERVICE_NAME if unset. |
LASTLIGHT_OTEL_INCLUDE_CONTENT | No | false | Include prompt / message / tool-result content in spans (truncated). Sensitive — only enable with a trusted collector. Default exports metadata only. |
LASTLIGHT_OTEL_FORWARD_TO_SANDBOX | No | true | Emit telemetry from inside workflow sandboxes too. On the docker backend this routes through an in-network collector; on gondolin/none it forwards OTEL_* env directly. Set false to keep telemetry harness-only. |
LASTLIGHT_OTEL_STRICT | No | false | Throw on OTEL init/export-setup failure instead of warning and continuing without telemetry. |
LASTLIGHT_OTEL_METRICS_ENABLED | No | true | Export OTLP metrics. Set false for a traces-only backend that rejects the metrics signal (e.g. Arize Phoenix) — the metric reader is never started, so nothing hits a metrics endpoint that would 404/415. Traces still flow. Overlay key: otel.metrics. |
LASTLIGHT_OTEL_COLLECTOR_HOSTS | No | — | Comma-separated collector hostnames added to the strict sandbox egress allowlist. Used only by the gondolin backend — the docker backend reaches its collector internally and ignores this. |
OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_*_ENDPOINT | No | — | Standard OTLP/HTTP collector endpoint(s). Used by the harness directly, and as the re-export target for the in-network collector on the docker backend. |
OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_*_HEADERS | No | — | Standard OTLP headers (e.g. an auth token). Secret/env-only — never shown in the dashboard Config tab, and never forwarded into a sandbox on the docker backend. |
OTEL_SERVICE_NAME, OTEL_RESOURCE_ATTRIBUTES | No | — | Standard OTEL resource configuration (e.g. deployment.environment=prod). |