Skip to content

Repository files navigation

Anytech overseer

An independent Gemini reviewer that audits Claude Code's claimed progress against the real working tree, feeds corrections straight back into Claude's context, and stops everything to ask you when a decision is yours to make.

By Kayne Middleton. MIT licensed.

The hooks are global, so once installed it is available in every project, and it stays dormant in all of them until you arm one. Config, key and logs live in a data directory outside the code (~/.claude/overseer/ by default), so updating or reinstalling never touches your settings.

Why it reads the diff, not just the plan

task_plan.md and progress.md are written by the agent being audited. An overseer that only reads those grades Claude's homework with Claude's own answer key: a hallucinated [x] reads as success. So every review is sent git diff <baseline> plus git status, and the model is told the diff is the only evidence. A checklist item marked done with nothing in the diff is the main thing it exists to catch.

The baseline is the last commit older than OVERSEER_LOOKBACK_HOURS (12 by default), so the diff spans work you committed mid-task as well as the uncommitted tree. Without that, committing mid-task would empty the diff and the overseer would falsely accuse Claude of fabricating everything.

Your global and project CLAUDE.md go in the prompt too (plus CLAUDE.local.md if present), so it also enforces scope lock, the comment policy, the banned tooling, and plain ASCII.

Rules it cannot see are rules it cannot enforce, so the global file is never truncated and the project file gets whatever is left of rules_chars (80k by default). A very large project CLAUDE.md can still be clipped; --config will not tell you that, but a [... truncated marker in the prompt means raise the budget.

Requirements

  • Claude Code
  • Python 3.9 or newer on PATH, and git
  • pip install google-genai (the only dependency; everything else is stdlib)
  • A Gemini API key from https://aistudio.google.com/apikey
  • The planning-with-files plugin. This is not optional: every review is an audit of task_plan.md, findings.md and progress.md against the diff, and the Stop hook refuses to let substantive work finish with no plan behind it. Without those files the overseer has nothing to audit.

Install

Add the planning-with-files marketplace first. The overseer declares planning-with-files as a plugin dependency, but Claude Code will only fetch a dependency from a marketplace you have already configured, and it refuses to pull one from a different marketplace than the plugin declaring it unless that marketplace is allowlisted. Both sides are set up here, so the add is the one step left to you:

/plugin marketplace add OthmanAdi/planning-with-files

Then the overseer, which brings planning-with-files in with it:

/plugin marketplace add anytech/overseer
/plugin install overseer

Installing and enabling the overseer installs and enables planning-with-files at the same scope. You do not install it separately, and you cannot disable it while the overseer is enabled: Claude Code refuses and hands you a chained command that disables both. If you uninstall it anyway, /reload-plugins and the background plugin update put it back, because its marketplace is configured.

Skip that first marketplace add and the dependency is left unresolved. The overseer installs but stays disabled with a dependency error until you add the marketplace, at which point Claude Code resolves it on its own.

The dependency carries no version constraint, so it tracks whatever planning-with-files publishes. The overseer reads task_plan.md, findings.md and progress.md under .planning/<date>-<slug>/; if upstream ever moves those, pin the version in .claude-plugin/plugin.json.

The install dialog asks for one thing, your Gemini API key, and stores it in your OS keychain rather than in settings.json. Everything else ships on a working default and is changed later through the config file or the environment, so there is nothing to decide at install time. Hooks are wired for you.

Confirm the key actually landed, then arm the repos you want audited. Every hook exits immediately in an unarmed repo, so nothing happens until you arm one:

/overseer:status
/overseer:arm

/overseer:disarm stands a repo down again, /overseer:review runs one review on demand, and /overseer:watch gives you the command for the live viewer. That is the whole command surface, and none of it needs you to know where the plugin put its files.

Installing from a clone instead

If you would rather not use the plugin system:

git clone https://github.com/anytech/overseer ~/.claude/overseer
pip install -r ~/.claude/overseer/requirements.txt
python ~/.claude/overseer/monitor_plan.py --set-key
python ~/.claude/overseer/monitor_plan.py --install-hooks

--set-key prompts for the key and writes it to <data dir>/.key with owner-only permissions. --install-hooks backs up settings.json first, is idempotent, records the absolute path of the interpreter that ran it, and adds any hook that an older install is missing, so re-run it after every upgrade.

The clone can live anywhere; the hooks point at wherever you put it.

Running it from a terminal

Everything below is written as <overseer>, which is the full path to monitor_plan.py. A clone install is wherever you cloned it. A plugin install is inside the plugin cache, under a directory named for the plugin version:

%USERPROFILE%\.claude\plugins\cache\anytech\overseer\<version>\monitor_plan.py

Do not memorise that. /overseer:status prints the live path on its first line, and /overseer:watch hands you the viewer command with the path already filled in, so a version bump cannot leave you holding a stale one.

Check it before you trust it

python <overseer> --config

That prints the script path, the data directory, every setting, where each value came from, whether a key resolved, and whether planning-with-files is installed. api key resolved: NO means every review fails open and nothing will ever be blocked. This is the one failure worth checking for, because the overseer stays quiet either way. planning-with-files: NOT INSTALLED prints the exact commands to fix it, and every session in an armed repo opens by telling the agent to put those commands to you.

Pick a model with --list-models to see what your key can actually use. The default is gemini-3.5-flash-lite, which is cheap and good enough for diff auditing; set model_stop to something stronger if you want the end-of-turn review sharper than the mid-turn ones.

Then try a paid review by hand from inside an armed project:

python <overseer> --once

Where things live

What Where
Code the clone, or the plugin's install directory
Config, key, notification state, escalation log the data directory
Data directory $OVERSEER_HOME, else ~/.claude/overseer/
Per-repo review log and review state .planning/<plan>/ in the audited repo, gitignored automatically
The armed marker .overseer-on at the audited repo's git root, gitignored automatically

Keeping the data directory outside the code is what makes a plugin update safe: plugins install into a versioned directory that is replaced on upgrade, so a key stored next to the code would not survive one.

Platform support

Reviews, blocking, plan repair and debriefs work on Windows, macOS and Linux.

Desktop notifications are Windows only. On macOS and Linux notify() returns immediately and everything else behaves normally, so you get the blocks and the corrections but no tray balloon.

The plugin hooks run through sh, which means Git Bash on Windows. If you do not have it, install from the clone instead: --install-hooks writes the absolute path of your Python interpreter and needs no shell at all.

Off by default, armed per repo

The hooks are installed globally but exit immediately unless the repo holds an .overseer-on marker file. From inside a project, either /overseer:arm and /overseer:disarm, or directly:

python <overseer> --enable     # arm this repo
python <overseer> --disable    # stand it down

--enable writes the marker at the git root, prints where it put it, and adds it to that repo's .gitignore. Both matter: the marker must never be committed, and an untracked file would otherwise turn up in the git status handed to Gemini as evidence in every single review.

The search for the marker walks up from the working directory and stops at the git root, so a marker left in a parent folder full of checkouts cannot silently arm every project underneath it. In an unarmed repo the hook exits before it reads a plan file, runs git, or touches the network.

Manual runs (--once, --watch) ignore the marker, so you can review anything on demand without arming it.

What the hooks do

In an armed repo:

Hook Fires Effect
SessionStart Every new session Mandates the planning-with-files skill, and says how to install it if it is missing
PreToolUse Claude is about to ask you a question Denies the question when the plan already answers it
Stop Claude says it is finished Blocks with the review; Claude must fix and stop again
PostToolUse Any file is written Rule-checks what was just written; reviews the plan when a plan file changes

In every repo, armed or not:

Hook Fires Effect
Notification Claude asks you something or wants permission Tray balloon, and a line in the watcher

Notification is deliberately not gated: being told Claude is sat waiting on you costs nothing and matters everywhere. Silence it with OVERSEER_NOTIFY=0.

Claude Code's idle notification is a wall-clock timer on the input box, so it fires while subagents are still running and tells you Claude is waiting when it is not. Before passing an idle alert on, the overseer scans the transcript for tool calls that have no result yet, and stays quiet if anything is still in flight. Permission prompts are never suppressed this way; only the idle one.

Enforcing CLAUDE.md

For its first 76 reviews this tool did not enforce the rules at all. The log proves it: one finding of kind rule_violation, for a rule nobody had written ("phase boundary markers"), and not a single citation of the bans on sed, grep, git commit, plain-ASCII text, comment policy or fallbacks. Two structural faults caused it.

The PostToolUse hook was registered for every Edit|Write|MultiEdit, then threw away anything that was not task_plan.md, progress.md or findings.md. So the overseer had never once looked at a line of code at the moment it was written, and all 19 of those reviews came back on_track, because bookkeeping was all they ever saw. Meanwhile the Stop review asked about rules as item 3 of a 10-point list, behind roughly 150k characters of plan, diff and rulebook. The same model, asked the same question on its own, gets it right; buried, it never fired once.

The fix is one dedicated call that is asked nothing else.

An earlier version of this tool tried to do the job with hand-written gates instead: pattern matchers for banned commands, a character sweep for em dashes and smart quotes, regexes for comment runs and docblocks. They are all gone. A gate that greps for sed has to be taught that parsed and based are not sed, then taught about git add versus git commit, then taught that a Herd binary path is not the banned herd, and it still only ever catches the rules somebody remembered to hand-code. The model reads the rulebook and applies all of it, including the rules written after the code shipped. Deleting the gates lost nothing and removed the part that needed maintaining.

Your rulebook is split and audited section by section. gather_rules() collects the global CLAUDE.md, the project one and CLAUDE.local.md. rulebook_chunks() splits that at markdown headings and packs it to roughly 2500 characters a chunk. Each chunk gets its own concurrent call, eight at a time, carrying only that slice of the rules and only the new code. Nothing is buried behind 150k characters of plan and diff, and nothing is asked to weigh ten questions at once.

The whole audit is bounded by audit_budget (60 seconds). Sections that fail or run out of time are counted, and when a run finishes with unfinished sections and no findings the log says so in as many words: a clean result there is not evidence of clean code.

Every finding has to prove itself twice, or it is dropped. A finding must carry a verbatim quote of the rule it invokes and a verbatim quote of the line that breaks it. The rule quote must be at least 20 characters and appear in the chunk that was sent; the code quote must appear in the code that was sent. Both comparisons are made on flattened text, so whitespace and formatting differences do not matter, but an invented rule or a paraphrased line does not survive.

That verification is the whole reason the audit is trustworthy. The one bogus finding in this tool's early history was a rule nobody had written; that class of finding can no longer reach you, because there is no section of your CLAUDE.md it can be quoted from.

Findings are then deduped by offending line and capped at 12 per audit.

It runs twice, on additions only. Existing code is never audited:

  • On each Edit, Write or MultiEdit, against what that edit adds, once the addition is over 120 characters. Identical additions are skipped by digest, and a session gets at most max_blocks edit-time blocks. A confirmed breach blocks immediately, naming each rule and each offending line, and tells Claude to fix that file before carrying on.
  • At Stop, against every line the whole task added, cached on a hash of the rules plus the additions so the same diff is never paid for twice. Here a confirmed breach forces the verdict to deviated at confidence 100 even if the plan review said on_track, and jumps the queue in the corrective prompt.

So a breach the per-edit pass missed, or one that only makes sense across several files, is still caught before the turn ends.

Rules were pulled out of the main Stop prompt when this call was added. That review now judges the plan against the diff and is told explicitly not to report rule violations, because this call owns them.

A rule you disagree with can be excused per repo. In the watcher, x excuses every rule the selected entry cites, clicking one finding excuses just that rule, and u undoes the last excusal. Each one writes the rule's hash to .overseer-exempt at the repo root, and later audits in that repo drop findings that quote it. Nothing else is ever silenced.

Turn the edit-time pass off with rule_check_edits: 0. The Stop sweep always runs in an armed repo.

Questions the plan already answers

Claude asking "which phase next?" when the plan lists the phases in order is an interruption you should never have to field. A PreToolUse hook on AskUserQuestion catches the question before the dialog appears, checks it against the plan with a small focused call, and denies it when the plan settles it, handing Claude the answer as the denial reason:

Do not interrupt for this. Your own plan already answers it:

Build Phase 3.

From the plan: - [ ] Phase 3. Decisions stream.

It cannot fill in the dialog, only stop it appearing. The check is deliberately biased towards letting questions through: a genuine product, business or risk call the plan never made passes straight to you, because interrupting is cheaper than guessing on your behalf. Denials are capped at two per session, so a wrong deflection can never leave Claude unable to ask anything.

Knowing it is your turn

Claude Code's own Notification hook only fires after roughly a minute of idling, so a finished turn can sit there in silence while you look elsewhere. The Stop hook therefore raises its own alert the instant Claude hands control back, carrying the verdict and summary. It fires in every repo, armed or not, and it never fires when the overseer blocked, because a block means Claude is still working and needs nothing from you. OVERSEER_NOTIFY_DONE=0 turns it off without silencing escalations.

Planning is mandatory in an armed repo

Two mechanisms, because a prompt alone is a suggestion:

  1. SessionStart injects the mandate into Claude's context: use planning-with-files in slug mode, keep task_plan.md, findings.md and progress.md under .planning/<date>-<slug>/, and never tick an item ahead of the work, because the overseer reads the diff.
  2. Stop refuses to let Claude finish substantive work with no task_plan.md behind it. That check is deterministic and runs before the API call, so it costs nothing and cannot be argued with. Substantive means two or more files touched, a diff over 1200 characters, or new untracked files, so genuine one-liners and question-answering turns are never held up.

PostToolUse also exits immediately unless the edited file is task_plan.md, progress.md or findings.md, so it costs nothing on normal edits.

Escalation to you

When Gemini judges that a decision is yours, it sets needs_human and the hook blocks with the question, instructing Claude to put it to you verbatim via AskUserQuestion rather than guessing. You also get a tray balloon, and every escalation is appended to escalations.jsonl.

The bar in the system prompt is deliberately high: a product or business call, a requirement with two defensible readings, an irreversible action, anything touching live production data or money. Never for something Claude could settle by reading the code.

Escalations ignore the block cap. Corrections do not.

Ending a turn on an offer gets blocked

"Say the word and I'll do it", "want me to do that next", "shall I continue". Every one of those is the agent stopping to ask permission for work it should have done, and the rules forbid it. The Stop hook reads what Claude actually said from the session transcript, quotes the offending line back, and tells it to either do the work or ask a specific question rather than end the turn on an offer.

Detection is two passes over the same closing message. Fifteen anchored patterns run over the last 800 characters with no API call and need no judgement, so the obvious phrasings cannot be talked around. A model call then classifies whatever the patterns missed, and the two results are merged. Both are subject to the per-session block cap, so with max_blocks at 2 the third offer in a session goes through unblocked.

Every hanging action is classified by who is allowed to do it, because the answer differs completely:

Who Example What happens
the agent "the tests still need writing" blocked, told to do it now
the owner decides "which phase next, 4, 5 or 6?" blocked, told to ask it properly with AskUserQuestion and wait
the owner acts "needs a bump and build" stop allowed, logged YOUR MOVE, balloon lists the actions

The classifier is told the agent is forbidden from committing, pushing, deploying, releasing, running live APIs and touching production, so anything of that shape is yours however casually it was mentioned. A message can carry all three at once, and then the agent does its own share first, asks the question, and leaves your deploys alone.

That last row matters: a deploy the agent may not perform is not an incomplete task and must not be pushed back at it. It is a handoff, and the point is that it arrives as a list rather than buried in the last paragraph of a summary.

Two layers catch it, and neither is the main review:

  1. A pattern match on the obvious phrasings, free and instant.
  2. Failing that, a separate tiny call carrying only the closing message and one question: did it hand any action back? Blocked-on-a-missing-credential is explicitly excluded, since that is a legitimate stop.

The second layer exists because of a measured failure, not a hunch. The full review was given the closing message and an explicit rule about it, and still returned on_track with 100 confidence, twice, on two different turns. The same model, asked the same question in isolation, gets it right every time and quotes the exact sentence. The signal was drowning in 150k characters of diff and rules, not missing. Model tier was never the problem, and a bigger model would not have fixed it: a focused question would.

It can also refuse to let Claude stop early

Correct work is not finished work. If a review is otherwise clean but the plan still has unticked items that are in scope, the overseer sets continue_work with the specific next_step and the Stop hook sends Claude straight back to it instead of letting the turn end. The badge for this is KEEP GOING.

It is told not to use it when the plan is genuinely complete, when the remaining items were explicitly deferred or belong to a later phase, or when the next step needs a human decision first, which is an escalation instead.

Pushes have their own budget, OVERSEER_MAX_CONTINUES (default 3), separate from the correction budget, so a disagreement about "finished" cannot loop.

What is outstanding is computed, not interpreted. The prompt carries a section built in code from the plan's - [ ] items, stating either the exact list or that nothing is outstanding, and the model is told it outranks the prose. That exists because a model reading a 100KB plan reliably mistakes retained history for live work. It also means you never need a "summarise what's left" rule in CLAUDE.md: a written summary would be another self-report, and the checkboxes are already the machine-readable answer.

Outstanding work is unticked - [ ] items plus any Phase N heading that carries no tasks at all. A plan that describes Phase 4 in prose and never breaks it down still has Phase 4 left to do, and counting boxes alone would call that finished. A heading marked closed, complete, done, superseded, retired or shipped is skipped, and a parent phase whose sub-phases hold the tasks is covered by them.

A push must be grounded in the plan's own unticked boxes. A model reading a 100KB plan cannot reliably tell a superseded scope table from the live one, and it will happily report retired history as outstanding work. So when the model says the plan is incomplete, that claim is checked against - [ ] items in task_plan.md. If the plan uses checkboxes and none are open, the push is dropped and the model's claim is kept as ungrounded_open_items for the record. If boxes are open, they replace the model's prose, so next_step always points at something the plan actually tracks. A plan written without checkboxes falls back to trusting the model.

Grounding works in both directions: it drops a push the plan does not support, and it creates one the model missed. The model calling a plan finished does not make it finished any more than its prose makes it unfinished. It only ever adjusts a push, though. A deviated or false_completion verdict still blocks on its own merits, and an escalation is untouched.

Repairing the plan instead of nagging about it

A phase with no tasks under it, or a plan with no checkboxes at all, is not work to push at Claude. It is a defect in the plan that makes every later review guesswork. That gets its own outcome, badged FIX PLAN: Claude is told to break the phase into - [ ] items and nothing else, tick none of them, and stop again. Two minutes of editing, after which the plan can state what is left and normal grounding takes over.

It is self-clearing by construction. The trigger is the absence of tasks, so performing the repair removes the trigger, and the nag cannot recur.

Debrief before the context goes

Claude Code records exact token usage per assistant message, so context pressure is measured, not guessed. Past context_debrief_percent (60 by default) the Stop hook takes over:

  1. First time, it blocks and sends Claude back to write the handover into progress.md, findings.md and task_plan.md -- on disk, where it survives compaction -- explicitly not as a chat summary, since chat is the thing that gets compacted.
  2. Next stop, with the handover written, it lets the turn end and tells you the session is safe to compact.
  3. It re-arms only after another 50k tokens, so a long session gets a fresh debrief at 85%, 92% and so on rather than nagging every turn.

The overseer cannot run /compact. That is verified rather than assumed: the installed CLI binary contains no triggerCompact, requestCompact, compactRequest or runSlashCommand literal, so no hook output can ask for one. It does contain autoCompact in abundance, so the session will compact itself eventually whatever anyone does.

So the overseer covers the part that actually loses work, from both ends. It pushes Claude to write the handover while the session is still alive, and it hooks PreCompact to write one itself the moment compaction begins, auto or manual. That second file, handover.md beside the plan, is authored by the overseer from the transcript and the plan's own checkbox state, so it exists whether or not Claude cooperated:

# Handover, written at compaction on 2026-08-11T22:20:53+00:00
- trigger: auto
- context at compaction: 918,885 tokens (92%)
- plan: 44 of 46 checkboxes ticked

## Outstanding, from the plan itself
- [ ] 4.1 central feed ingest
- phase with no tasks: Phase 6. Scale the control plane

## What the agent last said, newest first
...

Set context_window to match the model you run: the default 1,000,000 suits a 1M-context Opus, and on a 200k model it would never fire until you lower it.

The caps count repeats, not totals

max_blocks, max_continues and max_repairs are loop guards, so they count consecutive blocks about the same unresolved situation. Anything changing, whether the plan moved on or a different defect appeared, resets the budget:

Stop Situation Result
1, 2 Phase 4 has no tasks blocked
3, 4 Phase 4 still has no tasks let through, it has been said twice
5, 6 now Phase 7 has no tasks blocked, fresh situation
8 plan resolved clean stop, counters reset

Guardrails

  • Fails open. Missing key, network error, quota exhaustion, unparseable response: it exits 0 silently and logs. It can never wedge Claude.
  • Loop guard. Corrections block at most OVERSEER_MAX_BLOCKS times per session (default 2), and it never re-fires on a stop_hook_active payload.
  • Deduped before the call, not after. The fingerprint of the plan plus the diff is computed first, and an unchanged repeat reuses the cached verdict rather than paying for an identical answer. A cached block still blocks.
  • Only blocks on a correction when confidence is at or above OVERSEER_CONFIDENCE (default 70).
  • Every review is appended to .overseer-reviews.jsonl in the plan directory. If it starts producing junk, read that before trusting it.

Configuration

Everything lives in config.json in the data directory ($OVERSEER_HOME, else ~/.claude/overseer/). Claude Code's settings.json is only ever touched to register the hooks themselves, and nothing else needs to go in it.

python <overseer> --init-config   # write it
python <overseer> --config        # what is in effect

config.example.json in the repo documents every key inline; copy it over config.json if you would rather read than guess.

--config prints every setting with where it came from, which matters because an environment variable silently outranks everything else. Precedence is environment variable, then plugin option, then config.json, then the built-in default. The install dialog sets one plugin option, the API key, so every other setting is yours to change here or in the environment. A malformed value falls back to the default rather than crashing a hook.

Keys may be written either bare (model_stop) or prefixed (OVERSEER_MODEL_STOP). A typical two-model setup:

{
  "model": "gemini-3.5-flash-lite",
  "model_stop": "gemini-3.1-pro-preview",
  "timeout_ms": 90000
}

The API key is looked for in this order: GEMINI_API_KEY, GOOGLE_API_KEY, the plugin install dialog, api_key in this file, then .key in the data directory. --config names the one that won, so you never have to guess which copy is live.

Prefer the install dialog or --set-key. The dialog value goes to your OS keychain, --set-key writes .key owner-only, and neither leaves the key in a file you might later paste, commit or copy somewhere public.

Environment variables

Every one of these has a config.json equivalent without the OVERSEER_ prefix, in lower case. Use whichever suits; the variable wins.

Variable Default Purpose
GEMINI_API_KEY - Key, or GOOGLE_API_KEY, or .key
OVERSEER_MODEL gemini-3.5-flash-lite Reviewing model
OVERSEER_MODEL_STOP same as above Model for the blocking stop review
OVERSEER_CONFIDENCE 70 Minimum confidence to block on a correction
OVERSEER_MAX_BLOCKS 2 Correction blocks per session
OVERSEER_MAX_CONTINUES 3 Consecutive "keep going" pushes
OVERSEER_MAX_REPAIRS 2 Consecutive "fix the plan" blocks
OVERSEER_LOOKBACK_HOURS 12 How far back the evidence diff reaches
OVERSEER_RULES_CHARS 80000 Ceiling for global plus project CLAUDE.md
OVERSEER_RULE_CHECK_EDITS 1 Rule audit on each substantive edit, 0 leaves only the one at Stop
OVERSEER_AUDIT_WORKERS 8 Rulebook sections audited concurrently
OVERSEER_AUDIT_BUDGET 60 Seconds the whole rule audit may take
OVERSEER_CONTEXT_WINDOW 1000000 Context size of the model Claude runs
OVERSEER_CONTEXT_DEBRIEF_PERCENT 60 When to force a written handover
OVERSEER_TIMEOUT_MS 45000 Gemini request timeout
OVERSEER_NOTIFY 1 Tray balloons at all, 0 silences every one
OVERSEER_NOTIFY_DONE 1 Alert the moment a turn ends and it is your go
OVERSEER_NOTIFY_REPEAT 600 Seconds before the same message may repeat
OVERSEER_ASCII unset Force the plain ASCII watch UI
OVERSEER_COLOR unset 1 forces colour even when stdout is piped
NO_COLOR unset Standard opt-out, disables all colour

One review, two readers

Each review returns both a corrective_prompt written at Claude, and an owner_briefing written at you: plain English on what it actually did and whether to trust it. Same single API call, so watching costs nothing extra. Every field lands flat in .overseer-reviews.jsonl (verdict, confidence, issues, needs_human, human_question, and the rest) so a log line stands on its own.

Other modes

  • --watch is a two-pane terminal app over those logs. It never calls Gemini itself, so it cannot double up on cost, and it surfaces the fail-open errors hook mode otherwise swallows. Scoped to the current repo; --all shows every project. --interval sets the log poll in seconds, default 2, independent of the ~12fps repaint.

    The left pane is the index, one row per review with its time and badge: CLEAR, NITS, KEEP GOING, DEVIATED, INCOMPLETE, FALSE CLAIM, YOUR CALL, WAITING (Claude wants you) or FAILED (the overseer broke). The right pane is the selected review in full: briefing, verdict, confidence, every issue, the corrective prompt and any escalation. The bottom bar tallies reviews, flagged and escalations, and shows how long it has been quiet.

Key Does
up / down, k / j Move through the index
pgup / pgdn Scroll the detail pane
home Jump to the oldest review
end, f Jump to newest and resume following
x Excuse every rule this review cites, in this repo
u Undo the last excusal
q, esc Quit

Clicking a row in the left pane selects it, and clicking a single rule finding in the right pane excuses just that one rule.

It follows the newest review until you move the selection, then holds still so a live arrival cannot yank the thing you are reading out from under you. f resumes following. An escalation rings the terminal bell.

History in the viewer

It runs on the alternate screen buffer, the same one vim and less use, so the terminal's own scrollback does not apply and your shell comes back untouched when you quit. The app keeps its own history instead: the whole log for the current scope is loaded at startup and every new review is appended, so the index is the full record and nothing scrolls away. The log file remains the durable copy.

It degrades to ASCII on a console that cannot render UTF-8, and drops colour entirely when piped or when NO_COLOR is set. Keyboard control is Windows only; elsewhere it simply follows along.

  • --once runs one live review and prints it.
  • --cwd <path> targets a project other than the current directory.

Cost

Nothing at all in an unarmed repo. In an armed one: a review per plan-file write, a rule check per substantive edit, and per stop a review plus a rules audit. The stop review runs from about 50k characters on a small task to 150k on a heavy one, dominated by the diff and the closing message. Raise OVERSEER_LOOKBACK_HOURS and it grows.

Budget for the rule audit as several calls, not one. The rulebook is split into roughly 2500-character chunks and each chunk is audited separately, so the call count is your rulebook size over 2500: a 22k-character CLAUDE.md is 12 calls per audit, and the rules_chars ceiling of 80k would be about 32. Each call is small, carrying one slice of the rules plus only the new code, but the count is what to watch if you have a large rulebook. audit_workers caps how many run at once and audit_budget caps the whole thing in seconds.

The per-edit audit is the one that scales with how busy Claude is. It sees only the text the edit introduced, skips anything under 120 characters, and dedupes on a content hash so a retried write is not paid for twice. Set rule_check_edits: 0 to drop it and keep only the audit at Stop, which is itself cached on a hash of the rules plus the additions.

Dropping rules out of the main stop prompt paid for a good part of this: that prompt is now roughly 35k characters lighter.

Model choice matters less than call structure. A lite-tier model has been observed missing an explicit instruction it was given in full, and the fix was never a bigger model. It was asking one question at a time, on one slice of the rules at a time, and throwing away any answer that cannot quote the rule and the offending line back verbatim.

Split the models rather than paying pro rates on everything. Stop is the review that blocks, and there is one per turn, so it is the one worth spending on. Mid-task PostToolUse reviews can stay cheap: set model and model_stop in config.json.

Run --list-models first; ids come and go, and a listed model can still 404. The stop hook is installed with a 120 second timeout for this reason. If a pro review runs longer than OVERSEER_TIMEOUT_MS (45s) it fails open, meaning no review at all, so raise that too if you see FAILED rows in the viewer.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages