A Go CLI that steers Claude Code and OpenAI Codex CLI agents by registering as their hook command. On every hook event (PreToolUse, UserPromptSubmit, etc.) it judges the situation against a markdown constitution + a versioned precedent ledger, then returns one of:
allow— the agent proceeds with no frictionask— the host agent's own permission UI shows; the human decidesdeny— the action is blocked and the reason flows back to the agent
Every outcome is appended to the ledger so it becomes precedent for next time.
┌────────────────────────┐
tool call │ Claude Code / Codex │
prompt ────────► │ (host agent) │
└───────────┬────────────┘
│ spawn `overseer hook PreToolUse`
│ pipe JSON to stdin
▼
┌────────────────────────┐ ┌──────────────────┐
│ overseer (Go) │ ◄────── │ constitution/ │
│ rule × precedent │ │ *.md (markdown) │
│ decision tree │ └──────────────────┘
└───────────┬────────────┘ ▲
│ │ append
▼ │
┌────────────────────────┐ │
│ hookSpecificOutput │ ┌──────────────────┐
│ {permissionDecision} │ │ ledger │
│ + exit code │ │ (SQLite / Dolt) │
└────────────────────────┘ └──────────────────┘
go install github.com/fireharp/overseer@latest
overseer init # scaffold ~/.overseer/
overseer install # write hooks to ~/.claude + ~/.codex
overseer doctor # verifyoverseer install writes entries marked with _overseer: "managed" into ~/.claude/settings.json and ~/.codex/hooks.json. overseer uninstall removes only those marked entries — your other hooks are left untouched. Use --scope project to install to ./.claude/settings.json and ./.codex/hooks.json instead.
| Command | Purpose |
|---|---|
overseer init [--local] |
Scaffold ~/.overseer/ (or ./.overseer/ with --local). |
overseer install [--claude] [--codex] [--scope user|project] [--binary PATH] |
Register hooks. Idempotent. |
overseer uninstall [--scope ...] |
Remove only entries with our managed marker. |
overseer hook <event> |
Subprocess entry — Claude/Codex spawn this; reads stdin JSON, writes stdout JSON, exits 0/2. |
overseer rules list / show <id> / validate |
Inspect the loaded constitution. |
overseer ledger tail [-n N] / search <q> / outcome <event-id> <allow|deny|ask> |
Browse recent events, token-search, or record a human outcome. |
overseer ledger branch <name> / checkout <name> / merge <from> <into> |
Dolt-only branching. |
overseer why <event-id> |
Explain a past verdict — event, rules cited, precedent cited, human outcome. |
overseer doctor |
Self-check + dry-run a known-bad event. |
Markdown files in ~/.overseer/constitution/ (or ./.overseer/constitution/ for project-local):
shared.md → ScopeShared (apply everywhere)
project.md → ScopeProject (apply in this repo)
claude.md → ScopeAgent (Claude Code only)
codex.md → ScopeAgent (Codex CLI only)
One rule per block:
Rule: block-rm-rf-root
Priority: 1000
Scope: shared
When:
event=PreToolUse
tool=Bash
cmd=/(?:^|[\s'"();|&]|\/)rm\s+(?:-[A-Za-z]*r[A-Za-z]*f[A-Za-z]*|-[A-Za-z]*f[A-Za-z]*r[A-Za-z]*|-[A-Za-z]*r[A-Za-z]*\s+-[A-Za-z]*f[A-Za-z]*|-[A-Za-z]*f[A-Za-z]*\s+-[A-Za-z]*r[A-Za-z]*)(?:\s+--)?\s+\/(?:[\s'"();|&]|$)/
Do: deny
Reason: `rm -rf /` is almost certainly catastrophic.
Examples: rm -rf /, sudo rm -rf /, rm -rf -- /When: directives are AND-ed:
| directive | meaning |
|---|---|
event=PreToolUse,UserPromptSubmit |
only these hook events |
tool=Bash,Write |
only these tool names (PreToolUse / PostToolUse) |
path=**/.env,/etc/** |
candidate file paths must match a glob |
cmd=/regex/ |
bash command (or cmd field) must match the regex |
keyword=migrate,deploy |
content (prompt + command + paths) must contain a keyword |
Free text in When: (lines without key=value) becomes lowercase keyword matchers (length ≥ 3).
Do: is one of deny, ask, allow, or freeform guidance (treated as no-op for the verdict but still emitted in Reason).
1. Hard rule (deny / allow) wins, sorted by priority desc, then scope (agent>project>shared).
2. Otherwise tally precedent:
- if human-resolved outcomes lean deny ≥ allow and deny > 0 → deny
- if N similar past actions were allowed and none denied → allow
3. If an "ask" rule fired but precedent didn't decide → ask
4. If safe_mode and event=PreToolUse → ask
5. Default → allow
Tunables in ~/.overseer/config.toml:
safe_mode = false
[ledger]
backend = "dolt" # or "sqlite"
path = "~/.overseer/ledger"
[ledger.dolt]
branch = "main"
[precedent]
min_score = 0.25
allow_threshold = 3
top_k = 10
[privacy]
redact = true
patterns = [
'(?i)(api[_-]?key|secret|token|password)\s*[:=]\s*\S+',
'AKIA[0-9A-Z]{16}',
'[A-Za-z0-9+/]{40,}={0,2}',
]Two backends share one ledger.Store interface:
- SQLite (default, via modernc.org/sqlite — pure-Go, no CGO). Single file
<path>/ledger.db. FTS5 candidate search + Go-side Jaccard rerank. - Dolt (via the MySQL wire protocol; build with
-tags dolt). Same schema, plusbranch/checkout/mergefor experimentation. Requires a runningdolt sql-serveragainst your ledger directory.
To use Dolt:
go install -tags dolt github.com/fireharp/overseer@latest
cd ~/.overseer/ledger && dolt sql-server --user overseer --password '' &
# in ~/.overseer/config.toml:
# [ledger]
# backend = "dolt"
# path = "tcp(127.0.0.1:3306)/overseer"
# [ledger.dolt]
# branch = "main"Both Claude and Codex deliver the same six events with near-identical JSON shapes; overseer normalizes them. A PreToolUse event from either looks roughly like:
{
"hook_event_name": "PreToolUse", // Codex (snake_case) or "hookEventName" (Claude camelCase)
"session_id": "abc123",
"cwd": "/repo",
"tool_name": "Bash",
"tool_input": { "command": "rm -rf /" }
}Overseer responds with a hookSpecificOutput:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "rule block-rm-rf-root: ..."
}
}Exit code: 0 for allow/ask, 2 for deny. The --agent claude|codex flag (set automatically by overseer install) tells overseer which JSON shape to expect; without it, the parser sniffs by hookEventName vs hook_event_name keys.
Docs:
- Claude Code hooks — https://code.claude.com/docs/en/hooks.md
- Codex CLI hooks — https://developers.openai.com/codex/hooks
go build ./... # default (SQLite only)
go build -tags dolt ./... # with Dolt support
go test ./...
# Opt-in real Claude Code integration test.
OVERSEER_E2E_CLAUDE=1 go test ./e2e -run TestClaudeBlocksRmRfRoot -count=1 -vInspirations from sibling projects in this repo (TS / Python; not ported verbatim):
/z_archive/resolver/— markdown rule format, three-tier rule scoping, JSONL event ledger, Jaccard precedent search./z_archive/decisions/— OAVI governance vocabulary (Owner / Advisors / Veto / Informed) and decision-class taxonomy (Routine / Elevated / One-way-door / Emergency) — informs v1.1 escalation richness./z_archive/overseer/— confirms the hook-command-registration shape used by both vendors.
See docs/prior-art.md for file-level references.
v0.1 — works end-to-end against synthetic events. Smoke-tested with both Claude- and Codex-shape JSON payloads against a sandbox HOME. Real-agent integration (claude -p "rm -rf /") is covered by the opt-in ./e2e test.