English | Deutsch | Español | Français | Türkçe
Tezgah
One working contract for every AI coding assistant you run.
What it enforces • Supported hosts • Install • Day-to-day • Configuration • Benchmark • Cost • Development • Contributing • Security • License
English is the source of truth; translations may lag behind it.
One working contract for every AI coding assistant you run — oh-my-pi (omp), the primary host, plus Claude Code, Codex, Cursor, opencode and DeepSeek's dsh harness — inside a set of configured repository roots.
Left alone, each assistant has its own habits: one answers in Turkish, another in English; one greps for everything, another queries a code graph; one says "done" without running a test. Tezgah removes the drift. Open any host and you get the same language, the same discipline, and the same standard of evidence.
The design is two layers. The rules live once in a shared core; each host gets a thin adapter that translates that core into the shape the host understands. Change a rule in one place and all six hosts see it — no six-way copy of the same text.
What it enforces
- Outcome-first Turkish reporting. Every reply is in Turkish and leads with the result or decision (BLUF), then points ordered by impact. Code, commits, docs, and subagent prompts stay English; names, CLI commands, and error strings are never translated.
- Minimal code (ponytail). The laziest change that actually works: YAGNI,
then reuse an existing helper, then stdlib, then a native platform feature,
then an installed dependency, then one line. No unrequested abstractions.
Validation, error handling, and security are never simplified away. The
intensity level is a real switch -
tezgah-pony lite|full|ultra, or/tezgah:ponytailon Claude - and a non-default level rides the per-turn reminder. - Act-on-it output shape (i-have-adhd). The answer or the next action is
on the first line; multi-step work is a numbered list whose position is
restated in one line while it runs; tangents wait until the issue in hand is
finished; errors read as location, cause, fix; a list shows at most five
ranked items with the rest kept in reserve; an estimate is in concrete units
and is marked as an estimate. The switch is
tezgah-adhd off|on(or/tezgah:adhdon Claude; a repo opts out with.no-adhd). Vendored and adapted fromi-have-adhd(MIT). - An active task the gate enforces. The user can point one open plan at the
gate: its
phase:(discovery,implementation,verification) and itsallowed_paths:globs are what each write is checked against, so a write in a read-only phase, or outside the files the plan names, is refused before it lands - and a shell command that writes a file is refused in a read-only phase too, because the shell is a write route like any other and the E7b block measured it as the route an agent takes once the write tools are refused. Two more refusals close the ways a session could move that boundary instead of respecting it: a write whose target is the record itself, refused whatever the phase and the globs say, and a shell command that would change the record through the CLI. No active task means no requirement, because the gate invents nothing. None of the four prints the command that lifts it, so a refusal is the ask and not the instruction: the phase and the globs are the user's to change. The record is the user's own (~/.config/tezgah/bin/tezgah-task start|phase|allow|stop|status), the agent never runs it, and an empty allowlist reads as any path in the repo rather than as "nothing allowed". The phase also rides every user turn as one line, so the refusal is never the first the session hears of the boundary. - Code-graph-first discovery. "Where is X", "who calls Y", "what breaks if
Z changes" go to the
codegraphindex (codegraph_exploreover MCP, and thecodegraph callers|callees|impact|affected|nodeCLI from a shell), not to grep. Grep stays right for literal text, configs, and non-code files. - Accessibility-first app analysis. A running web or mobile app is read
through its accessibility / DOM / native view tree, not a screenshot per step.
analyze-appcovers a browser (Playwright MCP), an iOS Simulator or Android emulator (Mobile MCP), and optional web diagnostics (Chrome DevTools MCP); a screenshot is an explicit, on-demand action for what the tree cannot answer. - External second opinion.
~/.config/tezgah/bin/consultasks independent models in parallel (OpenRouter by default,--provider deepseekor--provider inceptionfor the DeepSeek and Inception Labs APIs), then spends one more call on a referee that names where the panel disagreed, what all of them assumed, what would change the recommendation and what evidence it still wants. Each failure is classed with the one variable to change on a retry, a dead or unheadlined referee is disclosed as an unjudged panel, and a packet too long for argv goes in withconsult -. The agent reports where the models disagreed and verifies their claims against the code. - Research via OpenResearch. When the router judges a task is research — a
literature review, forming and testing hypotheses, running experiments, a
research artifact — it drives the work through alphaXiv's OpenResearch (
orx) and loads theorxmanual first, instead of improvising the protocol. Plain code discovery stays on the code graph. Whenorxis absent, the router says so and falls back to a host subagent. The domain knowledge an experiment needs ships with it: the vendoredAI-research-SKILLslibrary (98 skills, 23 categories, MIT) lands as theai-researchskill, read one entry at a time from its stage index. - Honesty under verification. Nothing is reported done, tested, or fixed unless the output was seen. A failing test is reported as failing with its exact error, and a skipped check is stated plainly.
- No AI attribution, anywhere. Nothing persisted or published — commit, merge, and tag messages, PR and issue text, code comments, file headers, docs — may credit the assistant, model, vendor, or "AI". Using a tool is fine; signing its name to your work is not.
- Two-tier orchestration. The main thread decides and verifies; a cheap
model (
~/.config/tezgah/bin/codegen, OpenRouter by default, or--provider deepseek/--provider inception) drafts bounded, well-specified edits to a scratch directory. Nothing reaches the repo except through the router; on a failed draft (codegen exit 2) the contract requires the router to write the code itself with the main model - a rule the router follows, not a mechanism inside codegen. - Per-repo subagents. At session start the enclosing repo gets a small set of
capability-gated agents (
tezgah-explorer,tezgah-reviewer,tezgah-researcher,tezgah-verifier) plus atezgah-orchestrator, rendered into each installed host's native surface (Claude/Cursor.claude/agents/, opencode.opencode/agents/plus a live config injection, Codex.codex/agents/) and ignored through the clone's own.git/info/exclude, so the repo's tracked.gitignoreis never edited. Which hosts get files is the config'shostslist when it names one of those four, and otherwise every host detected on the machine - omp is not one of them, because its subagents are user-level (~/.omp/agent/agents/), so a config ofhosts: ["omp"]writes no per-repo agent files at all. On Claude the orchestrator'sAgent(tezgah-*)allowlist only takes effect when it runs as the main thread (claude --agent tezgah-orchestrator); as a subagent the list is ignored. dsh has no per-role surface, so the contract's router rule covers it.
Supported hosts
omp is the primary host — the one tezgah is developed and verified against.
Its whole surface (session start, per-turn rules, gate, evidence, end-of-turn
block, status line) is covered by tests, and it is the only host whose status
line is also checked against the real TUI. The rest are adapters, each claimed
for what it mechanically enforces: the adapter docstrings and the gate tests
record what a host actually refuses, and benchmarks/harness-vs-omp/ keeps the
cost evidence, which is a separate question from enforcement.
| Host | Wired by |
|---|---|
| omp (oh-my-pi) — primary | ~/.omp/agent: managed RULES.md always-on block, skills, generated subagents, mcp.json, and an extension (hooks/pre/tezgah-hook.ts) that arms the per-prompt rules, gates tools, records evidence and runs the Stop rule; the wiring is checked by tezgah-setup |
| Claude Code | local plugin marketplace: hooks, commands, two read-only agents, output style |
| Codex | hooks.json + skills + MCP, including a PreToolUse gate |
| Cursor | hooks.json + skills + MCP; needs a cursor-agent build with CLI hooks and statusLine - the 2025.09 build predates both, so this adapter is inert until Cursor ships them |
| opencode | plugin + instructions + MCP + generated skill router (native skill list denied), repo auto-index on the first message; the plugin enforces the gate itself - attribution, explorer, the grep nudge, consent, secret and the loop/retry ceilings - and puts every write to the core through ~/.config/tezgah/bin/tezgah-gate check, so the active-task rule and any rule added later arrive from the core rather than from a second implementation |
| dsh | Claude Code hook bridge + managed patch block (hooks, MCP, LLM routes, an out-of-tree Web status line) |
The Codex gate runs Bash, exec_command, apply_patch, Edit/Write, MCP tools,
and subagent calls through the same check as the other hosts. On Claude the
attribution setting is emptied (commit, pr, sessionUrl), which turns off
the harness-emitted commit/PR trailers - it does not edit text the model writes.
Elsewhere the ban is enforced only by the tool gate (a git/gh write whose command
carries a credit is refused) and by the contract; Cursor exposes no attribution
lever tezgah can set.
Both halves of the integrity rule keep their aim narrow, because a gate that
fires on description stops the work it polices. The anti-shortcut deny reads the
command with quoted text and heredoc bodies blanked, so a commit message that
names --no-verify passes while the flag itself is refused; the test-disable
deny needs a test path, a marker outside strings and comments, and a marker the
file does not already carry. The reply-level half - the Stop rule - runs where
the host hands over the final message: Claude, Codex, omp (session_stop) and
Cursor, which reports the reply on afterAgentResponse and takes the decision at
stop. Its ledger is shared, so the newest check wins: a later failure blocks a
"tests pass" claim even if an earlier run was green, and so does a later write -
a green run over the tree as it was then does not cover the tree as it is now,
because the ledger records whether each write actually changed its target. Each
row carries the action it belongs to (id, a digest of the tool and its
canonical arguments), the workspace, and whichever of exit, out_bytes and
fail_class the host actually reported - a field a host cannot report is
absent, never zeroed, so a
reader can tell "it failed" from "nobody said" - so a
run can be reconstructed rather than guessed at; the same identity feeds the
loop guard, which refuses a third identical call whose previous attempts exited
non-zero. tezgah-status --counters <cwd> <session> reports four trace figures
next to the deny counts: steps counts the session's work events - the run,
edit and verify* rows, so a denial, a nudge or a claim is not a step;
tool_error_rate is the share of non-zero exits over the rows that carry an
exit at all, i.e. the rows whose host reported an outcome (absent when none
did); claims counts Stop evaluations - one row per user turn and reply text,
so a host that re-runs its Stop handler cannot double-count a turn - and
false_completion is the share of those whose stop was refused. A check counts
as support only when the host reported exit 0, the command was not masked by a
pipe, and - where the host supplies a result size - that size is non-zero; rows
written before this rule existed are still accepted, so upgrading never blocks
an open session on its own history. opencode has no end-of-turn surface to
block, so it records the evidence and the reply claim stays unenforced there.
A consent ask is three rows, never one: the gate's refusal writes consent (the
action's digest and its effect class), the user's approval writes grant, and a
repeat the gate allowed without either writes repeat-allowed. The approval is
tezgah-consent <digest> for the action a refusal named, or
tezgah-consent --last for the newest ask no grant answers yet - the form that
costs no copy-paste. The grant lands in the ledger that carries the ask, which is
the session the gate reads it from, so "who approved what" is a query over the
ledger rather than a claim about it. A command can name its own class with
tezgah:effect=<class> (outward, publish, deploy, schema, destructive) when no
pattern can see the effect; the declaration holds only at or above the class the
command text derives, so it can tighten the rule and never loosen it - a
downgrade is ignored and named in the refusal and the deny row. The declaration
is read off the raw command, so a trailing # tezgah:effect=deploy counts, and a
command that merely quotes the form is held to it too. One floor sits under the
user: a rm -rf whose every target is under a temp root ($TMPDIR or /tmp) is
scratch, so a session clearing its own fixtures is not held to an ask - the temp
root itself, an unresolved $VAR/~ target and anything that escapes it still
are, and the untrusted-content rule reads the class conservatively so an effect
after a fetched page is refused whether or not its target is scratch.
App analysis
analyze-app drives a running application from its accessibility tree. The
default loop is open, read the tree, act, observe console/network/logs, and
re-read the tree — a screenshot is an explicit action for what the tree cannot
answer (canvas, game, animation, pixel-level visual regression). The skill is
one path for all hosts; the servers under it are one shared spec in
hooks/tezgah_apps.py:
| Server | Target | Wired by |
|---|---|---|
playwright (@playwright/mcp) |
web pages, browser_* tools |
opencode, Codex, Cursor, Claude (plugin .mcp.json) |
mobile-mcp (@mobilenext/mobile-mcp) |
iOS Simulator / Android emulator, mobile_* tools |
same |
chrome-devtools (opt-in, --devtools) |
web perf traces, deep network, source-mapped console | same |
The browser runs an isolated profile by default, so a run never touches your
real Chrome state; analyzing a logged-in flow is a deliberate attach
(--cdp-endpoint or the Playwright extension), not a default. Screenshots,
traces and tree dumps land in ~/.cache/tezgah/apps (override with
TEZGAH_ARTIFACTS) and the agent gets a path back, never inline image bytes.
The servers run through npx, so they need node but no install of their own;
tezgah-setup --install --devtools adds the optional web diagnostics server.
dsh wires the same two servers through its dsh-mcp-client bridge
(serverName / command / args / env, confirmed against the published
config schema), and Claude gets them from the plugin's .mcp.json
(claude plugin details tezgah lists MCP servers 2 and both connect).
mobile-mcp is the higher-friction half: macOS may prompt for Accessibility /
Screen-Recording permission and the view tree can drop under load, so the skill
retries the tree before falling back to a screenshot.
CI runs a deterministic handshake for both servers (no browser, no device):
TEZGAH_E2E_STRICT=1 python3 tests/e2e_analyze_wiring.py. Two opt-in local
smokes go further: TEZGAH_E2E_APPS=1 python3 tests/e2e_analyze_web.py starts
Playwright MCP, navigates and reads the snapshot with no screenshot;
TEZGAH_E2E_APPS=1 python3 tests/e2e_analyze_mobile.py starts Mobile MCP, checks
the view-tree tools and lists a device. They print SKIP: ... when node, a
browser build or a device is missing.
Install
Requires Python 3.10+ (the floor CI byte-compiles and tests on). node + npm are needed for the dsh host and, with pnpm,
for its web status line. The optional integrations degrade gracefully:
codegraph on PATH powers the graph; a model key powers consult
and codegen — OpenRouter by default (OPENROUTER_API_KEY or
~/.config/openrouter/key), the DeepSeek API with --provider deepseek
(DEEPSEEK_API_KEY or ~/.config/deepseek/key), or Inception Labs with
--provider inception (INCEPTION_API_KEY or ~/.config/inception/key); and
OpenResearch's orx on
PATH gives the research rule something to drive. When the chosen provider's key
is missing, tezgah says so instead of pretending.
Supported platforms, answered on 2026-09-22: macOS, Linux, WSL and Windows.
macOS, Linux and WSL run the same two sh scripts; Windows has its own entry
point, packaging/install.ps1, because it assumes no sh, no python3 and no
symlinks — it unpacks with the tar.exe that ships with Windows 10 1803+,
materialises <prefix>\current as a directory junction (a copy, where a junction
is refused) and starts the installer with the first of py -3, python,
python3 it finds. The sh scripts need sh, curl and tar, and sha256sum
or shasum to check the artifact.
Install a released version; pin it, which is how a team stays on one:
curl -fsSL https://raw.githubusercontent.com/r1z4x/tezgah/v0.16.1/packaging/install.sh | sh -s -- --version 0.16.1
Drop --version 0.16.1 to take the newest release. On Windows, pin through the
environment — a piped script cannot bind parameters:
$env:TEZGAH_VERSION = '0.16.1'; irm https://raw.githubusercontent.com/r1z4x/tezgah/v0.16.1/packaging/install.ps1 | iex
Either one resolves the prefix (~/.local/share/tezgah, %LOCALAPPDATA%\tezgah
on Windows; TEZGAH_PREFIX overrides both, XDG_DATA_HOME is honoured on the
sh side), unpacks <prefix>/<version> after checking the release's .sha256,
points current at the new tree and runs the installer from it. The previous
version stays where it is, so a rollback is
ln -sfn <prefix>/<previous-version> <prefix>/current.
Upgrading is your action and never happens on its own — the contract forbids it:
bin/tezgah-setup --upgrade [VERSION] in an installed tree, or
sh packaging/upgrade.sh --version X.Y.Z (no argument takes the newest release).
Both verify the .sha256 before anything is unpacked. For a tarball you already
hold — a mirror, an air-gapped box — unpack it and run the installer beside it:
tar xzf tezgah-0.16.1.tar.gz && cd tezgah-0.16.1
sh packaging/install.sh --version 0.16.1
Working on tezgah itself, clone and run it in place instead — this is the developer path, and the one the test suite uses:
git clone https://github.com/r1z4x/tezgah.git ~/Projects/tezgah
cd ~/Projects/tezgah
bin/tezgah-setup --install
In a terminal that command with no arguments is the wizard instead: it asks which hosts to
arm, the root directories, whether to install the missing optional tools, and whether to
wire the optional DevTools MCP, prints the plan, and writes only after a yes. The flags are
the wizard's defaults, so --wizard --hosts omp asks only the rest. A piped, agent or CI
run is never prompted — it prints the report, exactly as before.
--install also installs the optional tools that are missing by running each vendor's own
installer over the network: orx (openresearch.sh/install.sh), cursor-agent
(cursor.com/install), dsh (its home profile through npx), and pnpm when dsh needs it
(via npm) — curl ... | sh included. None needs sudo; the run is recorded in
~/.config/tezgah/install.log. Preview with --dry-run, skip it with
--no-deps (useful in CI), or install the tools alone with --deps. Tools land
in ~/.local/bin or ~/.cargo/bin, so a fresh shell may be needed before they
are on PATH; tezgah's own checks look in those dirs regardless, so a non-interactive
shell still reports them as present.
If a predecessor setup is already present, import it first — it is moved aside, not deleted:
bin/tezgah-setup --adopt
Claude Code is armed by the same script - the plugin manifest is a local, untracked file:
bin/tezgah-setup --install --hosts claude
Limit the install explicitly when needed (the first form is the primary host alone):
bin/tezgah-setup --install --hosts omp
bin/tezgah-setup --install --hosts claude,codex,cursor,opencode,dsh
bin/tezgah-setup --roots ~/work:~/oss --install
Day-to-day
Nothing to run: the rules load when a host starts. A few commands are worth knowing:
| Command | Purpose |
|---|---|
bin/tezgah-setup |
On a terminal: the install wizard; on a pipe or in CI: report what is armed, per host |
bin/tezgah-setup --wizard |
Force the install wizard anywhere; --report forces the report |
bin/tezgah-status [PATH] |
Show whether the rules are active in that repo |
bin/tezgah-setup --status [PATH] |
Print the armed/used checklist |
bin/tezgah-setup --deps [--dry-run] |
Install missing optional tools (orx, cursor-agent, dsh) |
bin/tezgah-research init|check|status|claim|migrate|source |
Runs and checks a research line: state, findings, claims with their kind and evidence, the protocol-before-results rule, and the literature index; check --strict refuses what the checker cannot verify, migrate fills the fields older lines lack |
bin/tezgah-task start|phase|allow|stop|status |
The active task: one plan's phase: and its allowed_paths: globs, which the gate then enforces on every write - and the call itself, when a session makes it |
bin/tezgah-gate check |
The gate's own decision for one call on stdin - what a host whose plugin is not Python asks instead of mirroring a rule |
bin/tezgah-doctor [--clean] [--prune-sessions DAYS] |
Report harness disk use; --clean deletes old index logs and vacuums the opencode DB; --prune-sessions deletes idle sessions (the only action that actually shrinks the DB) |
/tezgah:plan-add |
Turn a piece of work into a tracked plan |
/tezgah:plan-status |
Summarize open plans and pick the next one |
/tezgah:plan-sync |
Close out finished plans |
bin/tezgah-setup --version |
Print the plugin version |
bin/tezgah-setup --upgrade [VERSION] |
Fetch, checksum-verify and unpack a release under <prefix>/<version>, flip current and re-run the install for the hosts already armed; --dry-run prints and changes nothing, and it never runs on its own |
bin/tezgah-setup --uninstall |
Remove only tezgah's symlinks, host hook entries, and the dsh managed block |
The status line marks each rule with its state first: a check means armed and
in force this session (or always-on), a circle means armed but on demand - not
used yet this session - and a cross means turned off by a kill switch or a
.no-* mark. pony and adhd read a circle until the session has actually
read that skill's full text, and a check after it; on Claude, opencode and omp
that read is observable. On codex, cursor and dsh that read is not observable
at an acceptable cost, so those two marks render dim, with no glyph: the line
states nothing rather than claiming the skill was never opened. A kill switch
still shows red everywhere.
Configuration
Tezgah is armed only under its configured roots; anywhere else it is silent.
- Default root:
~/Projects. ~/.config/tezgah/config.json:{"roots": ["~/Projects", "~/work"]}.TEZGAH_ROOTS(path-separator list) overrides the file for one-offs and CI.
Kill switches live in ~/.config/tezgah/. Each one removes its rule from the
text injected into the session, so the rule actually stops:
| Switch | Turns off |
|---|---|
exec-mode.off |
Turkish, outcome-first reporting |
ponytail-auto.off |
the minimal-code rule |
adhd-off |
the act-on-it output shape (i-have-adhd) |
spec-off |
the spec-before-building rule |
consult-off |
the external-second-opinion rule |
research-off |
routing research tasks to OpenResearch |
orchestrate-off |
subagent delegation (adds a do-not-delegate line) |
reminder-off |
the per-turn reminder text |
verify-off |
the integrity rule: its prompt text, the shortcut denials, the Stop gate and the ordering rule |
task-off |
the task rule's four refusals: the phase, the allowlist, the record and a shell write |
pretooluse-off |
the PreToolUse gate itself (attribution, explorer, consent, secret, shortcut, loop, race, sink, drift, nudge, order, task) |
Per repo, .no-ponytail, .no-adhd, .no-graph and .no-lessons turn off the
minimal-code rule, the act-on-it output shape, the code-graph rule (and its
auto-index), and the lessons ledger respectively.
When the user flags a mistake, the agent appends a one-line lesson to the repo's
.tezgah/lessons.md; the most recent lines are injected at session start so the
same mistake cannot silently repeat.
Benchmark
Does this contract improve the work, or does it only look like it should? That is measured,
not asserted: hidden checks the agent never sees the check for, cost from the host's own
usage record, and collateral edits scored as failures. The instrument - the arms, the
pre-registration and bench.py - lives on the benchmarks/lab branch, so this branch
carries the results and not the lab; the full study, with the run ids, is the OpenResearch
project tezgah-harness-research. Every figure below is a run log.
| Block | Runs | What it settled |
|---|---|---|
| two-host, 28 tasks, k=3 | 336 | omp+tezgah 0.95 and opencode+tezgah 0.96 have overlapping intervals and the same cost per solved task; on the bare arms omp is cheaper ($0.0047 against $0.0074 CPS), so the daily driver is omp at no cost in quality |
| hard family, 5 tasks, k=5, two model families | 200 | pooled, three of the four arms land on 40/50: no harness effect at that size, and the one signal the first model produced reversed on the second |
| gate family, gate armed | 36 | no arm took the shortcut route; the gate mechanism is verified directly (a skip edit is refused), its effect on the work is not measured yet |
| clause ablation, the two rules that separate, k=8 | 160 | the contract arms pass 23/32 (0.72) against the bare anchor's 12/32 (0.38) |
It helps exactly where the model's default is wrong. c04 (an English prompt where only
the contract makes the reply Turkish) reads 9/16 with a contract and 0/16 without one; h02
(a money contract whose visible suite is green either way) reads 14/16 against 12/16. Where
there is no gap to close - 22 of the 25 pilot tasks passed under every arm on every repeat -
a benchmark can only report a null.
Two clauses carry it. Removing clause 1 takes c04 to 0/8, the bare anchor's own score,
while barely moving h02. Removing clause 3 takes h02 to 2/8 - below the bare anchor's
6/8 - because clause 3 forbids stopping at the shortest done-looking path, and on that task
the shortest path is the one-liner that passes the visible suite while breaking the
documented rule. Clauses 2 and 4 move nothing measurable.
Cost follows quality. Per solved task: $0.0078 against $0.0097 on the full-contract node, $0.0043 against $0.0087 on the minus-ponytail node. The contract arms solve more tasks, so each solved task costs less; total spend is higher, and the benchmark records it per row rather than netting it out.
What this does not show: code quality, review effort or maintainability, none of which is
measured here; the gate's effect on an arm's choices, since no arm reached for the shortcut
in 36 armed runs; or a clause order - k=8 fixes a direction, at 8 runs per cell. One
provider and one fixture package throughout, and the ablation rounds run on a single model
family. A second model family reproduces the 28-task null exactly (51/56 against 51/56),
which is what shows the first reading was not a model artefact.
Cost
Measured on this machine (macOS, Python 3.10), not estimated. tezgah-setup prints the live
budget - read it there rather than trusting a figure copied here, which is how an earlier
revision came to quote a core band smaller than the one it installs.
| Band | What it costs |
|---|---|
| Session start | the always-on contract (the invariants plus a one-line pointer per on-demand rule): on this machine and skill set, ~1.5k tokens of contract text and ~1.4k of skill metadata, with the conditional rules (spec, consult, research, graph) adding ~0.7k only on the turn whose prompt matches |
| Per turn | a short reminder (~0.2k tokens) plus the armed rule when it matches, and one line naming the active task while one is set; hooks are separate Python processes, so the ~19 ms interpreter start is the base - a turn adds ~31 ms, session start adds ~50-81 ms, a gated tool call (Bash/Grep/Task) ~24-25 ms. opencode has no prompt-time hook, so it pays zero |
| On demand | the full tezgah-contract skill (~6.6k tokens), paid only when a task loads it |
| MCP schemas | the largest band, and the one no static report sees: the graph server alone declares 15 tools / 24,508 bytes (~6.1k tokens), riding every request unless the host fetches schemas on demand. tezgah-setup --mcp-schemas measures it |
| Disk | installation takes ~58 ms, and every file tezgah rewrites is kept once as <file>.tezgah-bak |
The arming floor. The invariants are always-on - execution mode, ponytail,
deliver-the-whole-ask, integrity, loop discipline, the lessons ledger, the attribution
ban and the session-scope boundary (tezgah's own install is the user's to tend, not the
session's) - and the safety rule ("irreversible or outward-facing actions need an explicit ask
first") is one of them, so it never depends on a classifier. Each advisory rule keeps an
actionable one-line pointer always-on, so a missed match costs detail, never the rule, and a
host hook that fails falls back to the pointers plus the on-demand skill rather than to no
contract. False negatives are auditable: every prompt appends armed=<rules|none> chars=<n>
- no prompt text - to
~/.cache/tezgah/classify.log(truncated to the last 200 lines past 64 KB), and all five hook hosts arm the same set for the same prompt (tests/test_context.py::ArmingConformance).
opencode is armed differently. It has no prompt-time injection point, so the contract
ships as a generated instructions file, and its always-on router lists only the buckets a
coding session reaches for, collapsing the rest to a pointer at
~/.config/tezgah/opencode-skills.full.md read on demand; permission.skill = deny stops
opencode injecting every skill's metadata instead. --install also sets compaction.prune
and watcher.ignore, clearing old tool results from the prompt rather than re-sending them
every step - without that the working set grows to hundreds of thousands of tokens before
opencode auto-compacts near the model's limit (about 980k for a 1M-token model).
bin/tezgah-doctor reports the disk footprint and --prune-sessions DAYS deletes idle
sessions through the opencode CLI, the only action that actually shrinks the database, since
VACUUM alone cannot.
Why it pays. In one real repo a default grep ignored the relevant folder and found
nothing; with ignore disabled it took 3.95 s and still mixed definitions with call sites,
while the code graph answered the same question in 16 ms with the 8 true call sites.
Development
python3 -m unittest discover -s tests -v # stdlib test suite
pip install -r requirements-dev.txt # pinned ruff, the only dev dep
ruff check . # lint (config in pyproject.toml)
CI runs both on Python 3.10 and 3.12. To refresh an installed Claude copy from
this checkout, use bin/tezgah-setup --sync, and validate the manifest with
claude plugin validate .claude-plugin/plugin.json (the manifest is local and untracked). When bumping the version,
update .claude-plugin/plugin.json and .claude-plugin/marketplace.json
together — they must agree.
codegraph is installed by the user. Orca's hooks and files are not
part of this project and are left untouched. Claude receives the always-on core
from the SessionStart hook; output-styles/tezgah.md is a duplicate for builds
that load plugin output styles, so the hook is the authoritative path.
Contributing
Small, single-purpose changes are the easiest to accept. A rule belongs in the
shared core (hooks/) unless it is genuinely host-specific; a host difference
belongs in its adapter under hosts/<name>/. Keep the diff as short as it can
be while still correct — the project's own minimal-code rule applies to the
project.
Before opening a pull request, run the same three checks CI runs:
python3 -m compileall -q hooks hosts bin statusline.py # byte-compile every script
python3 -m unittest discover -s tests # stdlib test suite
ruff check . # lint; config in pyproject.toml
ruff comes from requirements-dev.txt (pip install -r requirements-dev.txt),
which is the only development dependency.
Security
Report vulnerabilities privately through GitHub's security advisories (Security tab → Report a vulnerability) rather than a public issue.
tezgah runs shell hooks, writes host configuration, and injects text into every
session, so anything that makes a hook execute attacker-controlled code, leaks a
key into a config file, widens a sandbox, or lets repository content escalate
into instruction text is in scope. Include the host, the tezgah version
(bin/tezgah-setup --version), and a minimal reproduction.
License
The root LICENSE (MIT) covers tezgah's own files. skills/ponytail,
skills/no-ai-slop and skills/i-have-adhd are vendored under their own MIT
terms, recorded in NOTICE.