omp-auto-mode

September 19, 2026 · View on GitHub

Auto-mode for oh-my-pi (omp): every tool call the agent wants to run is classified as safe, ask, or unsafe before it executes, using TypeSafe Jev as the judgment backend.

  • safe — runs without interruption.
  • ask — you get a confirmation dialog with the reason.
  • unsafe — blocked; the model is told why so it can try something else.
$ omp-auto-mode -- git push --force origin main
{
  "label": "unsafe",
  "triggered": ["other_risk", "runs_untrusted", "network_egress", "destroys_data", "outside_project"],
  "uncertain": ["exposes_secrets", "system_config"],
  "hazards": { "destroys_data": 0.73, "network_egress": 0.81, … },

}

How it decides

Instead of one "is this dangerous?" question, each call is judged against seven independent yes/no hazards in a single Jev request. Code, not the model, owns the policy:

hazardfires →catches
exposes_secretsunsafereading/printing/sending keys, tokens, .env, $(env)
destroys_dataunsaferm -rf ~, disk formatting, overwriting a dotfile, force-push to main
runs_untrustedunsafecurl … | sh
outside_projectaskwrites or chmod outside the project directory
network_egressaskgit push, curl -d, deploy/publish
system_configasknpm i -g, sudo, global git/shell config
other_riskaskcatch-all: hard to undo, costs money, affects others

Two thresholds turn probabilities into a label (fire = 0.7, clear = 0.3 by default):

  • any unsafe-severity hazard ≥ fireunsafe
  • any hazard ≥ fire, or any specific hazard in [clear, fire) (the model is genuinely unsure) → ask
  • everything below clearsafe

Because each hazard is an absolute judgment, safe means every hazard came back near zero — not that "safe" won a relative vote. The catch-all only counts when it fires outright; its mid-range means "mildly consequential", not "unsure", so it never prompts on its own.

Install

Requires Node ≥ 23.6 (runs TypeScript natively) and a TYPESAFE_API_KEY in your environment.

git clone https://github.com/alexsatch/omp-auto-mode
cd omp-auto-mode && npm install

Load it into omp for one session, or link it permanently:

omp -e /path/to/omp-auto-mode
omp plugin link /path/to/omp-auto-mode

The extension gates bash, write, edit, eval, and ast_edit. Read-only tools are not classified. If the classifier itself is unreachable, the call falls through to omp's normal approval instead of failing closed.

Allow and deny rules

Commands you trust (or never want run) can bypass the model entirely. Rules live in <project>/.omp/auto-mode.json and ~/.omp/agent/auto-mode.json (merged; deny always wins):

{
  "allow": ["git status", "git diff *", "npm run *", "ls *"],
  "deny": ["git push --force *"]
}

A rule is a list of tokens with an optional trailing * meaning "any further arguments". Rules match only commands that are a flat argument list — no ;, &&, |, $(…), globs, expansions, or redirects to real files — so git status; rm -rf ~ never matches git status; it goes to the classifier. Redirects that cannot touch a file (>/dev/null, 2>/dev/null, 2>&1) are ignored for matching, so npm test 2>&1 matches an npm test rule.

When a call is classified ask, the dialog offers Allow once, Always allow (the exact command, and its <cmd> <sub> * prefix when longer), and Deny. Choosing Always allow appends the rule to the project file, so the list grows from real decisions.

CLI

# classify one command
node src/cli.ts -- rm -rf ~/
node src/cli.ts --project-dir ~/work/app --fire 0.8 -- git push origin feature

# classify a batch of tool calls from a JSON file, all in one request
node src/cli.ts fixtures/tool-calls.json

Batch files look like { "project_dir": "...", "calls": [{ "id", "tool", "input" }] }. Output is one JSON envelope in both modes: { "model", "thresholds", "verdicts": [{ "id", "tool", "input", "label", "triggered", "uncertain", "hazards" }] }.

flag / envdefaultmeaning
--fire / AUTO_MODE_FIRE0.7probability at or above which a hazard holds
--clear / AUTO_MODE_CLEAR0.3probability below which a hazard is absent
--project-dircwddirectory the command runs in (command mode)
--modeljev-latestJev model or alias

Exit codes: 0 ok · 2 usage/config · 3 API failure.

Development

npm run typecheck   # tsc --noEmit, free
npm test            # typecheck, then node --test against the live API (~27k input tokens)

The test suite is test/classifier.test.ts on node:test: it classifies every call in fixtures/tool-calls.json in one request and asserts each call's expected label. Jev's probabilities drift by a few hundredths between runs, so after changing hazard wording or thresholds run it a few times and fix flakes in the question text, not by widening thresholds. See AGENTS.md for the conventions.

Known limitation: clearly hostile commands tend to light up unrelated hazards too (mkfs scores high on exposes_secrets). Labels are unaffected — it only over-fires on calls that are already unsafe — but the reason string for a hard block may list hazards that do not literally apply.

License

MIT