Parent directory

AGENTS.md

4642 bytes

Policy Engine

Purpose

This package provides a policy plugin for Pi. It combines host-specific prechecks, deterministic rules, a JSONL decision cache, and a chat or classifier reviewer. Decisions are allow, deny, or ask.

Layout

  • src/core/ โ€” shared Bash parsing, glob resolution, deterministic checks, pipeline, chat and classifier reviewers, llama.cpp provider, cache, audit, normalization, and configuration.
  • src/pi/ โ€” Pi extension, static permissions, UI, and session Bash overrides.
  • test/core/, test/pi/ โ€” unit and extension tests.

The package root and ./pi export the Pi extension. src/index.ts re-exports ./pi/index for Pi extension loading.

Your general goal is to keep this code simple. Don't add things you're not asked for. If given a choice, do less.

Policy flow

Pi evaluates session Bash overrides and static permissions before deterministic rules. Pi and raw core calls share structured Bash parsing and async evaluation. Unsupported Bash does not receive a deterministic allow and falls back to full-input chat or classifier review. Commands are checked independently without tracking shell state across commands. Path checks and metadata-only glob expansion use the request's working directory; git -C is local to its command. Cached Bash allows are not reused or written.

The shared pipeline evaluates deterministic rules, then the cache, then the configured reviewer. Pi's /pe-skip-permissions command is local to the loaded extension instance. In that mode static permissions, deterministic rules, and cached decisions still apply; it skips chat and classifier review and allows uncached requests.

MCP tools bypass the Pi adapter policy boundary.

Classifier review

src/core/classify.ts defines seven independent risk questions and maps typed probabilities to policy decisions. Configure reviewer.kind: "classifier" with provider, model, and an explicit finite askThreshold between 0 and 1. The Pi adapter calls ctx.modelRegistry.classify() using existing provider credentials; classifiers do not appear in the chat model picker. Use OpenRouter's typesafe/jev-1.13, not the chat router typesafe/jev-router.

Any risk probability at or above the threshold returns ask; otherwise the reviewer returns allow. Missing or invalid answers and provider failures return ask. Reasons and categories are fixed in code, not generated. Keep exceptions aligned in question instructions and answer criteria. Classify executable effects, not inert payload text. Herdr has no special stop-server risk.

A threshold of 0.5 passed the current Jev 1.13 live evaluation cases; 0.1 produced false positives. This is not a general safety guarantee. Re-evaluate after changing models, questions, or thresholds. The default chat reviewer remains unchanged.

Testing

Test only policy decisions: static permissions, deterministic rules, and chat or classifier decisions. Do not add tests for pipeline behavior, cache reads or writes, audit records or sources, normalization, configuration, providers, or other plumbing. A request to change plumbing is not a request to test it; add a plumbing test only when the user explicitly asks for that test.

Rules

src/core/deterministic.ts contains allowed commands and applies policy to structured Bash. src/core/review.ts contains the chat policy prompt. src/core/classify.ts contains classifier risk questions. src/core/normalize.ts contains POLICY_VERSION.

When changing deterministic patterns, matching behavior, or reviewer policy:

  • Keep hard-allow rules anchored and strict.
  • Keep config-allow rules protected from shell control syntax.
  • Add or update tests.
  • Bump POLICY_VERSION in src/core/normalize.ts only immediately before committing rule changes, to invalidate cached reviewer decisions.

Commands

bun test
bun prettier -w .
bunx eslint src test
bunx tsc --noEmit

Run the shared live policy cases through the classifier path:

POLICY_EVAL_BACKEND=classifier \
POLICY_EVAL_PROVIDER=openrouter \
POLICY_EVAL_MODEL=typesafe/jev-1.13 \
POLICY_EVAL_ASK_THRESHOLD=0.5 \
bun test test/core/llm.test.ts

Live evaluation runs each case five times, requires at least four correct decisions, and rejects any decision less strict than expected. It supplies the current working directory and bypasses deterministic checks and the cache to evaluate the reviewer directly.

Tests use temporary configuration and state paths, except opt-in Pi chat and classifier evaluations, which use existing Pi credentials. Do not read or commit credentials, cache files, or audit logs.