@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
Package details
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:
classifytool — 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.- 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: falseto 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:
- 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. - 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.
- 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.logwithout acting. Run it for a few days, read the log, then flipmode: "enforce". - One audit line per decision — command, scores, elapsed ms, model.
- 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.