pi-steering is a deterministic guardrail layer that sits between your pi agent and the tools it invokes. You declare TypeScript rules that gateDocumentation 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.
bash, write, and edit tool calls; the engine parses every command with a full bash AST, walks a per-call tracker state, matches against your rules, and returns a block verdict before pi executes anything. The key difference from every regex-on-raw-string approach is that pi-steering reasons about shell structure, not text patterns — which is the only correct way to make safety rules that adversarial or accidental shell composition cannot bypass.
The Problem With Substring Matching
Naive substring or regex matching on the raw command string has two failure modes that make guardrails unreliable in practice. False positives fire on safe commands that merely mention a forbidden pattern as data:git push --force blocks this harmless echo, creating friction and eroding trust in the guardrail.
False negatives let dangerous commands through via shell wrapper indirection:
basename of each extracted command ref is git in all three wrapper cases above — not sh, not sudo, not the raw string — so the same ^git\s+push.*--force(?!-) pattern fires correctly on all of them. And echo 'git push --force' never fires because the extracted ref’s basename is echo.
Three-Layer Architecture
unbash parser produces a structured AST from the raw command string. unbash-walker traverses that AST to extract individual command refs (one per chained command), resolve their effective working directories, and normalize basenames regardless of path prefix or wrapper depth. pi-steering builds on top of this to provide a complete rule engine with config loading, plugin composition, session-level state, and pi lifecycle integration.
Key Features
AST-Backed Matching
Every bash command is fully parsed before evaluation. Rules match against structured command refs, not raw strings — wrapper commands like
sh -c, sudo, env -i, and similar are automatically expanded so inner commands are visible to your rules.Per-Command cwd Scoping
The walker resolves the effective working directory for each extracted ref in a chain. A rule scoped with
when.cwd evaluates cd /tmp && git commit against /tmp, not the session root — giving you precise, per-ref cwd constraints.Plugin-First Composition
Plugins are TypeScript modules that bundle predicates, rules, observers, and walker trackers under a single name. Users opt in via
plugins: [...]; plugins ship as npm packages. The git plugin ships by default; community plugins use the pi-steering-package keyword for discoverability.Stateful Observers with `when.happened`
Observers watch
tool_result events and write typed session entries. Rules gate on those entries via when.happened, enabling “must run X before Y” guardrails that survive across tool calls within the same user prompt — without any per-turn bookkeeping in your rules.Compile-Time Safety via `defineConfig`
The
defineConfig helper threads generics through your entire config so that rule names, plugin names, observer names, and when.happened.event references are all type-checked at tsc --noEmit time. Typos become compile errors, not silent misconfiguration.`&&`-Chain Speculative Allow
Agents frequently chain commands like
sync && cr. pi-steering synthesizes speculative session entries for &&-reachable refs so a rule requiring a prior observer event isn’t naively blocked when the observer and the guarded command appear in the same chain.Walk-Up Config Merge
The loader walks from the session’s launch directory up to
$HOME, importing every .pi/steering/index.ts it finds. Inner layers (closer to the project root) take precedence over outer layers on name collisions, so per-project rules can override user-level defaults without breaking them.Hot Config Reload
Running
/reload inside pi re-evaluates your .pi/steering/index.ts without restarting pi. The loader cache-busts the dynamic import so edits to your config (and to plugin .ts source, when the plugin ships source as its entry) take effect immediately.Package Ecosystem
pi-steering ships as four related packages in thecad0p/pi-steering-hooks monorepo:
pi-steering— the core engine. ExportsdefineConfig,DEFAULT_RULES,DEFAULT_PLUGINS, the full rule/plugin/observer schema types, and walker primitives re-exported fromunbash-walker. This is the package you install to use pi-steering.unbash-walker— the AST utility layer. ProvidesextractAllCommandsFromAST,expandWrapperCommands,cwdTracker,CommandRef, basename normalization, and theTracker/ModifierAPI for walker extensibility. General-purpose infrastructure planned for extraction into its own repository once the PoC proves its value.pi-steering-flags— the first official community-style plugin. ShipsrequiresFlagandallowlistedFlagsOnlypredicates plus helper primitives for building flag-constraint rules.pi-steering-commit-format— ships thecommitFormatpredicate and acommitFormatFactoryfor composing custom commit-message format checkers. Bundled formats include Conventional Commits 1.0.0 (Angular preset type allowlist) and bracketed JIRA-style references.
Requirements
pi-steering requires Node ≥ 22. The config loader reads.pi/steering/index.ts files via Node’s native type-stripping (no tsx or ts-node runtime dependency). On older Node versions the loader throws with an upgrade message at startup.
Get Started
Quickstart
Install pi-steering, write your first rule, and test it against your pi agent in under five minutes.