Configuration reference

September 20, 2026 · View on GitHub

Every key, its default, what a project file may change, the status line templates, and the update note. The README covers the common cases; guards.md explains what each threshold does.

Contents: User config · Project config · Environment · Status line and trace sidebar · After updating the package

User config

User file ~/.pi/agent/pi-warden/config.json (owner-only). /warden config opens it in Pi's editor. Missing keys use these defaults:

{
  "enabled": true,
  "typesafe": false,
  "mode": "steer",
  "timeoutMs": 5000,
  "maxRequests": 500,
  "action": {
    "enabled": true,
    "tools": ["bash", "powershell", "ctx_execute", "ctx_batch_execute", "ctx_execute_file", "write", "edit"],
    "failOpen": true,
    "irreversible": { "warn": 0.5, "confirm": 0.7 },
    "offTask": { "warn": 0.6, "steer": 0.85 },
    "intentMismatch": 0.9,
    "visibleMismatch": 0.8,
    "shouldProceed": { "hold": 0.6 },
    "feedbackLog": true,
    "commandRules": [],
    "commandDenyRules": [],
    "exemptRules": [],
    "pathRules": [],
    "armingRules": []
  },
  "rules": {
    "enabled": true,
    "threshold": 0.7,
    "files": [],
    "fallback": true,
    "maxChars": 8000,
    "exclude": [],
    "skip": [],
    "sensitivePaths": {}
  },
  "slop": {
    "enabled": true,
    "threshold": 0.7,
    "prose": { "enabled": true, "audience": "technical", "threshold": 0.7, "trend": 2, "minChars": 200 }
  },
  "security": { "enabled": true, "threshold": 0.7 },
  "stuck": { "enabled": true, "window": 12, "minFailures": 3, "cooldown": 3, "sameStrategy": 0.7, "nudge": true },
  "done": { "enabled": true, "claimsDone": 0.7, "nudge": true },
  "context": { "enabled": true, "tailMinChars": 12000, "confidence": 0.8, "duplicateMinChars": 2000, "recallTool": "auto", "formatConfidence": 0.7 },
  "runaway": { "enabled": true, "repeats": 4, "thinkingRepeats": 10, "minChars": 400, "recover": true },
  "notify": { "enabled": false, "cooldownMs": 10000, "command": [] },
  "subagent": { "enabled": true, "wake": true, "threshold": 0.8, "cooldownMs": 120000 },
  "widget": { "enabled": true, "placement": "aboveEditor", "shortcut": "ctrl+shift+w", "panelWidth": "40%" },
  "steerVisible": false,
  "notices": false,
  "steerBudget": 3,
  "typesafeBackend": "typesafe"
}
KeyMeaning
enabledMaster switch for the extension.
typesafeConsent to send requests to TypeSafe. Set by /warden enable; only the user file or PI_WARDEN_ENABLED=1 can grant it.
typesafeBackendThe judgment service: "typesafe" (default) or "openrouter". User file only — a project must not redirect judgments.
modesteer (hold goes back to the agent), confirm (dialog for you), advise (never holds).
timeoutMsPer-request timeout. On timeout the call is allowed with a warning when action.failOpen is true.
maxRequestsPer-session request budget. When spent, pi-warden says so once and continues with offline checks.
action.toolsTools the action guard inspects. Add your own shell-like tools here.
action.irreversiblewarn and confirm (hold) thresholds on P(irreversible).
action.offTaskwarn and steer thresholds on P(off-task). Off-task never holds.
action.intentMismatchP(call differs from the agent's stated plan) that warns and tells the agent, on calls that can change something.
action.visibleMismatchLower mismatch threshold for commands whose effect is visible outside the working tree (commit, push, publish, install, launch).
action.shouldProceed{ hold } threshold. When P(should_proceed) drops below hold (default 0.6), the agent is steered to pause and ask the user. Steers never holds. Calibrated: AUC 0.07 (inverted) against regret, 0.64 against rejected turns.
action.feedbackLogWrite each judged call and its outcome to ~/.pi/agent/pi-warden/holds/; never the command.
action.commandRulesUser-defined command rules: { id, pattern, severity: "warn" | "confirm" | "deny", action?, message?, caseSensitive? }. Patterns match the data-text-stripped command, so heredoc bodies and commit messages do not fire them. confirm defaults to action: "dialog" (a prompt for you, in every mode); action: "hold" restores steer semantics. Patterns are JavaScript regexes matched against full commands, so a pattern with nested quantifiers can be slow on long commands — a pathological one is self-inflicted. User file only.
action.commandDenyRulesThe same shape, shorthand for severity: "deny": the call is blocked with no dialog and no TypeSafe request. User file only.
action.pathRulesUser-defined path rules: { id, paths, access, tools, action, message?, onlyIfExists?, regex? }. paths are globs (** any depth, * one segment, ? one character, ~ expands; regex: true reads them as regexes). access is the dimension: "none" any touch matches, "read" writes are held and reads flow, "write" reads are held and writes flow (an append-only log). tools picks the surface: file tools by name check the structured path field; "*" also matches bash commands — a none rule fires on a mention anywhere in the data-text-stripped command, and the write side is matched only at redirect (>, >>) and tee targets, never in arbitrary argv. action: note (the default; the agent is told after the fact), warn, confirm (a dialog, in every mode), block (deny, no dialog). onlyIfExists defaults true, so phantom paths do not fire. A read-scoped rule needs read in action.tools, which does not inspect the read tools by default. User file only.
action.armingRulesUser-defined arming rules: { id, when: { edited, regex?, tools? }, arms: { command, for?, caseSensitive? }, action, message? }. Editing a file matching a when.edited glob arms the rule's arms.command regex for arms.for (default "10m"; accepts "30s", "2h", or ms). While armed, commands matching the regex fire the rule's action: confirm (dialog), hold (steer), block (deny). State lives for the rule's window within a session, refreshed on each matching edit, cleared on session_start, and shown in /warden status. Approving the dialog approves that call, not the window; the dialog re-fires for each matching command while the rule is armed. User file only.
action.exemptRulesBuilt-in or user rule ids to exempt, e.g. ["infra-destroy"] for a workflow whose kubectl delete is routine; also rm-recursive / rm-rf / rm-recursive-dangerous-target (the rm classifier) and sensitive-path. An id naming nothing is inert and reported once at startup. Exempting sensitive-path removes the only deterministic credential-touch signal, Jev questions aside. User file only.
action.escalationThresholdJev confidence above which a violation's severity is escalated in the blast-radius and rules-guard escalation paths. Default 0.85. Escalation fires when confidence strictly exceeds this threshold; setting it to 1 effectively disables escalation since noul confidence cannot exceed 1.
rules.*Rules source, threshold, path globs, sensitive-path notes. See guards.md → Rules.
slop.*Code slop threshold and reply (prose) checks. prose.audience is technical, plain, or free text.
security.thresholdWritten-code risk and tool-output injection threshold.
stuck.*Window of tool results kept, failures before a check, cooldown between checks, same-strategy threshold.
done.*Completion-claim threshold and whether the agent gets a follow-up turn.
context.*Compression thresholds, retention confidence, duplicate size, recall tool.
runaway.*Repeat counts that abort a reply, minimum size, whether the agent gets one recovery turn.
notify.*Desktop notifications, cooldown, optional relay command (user file only).
subagent.enabledRead async subagent reports at all. false ignores them, as before 0.14.
subagent.wakeAsk Jev whether a report that names trouble deserves a wake. false keeps the offline layer, which never wakes.
subagent.thresholdP(report needs the agent awake) that wakes it. Conservative on purpose.
subagent.cooldownMsAt most one batched wake per window, so several children finishing together cost one interruption.
widget.*Status line placement, sidebar shortcut and width, per-guard text templates (below).
learning.adaptiveThresholdsLearn from hold outcomes and suggest threshold adjustments via /warden recommend.
learning.patternAnalysisAnalyze hold patterns and generate recommendations.
learning.retentionDaysDays to keep hold records in SQLite before pruning. Records older than this are deleted on startup. 0 disables pruning. Default: 365.
steerVisibleShow steer messages in the transcript instead of only in the trace panel.
noticesPrint the per-call warning notices (warden · …) in the transcript. Off by default; the widget, the trace panel, and /warden trace always show every event.
steerBudgetSteers delivered to the agent per run before further non-critical ones are recorded in the trace only. Every delivered steer costs at least one LLM turn, and a closing run that collects six notices collects six restatements of the final status. 0 disables the budget. Critical guards (stuck, done, runaway recovery, subagent wake) always deliver.

Project config

A project may add .pi/pi-warden.json with enabled and per-guard overrides: stricter thresholds, extra guarded tools, rules.files, rules.skip, rules.sensitivePaths, or "done": { "enabled": false }. Project files are read only when Pi trusts the project. They can never grant typesafe consent, change mode, raise timeoutMs or maxRequests, or set notify.command.

A wince-style setup for a backend repo (the full version is examples/pi-warden.json):

{
  "rules": {
    "skip": ["tests/**", "**/*.test.*", "docs/**", "**/*.md"],
    "exclude": ["secrets/**", "**/*.pem"],
    "sensitivePaths": {
      "migrations/**": "This touches a migration: tell the user and add a rollback path",
      "**/permissions*": "Access control changed: ask the user for a security review before merging"
    }
  }
}

Environment

VariableEffect
TYPESAFE_API_KEYTakes precedence over the key stored by /warden enable or /typesafe login.
OPENROUTER_API_KEYAPI key for the OpenRouter backend. Required when typesafeBackend is "openrouter".
PI_WARDEN_ENABLED=1Grants consent for headless runs (same as "typesafe": true).
PI_WARDEN_MODE=steer|confirm|adviseOverrides mode.

Status line and trace sidebar

The line above the editor shows the latest verdict per guard. The verdict leads as a chip, the guard follows, and the body reads as data:

OK     rules · prose · done
WARN   action  write · irreversible 0.09 · off-task 0.95 · unrelated · slop: none · off task
       context bash · duplicate · saved 1024 bytes

A verdict the guard found nothing in (ok, allow, skipped) folds into one line per verdict naming the guards that spoke, so a quiet turn costs one line instead of one per guard. A quiet verdict keeps its own line when the line names a finding or a caveat (typesafe error, user approved, slop: <symptom>, patterns: <id>), because folding it would report a verdict the guard did not give. The worst verdict sits last, nearest the editor. Folded detail is not lost: /warden status prints the raw line per guard under Last:, and the sidebar keeps every event with its scores.

/warden trace, ctrl+shift+w, and a click on the line each toggle a right-hand sidebar with the full trace, newest first, live. The sidebar does not take the keyboard; click inside it for arrow keys and PgUp/PgDn, c clears, Esc hands input back, q closes. widget.panelWidth sets its width.

Clicks and the wheel need Pi's fullscreen mode (tuiMode: "fullscreen" in /settings). In macOS Terminal.app enable View → Allow Mouse Reporting.

Templates in config.widget control the text. Segments are separated by ·; a segment whose token has no value is dropped. A template should end on {level} or {status}: that trailing word becomes the chip. A template that keeps the level mid-line gives the line no chip, and the guard name leads it instead:

"widget": {
  "action": "warden · {tool} · irreversible {irreversible} · off-task {offTask} · {scope} · slop: {slop} · patterns: {patterns} · {flags} · {level}",
  "rules": "warden · rules · {tool} {path} · {asked} rules · {violations} · {status}",
  "stuck": "warden · stuck · {failures} failures · same strategy {sameStrategy} · change {approachChange} · progress {progress} · {flags} · {status}",
  "done": "warden · done-check · {changes} changes · {checksPassed}/{checks} checks passed · claims done {claimsDone} · claims verified {claimsVerified} · checks apply {checksApply} · {outcome} · {status}",
  "prose": "warden · prose · wordy {wordy} · clichés {cliches} · jargon {jargon} · {flags} · {status}",
  "security": "warden · security · {tool} · injection {injection} · exfiltration {exfiltration} · {status}",
  "context": "warden · context · {tool} · {retention} · saved {bytesSaved} bytes",
  "runaway": "warden · runaway · {kind} · {count}× repeated · {chars} chars · {signal} · {status}",
  "subagent": "warden · subagent · {agent} · {kind} · {wake} · {status}"
}

Tokens per guard:

GuardTokens
actiontool level source irreversible offTask scope approved intent visible plan slop slopStub slopComments slopDead slopHedging patterns reasons path model ms flags time
rulestool path asked violations status source reasons model ms flags time
prosewordy cliches jargon status reasons model ms flags time
stuckfailures sameStrategy approachChange progress status source reasons model ms flags time
donechanges checks checksPassed claimsDone claimsVerified checksApply outcome status reasons model ms flags time
securitytool injection exfiltration status
contexttool retention bytesSaved
runawaykind count chars signal block status time
subagentagent kind wake status time

"enabled": false hides the line; "shortcut": "" disables the keybinding.

After updating the package

Restart Pi after an update; /reload re-imports the entry module but can leave older modules of the same package in memory. Since 0.5.2 the extension checks the shape of the config it receives; a section that an older module does not know (the symptom of two package versions in one process) switches that guard off and prints one warning naming the sections and the schema numbers. 0.9.0 crashed instead when the shape-check module itself was the stale one; since 0.9.1 the extension guards the sections it reads in its own module, so the warning appears and everything else keeps working. If you see the warning, restart Pi.