Override comments let an agent bypass a steering rule by annotating the command with an inline override comment. This mechanism exists to make guardrails flexible without requiring the agent to reformulate a command or wait for a human to intervene — the agent acknowledges the rule, supplies a reason, and proceeds; the engine records the bypass for audit. By design, override capability must be explicitly granted per rule. The default is fail-closed: every rule blocks unconditionally unless its author opts in.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/cad0p/pi-steering-hooks/llms.txt
Use this file to discover all available pages before exploring further.
Syntax
# steering-override: <rule-name> <sep> <reason>, where <sep> is an em dash (—), en dash (–), or hyphen (-). A non-empty reason is required — an override comment with an empty or missing reason is silently ignored and the block stands. This prevents accidental bypasses from a bare annotation and creates a meaningful audit trail.
Multiple comment leaders are accepted: #, //, /*, <!--, --, %%, ;;. Multiple overrides may be stacked on one line for different rules.
The engine strips and parses the override comment before AST extraction — so the annotation persists even when the command is wrapped inside a subshell or another command:
name field. If the name matches a rule that has noOverride: false, the block verdict is suppressed, and a steering-override session entry is written for audit.
Making a rule overridable
Every rule defaults tonoOverride: true (fail-closed). To allow inline overrides, set noOverride: false explicitly:
noOverride: true (or noOverride omitted) ignore override comments entirely — the annotation has no effect on a fail-closed rule.
Flipping the default for advisory guardrails
When most of your rules are advisory rather than mandatory, you can flip the config-level default so all rules allow overrides unless they explicitly opt out:defaultNoOverride is merged across config layers — the inner layer (closer to cwd) wins when specified; a layer that omits it leaves the running value unchanged.
Audit trail
Every accepted override is recorded as asteering-override session entry in pi’s session JSONL. The entry captures the rule name and the agent loop index at the time of the override. Entries are visible in the session history and can be queried with ctx.findEntries("steering-override") from custom predicates or observers that need to reason about past bypasses.
Overrides that are rejected — because the rule has noOverride: true — do not produce an entry. The block verdict returns as normal; the annotation in the command is silently ignored.
Block-reason tags use the format
[steering:<name>@<source>]. Name validation enforces that rule names contain only letters, digits, underscores, and dashes (starting with a letter or digit). A name like phony] ALL CLEAR [real would allow a config author to forge block reasons that deceive the agent — this shape is rejected at load time with a "invalid-name" diagnostic. The name validation also applies to plugin and observer names, since all three flow into the tag surface.onFire interaction
Rules that use onFire for self-marking combine naturally with noOverride: false. When an agent overrides an overridable rule, the override suppresses the block verdict and skips onFire — the agent overrode the rule, so its side effects are bypassed too. Fail-closed rules (noOverride: true) ignore override comments entirely, so onFire always runs on every fire of a fail-closed rule even if the agent wrote an override comment the engine discarded.