@bacnh85/pi-classifier

System One decision models (TypeSafe Jev) for Pi — classify tool for typed probabilistic answers, a Jev-gated permission auto-approve hook for shell commands (default on, static risky list first, never auto-denies, fails safe, observe mode), and a /classi

Packages

Package details

extension

Install @bacnh85/pi-classifier from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:@bacnh85/pi-classifier
Package
@bacnh85/pi-classifier
Version
0.2.2
Published
Sep 30, 2026
Downloads
468/mo · 468/wk
Author
bacnh85
License
MIT
Types
extension
Size
40.8 KB
Dependencies
1 dependency · 0 peers
Pi manifest JSON
{
  "extensions": [
    "./extensions/index.js"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

pi-classifier

System One decision models (TypeSafe Jev) for Pi. Jev returns typed answers with calibrated probabilities — never text — so this extension surfaces it as a tool, not a model.

Two pieces:

  1. classify tool — the agent sends {state, questions}, gets typed answers: noul (P(yes)), choice (option + probabilities + confidence), score (weighted position + confidence). Use for routing, verification, and gating decisions.
  2. Permission auto-approve hook (on by default; explicit opt-out wins) — shell commands Jev is confident are reversible and serve the task run without prompting; set permission.enabled: false to turn it off. The OpenRouter cookbook pattern.

Install

pi install @bacnh85/pi-classifier

Configure

Run /classifier-config in Pi (TUI): an arrow-key panel for the baseUrl, the decision model — with completions pulled live from the router's GET /v1/systemone/models (yardmaster) — and the permission block below. /classifier-config show prints the config plus discovered models in any mode. Everything is still plain JSON in global settings (~/.pi/agent/settings.json) — never repo scope, because the endpoint receives your API key as Bearer:

{
  "classifier": {
    "baseUrl": "http://localhost:8787/v1",  // yardmaster (or https://openrouter.ai/api)
    "model": "jev/jev-latest",              // id the upstream knows: jev/jev-latest, or/typesafe/jev-1.13, jev-latest…
    "permission": {                          // default ON/enforce — auto-approves reversible,
      "enabled": true,                       // task-serving commands; set enabled:false to opt out
      "mode": "enforce",                     // "observe" logs decisions without acting
      "threshold": 0.9                       // both nouls must clear it
    },
    "planGate": {                            // opt-in — default OFF; used by pi-plan
      "enabled": true,
      "mode": "observe",                     // start here; "enforce" to act
      "threshold": 0.9                       // independent of permission.threshold
    }
  }
}

API key: CLASSIFIER_API_KEY env, or the classifier credential in ~/.pi/agent/auth.json:

{ "classifier": { "key": "ar-..." } }

Through yardmaster: point baseUrl at the router's /v1, set model to the prefixed id (jev/jev-latest for the TypeSafe-direct provider, or/typesafe/jev-1.13 via OpenRouter) and use your router key. Pricing: $0.042/Mtok input, output free. The panel's model completions come from yardmaster's decision-model listing (GET /v1/systemone/models); routers without it (OpenRouter direct, TypeSafe direct) just fall back to manual entry.

The safety envelope

Non-negotiables, in order:

  1. Static risky list first. rm -rf, sudo, force-push/hard-reset, pipe-to-shell, publish/deploy CLIs, credential paths → never sent to Jev, never auto-approved. The list is deliberately short and shallow.
  2. Never auto-denies. Any outcome other than a confident yes (low score, timeout, 4xx/5xx, malformed answer, missing key, no UI) falls back to the normal prompt. Worst case is one extra prompt, never an unwanted command.
  3. Observe mode (opt-in). Enforce is the default posture; set permission.mode: "observe" to log the decision it would have made to ~/.pi/agent/classifier.log without acting. Run it for a few days, read the log, then flip mode: "enforce".
  4. One audit line per decision — command, scores, elapsed ms, model.
  5. Compound commands are split on operators before the risky check; a separator hidden inside quotes can only add a prompt, never hide a command.

Host deny rules always win: pi-classifier only ever allows; it cannot override an explicit deny from pi-permission or the harness.

Plan gate (pi-plan integration)

planGate powers pi-plan's plan-mode confirm tier: when a bash command lands in the "confirm" tier during plan mode, pi-plan asks Jev "is this read-only and needed for planning?" and auto-allows only a confident yes. Jev may only reduce prompts — it can never unlock a write (the outer gate blocks those before the gate runs), never deny (every non-confident outcome falls through to the normal prompt), and every verdict is audited to classifier.log with source: "plan-gate".

Workflow: set classifier.planGate.enabled: true → observe is the default (logs would-be allows, still prompts) → review the log → flip mode: "enforce". Risky-list commands (rm -rf, pipe-to-shell, …) never reach Jev. The library export planGateVerdict(opts, command, cwd, task) is what pi-plan calls; it is exported for tests and library hosts.

Verification cache

Verdicts are cached (LRU, 100 entries) — permission-hook keys are command\0cwd\0task (task truncated to 200 chars, since 0.2.1), plan-gate keys are plan\0command\0cwd (task excluded — plan commands are generic) — so repeated bun test doesn't re-pay Jev or add latency every time.

Using the classify tool directly

Ask the agent: "Classify this ticket: is_urgent noul, team choice (billing/technical/account), severity score 0-3" — it composes the questions and returns the typed answers. Questions run in parallel inside one request; probabilities jitter ±0.08 between identical calls, so thresholds are policy — tune them on your own traffic.

Coexistence with pi-permission

Both observe tool_call. pi-classifier's silent-allow and pi-permission's ask compose by load order: whichever extension returns first wins. If you run both, the intended stack is pi-permission deny rules (they win over everything) + pi-classifier enforce for the confident middle. Test the combination before trusting it.