Configuration
PrismaLens reads its settings from a prismalens.config.yaml file. Nothing is
required to get started — every setting has a built-in default — but you’ll
usually want one to point the CLI at your telemetry stack and pick a harness.
Layered resolution
Section titled “Layered resolution”Settings are merged from five layers, each one overriding the last (objects are deep-merged; arrays are replaced wholesale):
- Built-in defaults — baked into the CLI, so every field has a sane value even with no config file at all.
- Global layer — an OS-specific config directory, e.g. on Linux/WSL
~/.config/prismalens/config.yaml(honorsXDG_CONFIG_HOMEif set). A good home for BYO-key values you want on every project. - Project layer — the file passed via
--config, or, if that flag is omitted, the nearestprismalens.config.yamlfound by walking up from the current directory to the filesystem root. - Project-local layer —
prismalens.config.local.yaml, sitting beside the project config. Meant to be gitignored — a per-checkout override that doesn’t leak into the shared project file. - CLI flags —
--model,--harness, and similar flags oninvestigatewin over every file layer.
Scaffolding a config
Section titled “Scaffolding a config”prismalens initWrites a commented prismalens.config.yaml into the current directory, with
every value sourced live from the schema’s defaults — it can’t drift from what
the CLI actually uses. If a config file already exists, init leaves it
untouched and does nothing.
Once you have a config, run prismalens doctor to check that it and your
environment are ready for an investigation.
Environment-variable interpolation
Section titled “Environment-variable interpolation”Any string value in the YAML can reference an environment variable with
${VAR_NAME} syntax. It’s resolved after all layers are merged and before the
config is validated:
telemetry: apiUrl: ${PRISMALENS_API_URL}If a referenced variable isn’t set, loading the config fails with an error naming the missing variable — there’s no silent fallback to an empty string.
Annotated example
Section titled “Annotated example”# Repository under investigation (owner/name). Auto-detected from the git# remote when omitted.# repo: owner/name
# Tier-2 harness backend the supervisor rents.agent: # Which agent harness to rent: deepagents | claude-code # (codex is a reserved value — planned, not yet available) default: claude-code
# BARE model id, e.g. "gpt-oss:120b" — the chosen harness applies its own # provider prefix. Omit to let the harness pick its own default. # model: gpt-oss:120b
# Permission posture (the one dial PrismaLens exposes; it's translated into # each harness's native settings): read-only | supervised | auto | dangerous # permissions: # mode: read-only
# Isolation boundary the harness runs in: auto | process | srt | e2b # - process: the always-on cooperative floor (no OS enforcement). # - srt: an enforced OS-level sandbox boundary; fails loudly if unavailable. # - auto (default): tries srt, but only after a self-check confirms its # network path is actually alive; otherwise degrades to the process floor # and says so — never a silent downgrade. # - e2b: an enforced cloud sandbox (needs E2B_API_KEY); only used when # explicitly requested, never chosen by `auto`. sandbox: auto
# Best-effort resource caps on the harness run. Every field is optional and # unset means "no cap" — nothing here is enforced unless you set it. # limits: # wall_clock_ms: 60000 # deadline, enforced by every sandbox mode # memory_mb: 512 # best-effort; only applied when sandbox is srt # cpu_cores: 2 # best-effort; only applied when sandbox is srt
# Maximum number of turns (tool calls) the harness can make before being halted. # Unset means the harness uses its own default. # max_turns: 40
# Tier-1 synthesis provider and model. Used to synthesize the final report.# synth:# provider: openai # overrides auto-selection# model: gpt-5-mini # omit to let provider pick default# base_url: https://... # required for 'custom' provider
# Read-only telemetry + app endpoints the harness may query. Point these at# your stack; the defaults assume a local dev setup.telemetry: prometheusUrl: http://localhost:9090 alertmanagerUrl: http://localhost:9093 apiUrl: http://localhost:5000
# Read-only log-query system the harness may curl (Loki, Elasticsearch, ...).# logs:# kind: loki# url: http://localhost:3100
# Service catalog. The alert's `service` label (else `namespace`/`job`)# selects an entry; the matched service's repo/tier/dependencies ride along# into the investigation so a single-alert run isn't context-free.# services:# checkout:# tier: tier-1# repo: acme/checkout# depends_on: [payments, inventory]
# Where runs, events, and reports are stored on disk (a prismalens.db record# store plus a runs/<runId>/ workspace dir per run).workspace: base_dir: ~/.prismalens
# Unattended intake for `pl listen` (token-authed Alertmanager webhooks). The# token is REQUIRED to start listening — keep it out of the file via env# interpolation. See the reference below and the Unattended alerts guide.# listen:# token: ${PRISMALENS_LISTEN_TOKEN} # required for `pl listen`# port: 4181 # local intake port# grouping_window_ms: 60000 # debounce firing alerts into one group# max_pending: 8 # queue depth before intake 503s# slack_webhook_url: ${PRISMALENS_SLACK_WEBHOOK_URL} # optional report delivery# caps:# max_concurrent: 2 # investigations running at once# max_per_hour: 10 # investigations started per rolling hour# max_turns: 40 # per-run turn ceiling (unset ⇒ harness default)
# Per-harness native passthrough — untyped, forwarded straight to the rented# harness. For `deepagents` (the deepagents-acp binary, driven over ACP),# `args` is appended verbatim to its CLI invocation.# harnesses:# deepagents:# native:# args: [--memory, ./AGENTS.md]agent.sandbox
Section titled “agent.sandbox”| Value | Meaning |
|---|---|
auto (default) | Prefers the enforced srt boundary, but only after confirming its network path actually works — a known WSL networking mode can leave it looking healthy while silently blocking all traffic. Falls back to process when that check fails, and reports the fallback rather than hiding it. |
process | The cooperative floor — no OS-level enforcement, just the harness’s own read-only behavior. |
srt | Requires the enforced sandbox; throws if it isn’t available rather than quietly falling back. |
e2b | An enforced cloud sandbox. Requires E2B_API_KEY and must be selected explicitly — auto never reaches for it. |
agent.limits
Section titled “agent.limits”All three fields are optional, and an unset field means “no cap” — nothing is
silently enforced. wall_clock_ms is enforced by every sandbox mode;
memory_mb and cpu_cores only take effect under sandbox: srt.
| Key | Type | Enforced by |
|---|---|---|
wall_clock_ms | integer, milliseconds | every sandbox mode |
memory_mb | integer, megabytes | srt only |
cpu_cores | number (fractional allowed) | srt only |
listen
Section titled “listen”Settings for pl listen, the unattended Alertmanager webhook receiver. token
is the only key listen can’t start without; everything else has a default. For
the end-to-end setup — the Alertmanager side, grouping, and Slack — see
Unattended alerts.
| Key | Type | Default | What it does |
|---|---|---|---|
token | string | — (required to start) | Shared bearer token every webhook request must present. Set it via env interpolation (${PRISMALENS_LISTEN_TOKEN}), never as a literal. Unset ⇒ pl listen refuses to start. |
port | integer | 4181 | Local intake port. Bound on 127.0.0.1 only. 0 picks an ephemeral port. |
grouping_window_ms | integer (1000–120000) | 60000 | Debounce window: firing alerts arriving within it are grouped into one investigation. |
max_pending | integer | 8 | Alerts waiting or running before intake replies 503 (an empty queue always admits a payload, so an oversized group can’t wedge permanently). |
slack_webhook_url | URL | — | Slack incoming-webhook for report delivery. Unset ⇒ delivery disabled. Set via env interpolation (${PRISMALENS_SLACK_WEBHOOK_URL}). |
caps.max_concurrent | integer | 2 | Investigations allowed to run at the same time. |
caps.max_per_hour | integer | 10 | Investigations started per rolling 60-minute window. |
caps.max_turns | integer | — | Per-run turn ceiling handed to the default harness. Unset ⇒ the harness’s own default. |
Over-cap groups aren’t dropped — they’re recorded as terminal suppressed runs
(visible in pl status) with the cap that turned them
away.
services
Section titled “services”A service catalog keyed by service name (ADR-0015). When an alert fires, its
service label — falling back to namespace then job — selects the matching
entry, and that service’s repo/tier/dependencies ride into the investigation so a
single-alert run isn’t context-free. pl investigate --service <name> selects an
entry explicitly, overriding the alert’s label.
services: checkout: tier: tier-1 repo: acme/checkout depends_on: [payments, inventory]| Key | Type | Default | What it does |
|---|---|---|---|
<name> | map key | — | The service name matched against the alert’s service/namespace/job label (or --service). No match ⇒ the run proceeds from the bare alert. |
tier | string | — | Free-form criticality label (e.g. tier-1) carried into the investigation context. |
repo | string | — | owner/name slug or a local path the harness reads for this service. |
depends_on | string[] | [] | Direct dependency service names — a blast-radius seed handed to the investigation. |
harnesses
Section titled “harnesses”Per-harness native passthrough (ADR-0017), keyed by harness id. Each entry
has a single native map, forwarded untyped to that harness — an escape hatch
for harness-specific knobs PrismaLens doesn’t model. Which keys a harness
actually consumes differs:
| Harness | What native does |
|---|---|
deepagents | native.args is appended verbatim to the deepagents-acp CLI invocation. native.shellAllowList and native.sandbox are rejected with an error — the published deepagents-acp binary has no such flags; use the --sandbox port for real isolation instead. |
claude-code | native is merged into the Claude Agent SDK query options for the run. |
harnesses: deepagents: native: args: [--memory, ./AGENTS.md]The permission posture (agent.permissions.mode) and the isolation boundary
(agent.sandbox) are not native knobs — they’re first-class dials PrismaLens
translates itself. Don’t try to set them through native.
Run storage and retention
Section titled “Run storage and retention”Every run persists to workspace.base_dir (~/.prismalens by default): a
prismalens.db record store plus a runs/<runId>/ workspace directory per run.
These grow unbounded — PrismaLens prunes nothing on its own, and the
footprint climbs much faster under pl listen, where alerts investigate
themselves around the clock.
There’s no built-in retention policy or prune command yet, so cleanup is
manual:
# Drop old per-run workspace dirs (keeps the record store)find ~/.prismalens/runs -maxdepth 1 -mtime +30 -type d -exec rm -rf {} +
# Full reset — removes all runs, events, and reportsrm -rf ~/.prismalensPoint workspace.base_dir at a volume with room to spare if you run pl listen
long-term, and keep an eye on prismalens.db (it holds every run’s metadata,
events, and report). Deleting ~/.prismalens wholesale is also the uninstall
cleanup step — nothing lives outside it except your config file.
Next steps
Section titled “Next steps”- Commands for the full flag reference on each command
- Providers & harnesses for what each
agent.defaultvalue needs installed