@r1z4x/tezgah 1.5.1 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +126 -1
- package/MANIFEST +11 -0
- package/README.md +6 -3
- package/bin/consult +13 -10
- package/bin/tezgah-docs +18 -16
- package/bin/tezgah-doctor +152 -0
- package/bin/tezgah-research +286 -12
- package/bin/tezgah-route +48 -1
- package/bin/tezgah-setup +27 -14
- package/bin/tezgah-task +771 -46
- package/bin/tezgah-taste +48 -10
- package/docs/architecture.md +7 -4
- package/docs/citations-baseline.json +1 -1
- package/docs/clarity-baseline.json +5 -5
- package/docs/contract.md +112 -25
- package/docs/evidence.md +201 -28
- package/docs/feature-audit.md +25 -4
- package/docs/gate.md +70 -33
- package/docs/glossary.md +4 -4
- package/docs/hosts.md +8 -8
- package/docs/index.json +30 -10
- package/docs/judge.md +9 -3
- package/docs/models.md +35 -2
- package/docs/operations.md +103 -3
- package/docs/research.md +392 -13
- package/docs/skills.md +24 -6
- package/docs/status-line.md +5 -5
- package/docs/testing.md +2 -2
- package/docs/vision.md +12 -4
- package/hooks/hooks.json +2 -2
- package/hooks/lang/en.json +323 -0
- package/hooks/lang/tr.json +344 -0
- package/hooks/projects-posttooluse.py +24 -5
- package/hooks/projects-pretooluse.py +9 -4
- package/hooks/projects-stop.py +28 -16
- package/hooks/tezgah_agents.py +29 -9
- package/hooks/tezgah_clarity.py +7 -3
- package/hooks/tezgah_components.py +2 -1
- package/hooks/tezgah_context.py +460 -390
- package/hooks/tezgah_gate.py +640 -104
- package/hooks/tezgah_integrity.py +1480 -197
- package/hooks/tezgah_models.py +337 -15
- package/hooks/tezgah_paths.py +103 -10
- package/hooks/tezgah_policy.py +286 -130
- package/hooks/tezgah_product.py +107 -0
- package/hooks/tezgah_rank.py +29 -27
- package/hooks/tezgah_replay.py +200 -30
- package/hooks/tezgah_research.py +1297 -12
- package/hooks/tezgah_skill_pick.py +58 -17
- package/hooks/tezgah_stopspec.py +7 -0
- package/hooks/tezgah_store.py +74 -27
- package/hooks/tezgah_task.py +917 -8
- package/hosts/codex/hook.py +9 -10
- package/hosts/cursor/hook.py +21 -14
- package/hosts/dsh/hooks.json +2 -2
- package/hosts/omp/hook.py +44 -23
- package/hosts/omp/tezgah-hook.ts.in +93 -7
- package/hosts/opencode/plugins/tezgah.js +119 -39
- package/output-styles/tezgah.md +76 -42
- package/package.json +1 -1
- package/skills/analyze-app/SKILL.md +57 -0
- package/skills/design-contract/EVALS.md +24 -1
- package/skills/design-contract/SKILL.md +17 -1
- package/skills/design-contract/data-views.md +86 -0
- package/skills/feature-audit/SKILL.md +32 -3
- package/skills/i-have-adhd/EVALS.md +22 -2
- package/skills/i-have-adhd/SKILL.md +12 -5
- package/skills/plan-add/EVALS.md +8 -8
- package/skills/plan-add/SKILL.md +22 -21
- package/skills/plan-sync/EVALS.md +2 -2
- package/skills/plan-sync/SKILL.md +14 -8
- package/skills/pm-frameworks/SKILL.md +6 -0
- package/skills/ponytail/EVALS.md +21 -2
- package/skills/ponytail/SKILL.md +4 -4
- package/skills/product-analysis/EVALS.md +23 -1
- package/skills/product-analysis/SKILL.md +50 -11
- package/skills/product-flow/EVALS.md +81 -0
- package/skills/product-flow/SKILL.md +91 -0
- package/skills/research/SKILL.md +99 -2
- package/skills/tezgah-contract/EVALS.md +52 -7
- package/skills/tezgah-contract/SKILL.md +204 -85
package/docs/gate.md
CHANGED
|
@@ -34,7 +34,7 @@ writes stays under `~/.cache/tezgah/replay`. The corpus, the exclusions and the
|
|
|
34
34
|
|---|---|---|
|
|
35
35
|
| claude, dsh | `hooks/projects-pretooluse.py::main` (`hosts/dsh/hooks.json:13-14`) | `hookSpecificOutput.permissionDecision: "deny"` |
|
|
36
36
|
| codex, cursor, omp | `hosts/codex/hook.py::main`, `hosts/cursor/hook.py::dispatch`, `hosts/omp/hook.py::handle` | that host's own envelope — [hosts.md](hosts.md) |
|
|
37
|
-
| opencode | `hosts/opencode/plugins/tezgah.js:
|
|
37
|
+
| opencode | `hosts/opencode/plugins/tezgah.js:2429` | `new Error(deny)` thrown at `hosts/opencode/plugins/tezgah.js:2590` |
|
|
38
38
|
|
|
39
39
|
Every refusal is also recorded before it is returned: `_deny` appends a ledger row `deny` whose `detail` is `"<rule>: <reason, first 80 chars>"`, plus any
|
|
40
40
|
`extra` (`hooks/tezgah_gate.py::_deny`). That row, not the reason wording, is where "why was this denied" is answered, and `<rule>` is the name used below.
|
|
@@ -93,7 +93,7 @@ It reads the arguments of `touch`, `rm`, `rmdir`, `unlink`, `mv`, `chmod`, `mkdi
|
|
|
93
93
|
It reads the value of `cp -t`, `curl -o`, `wget -O`, `tar -C` on extraction and `unzip -d` (`OPTION_TARGETS`). It expands `~`, `$HOME` and the XDG
|
|
94
94
|
variables first. It follows a `cd` within the line and reads past `if`/`then`/`do`/`!`/`{`. It opens `eval` and `bash -c`, and any flag cluster holding
|
|
95
95
|
`c` is `-c`. A path counts as written and with its last link resolved. A quoted word is a word, so a grep or a commit message that names a switch passes. `SHELL_WRITE` stays as it was: it feeds the
|
|
96
|
-
task phase rule and the opencode mirror. The task CLI
|
|
96
|
+
task phase rule and the opencode mirror. The task CLI is not a control-plane target: the session runs it itself (see the task rule below). Told: what the call would
|
|
97
97
|
change, and that the user changes it in their own terminal (`CONTROL_DENY` `hooks/tezgah_gate.py::CONTROL_DENY`). The text names no command. Standing. No switch
|
|
98
98
|
but `pretooluse-off` removes the rule, and that switch is a protected file. The rule cannot tell a change the user asked for from one the agent chose. It
|
|
99
99
|
refuses both inside the session, and it never sees the user's own terminal. Plan 050's replay of three weeks of real ledgers found the two evidence-only
|
|
@@ -130,7 +130,7 @@ Passes: a line that opens with `set -o pipefail` (`hooks/tezgah_integrity.py::PI
|
|
|
130
130
|
(`verify_ok`/`verify_fail`, [evidence.md](evidence.md)); a redirect to a file; a pipe of anything that is not a check (`git log | head`). The
|
|
131
131
|
remedy is one sentence, `hooks/tezgah_integrity.py::PIPED_REMEDY`, which the always-on core and the contract skill quote too. Told: keep the check last with its output in a file, then read the file in a separate call, or open the line with `set -o pipefail;`. A `; tail` on the same line hands the status to `tail`. The refusal names the check as it was typed. Standing, under `verify-off`
|
|
132
132
|
(`hooks/tezgah_gate.py::decision`). opencode asks the core for this rule on any shell line that pipes a check
|
|
133
|
-
(`hosts/opencode/plugins/tezgah.js:
|
|
133
|
+
(`hosts/opencode/plugins/tezgah.js:2515-2519`) and records such a line as `verify` when a pipe or a later command owns its status (`statusHidden`, `hosts/opencode/plugins/tezgah.js:445`).
|
|
134
134
|
|
|
135
135
|
### Attribution — an AI/model credit on its way into an artifact
|
|
136
136
|
|
|
@@ -144,9 +144,9 @@ a `git`/`gh` write for it to be this rule's. An MCP `write` or `publish` call me
|
|
|
144
144
|
|
|
145
145
|
### Language — an identifier or message that is not English
|
|
146
146
|
|
|
147
|
-
Trigger, shell: a command that would create an identifier - `git checkout -b`/`git switch -c`, `git branch` with a new name, a commit subject (`git commit -m`/`--message`/`-F`), or a `gh pr|issue create --title` (the language section `hooks/tezgah_gate.py:
|
|
147
|
+
Trigger, shell: a command that would create an identifier - `git checkout -b`/`git switch -c`, `git branch` with a new name, a commit subject (`git commit -m`/`--message`/`-F`), or a `gh pr|issue create --title` (the language section `hooks/tezgah_gate.py:355-468`, `hooks/tezgah_gate.py::created_texts`; the text is read from the raw command, because `mask()` blanks quoted strings and a commit subject is one) - whose name or subject is **not English**: a letter outside ASCII anywhere (`hooks/tezgah_lang.py::non_english` - a predicate rather than one language's letter list, so Russian, Greek, Arabic, Chinese and accented Latin hit it exactly as Turkish does) or an ASCII-folded Turkish word from the curated list (`hooks/tezgah_lang.py::ENGLISH`, wi…
|
|
148
148
|
|
|
149
|
-
The rule is a **heuristic** and its own text says so: a word list is not a language detector, so it cannot tell a proper noun or a product name in another language from a word that ought to be English, and a listed word may be the user's own term. A command that only names the words - `echo`, `grep`, `git log --grep` - creates nothing and passes. An identifier outlives the session that typed it, which is the reason the rule exists at all: `plan/004-admin-durum-onarimi` is in a public history from the moment it exists. On **opencode** the rule is not ported: the plugin asks the core for it through `bin/tezgah-gate check`, one spawn for a command its pre-test reads as identifier-creating (`IDENT_CMD` `hosts/opencode/plugins/tezgah.js:585`, the call site `hosts/opencode/plugins/tezgah.js:
|
|
149
|
+
The rule is a **heuristic** and its own text says so: a word list is not a language detector, so it cannot tell a proper noun or a product name in another language from a word that ought to be English, and a listed word may be the user's own term. A command that only names the words - `echo`, `grep`, `git log --grep` - creates nothing and passes. An identifier outlives the session that typed it, which is the reason the rule exists at all: `plan/004-admin-durum-onarimi` is in a public history from the moment it exists. On **opencode** the rule is not ported: the plugin asks the core for it through `bin/tezgah-gate check`, one spawn for a command its pre-test reads as identifier-creating (`IDENT_CMD` `hosts/opencode/plugins/tezgah.js:585`, the call site `hosts/opencode/plugins/tezgah.js:2515-2519`).
|
|
150
150
|
|
|
151
151
|
Off: `lang-off`, the rule's own switch (`hooks/tezgah_gate.py::decision`), and `pretooluse-off` drops it with the whole gate.
|
|
152
152
|
|
|
@@ -155,26 +155,44 @@ Off: `lang-off`, the rule's own switch (`hooks/tezgah_gate.py::decision`), and `
|
|
|
155
155
|
Trigger: another session recorded a write of this call's file inside `RACE_WINDOW_MIN = 10` minutes (`hooks/tezgah_gate.py::RACE_WINDOW_MIN`). The call's files are
|
|
156
156
|
its path field, its `apply_patch` headers, or a shell redirect or `tee` target (`hooks/tezgah_gate.py::write_paths`, `hooks/tezgah_gate.py::race_reason`).
|
|
157
157
|
The reader counts an `edit` row and a shell `run` row that carries a `target` (`hooks/tezgah_integrity.py::writers_elsewhere`).
|
|
158
|
-
Scratch is skipped: a temp file or a device (`hooks/tezgah_integrity.py::scratch_target`). A `run` row that writes only scratch carries no `target`.
|
|
158
|
+
Scratch is skipped: a temp file or a device (`hooks/tezgah_integrity.py::scratch_target`). A `run` row that writes only scratch carries no `target`. The rule also skips the harness's own channels (`hooks/tezgah_gate.py::URI_CHANNEL`), such as `agent://`, `xd://` and `conflict://`. It skips a shell target with an unknown variable too.
|
|
159
|
+
It skips this session's own subagent once its slice record has closed after the subagent's newest write of the file (`hooks/tezgah_gate.py::ended_child`). The writer ledger's `spawned` row names the child, and the record is read after `pending` closes an ended omp child. A child with no record, an open one, or a write newer than the close stays refused.
|
|
159
160
|
Told: re-read the file and re-apply the change to what is on disk now (`RACE_DENY`
|
|
160
|
-
`hooks/tezgah_gate.py::RACE_DENY`).
|
|
161
|
+
`hooks/tezgah_gate.py::RACE_DENY`). The text names each writer by its relation to this session (`hooks/tezgah_gate.py::RACE_WHO`, `hooks/tezgah_integrity.py::_relation`).
|
|
162
|
+
The relations are your subagent, your parent, a sibling subagent, an omp fork, the fork's origin and an unrelated session. The omp hook writes a fork's `forked` row from its header's `parentSession` (`hosts/omp/hook.py::handle`).
|
|
163
|
+
Standing (`RACE_REFUSE = True` `hooks/tezgah_gate.py::RACE_REFUSE`) — a notice would not close a silent overwrite — and it lifts only by time.
|
|
161
164
|
|
|
162
|
-
|
|
165
|
+
A write into another repository than cwd's gets one notice per session and target repository, never a refusal (`hooks/tezgah_gate.py::cross_project_note`). It says that repository's plans, lessons and slice journal do not apply to this session. A worktree counts as its main checkout, and a repository's own `.tezgah` as that repository. Claude, dsh, Codex, Cursor and omp carry it on their post-tool channel. opencode does not.
|
|
166
|
+
|
|
167
|
+
### Lease — a sibling slice owns this file
|
|
168
|
+
|
|
169
|
+
Trigger: a write into a file an open sibling slice owns, or into the main checkout while the writer's own brief declares a worktree (`hooks/tezgah_gate.py::lease_reason`).
|
|
170
|
+
A brief's `Owns:` line holds the globs, split on commas and spaces, or lists them as `- glob` items under it. The slice record stores them as `owns` (`hooks/tezgah_task.py::owned_globs`, `hooks/tezgah_task.py::open_slice`). The owner is the open record whose globs match (`hooks/tezgah_task.py::owner_of`). A reader, an agent named for a read-only role such as a reviewer, holds no lease.
|
|
171
|
+
The main-checkout refusal reads the worktree a `Worktree:`, `Checkout` or `Cwd:` line declares, the slice's own task first (`hooks/tezgah_task.py::_declared_in`). A path the brief only names declares nothing, and a declared main checkout means the slice works there. The journal still keeps the named worktree for reconcile, resume and the board.
|
|
172
|
+
A shell target resolves against the call's own `cwd` and every `cd` or `pushd` before it (`hooks/tezgah_gate.py::_shell_dir`). A redirect to `/dev/*` or a descriptor writes no file.
|
|
173
|
+
A slice may write a path its own `Owns:` names. The `.tezgah` directory and scratch stay free, and a closed owner frees its files.
|
|
174
|
+
On omp, the ledger's `spawned` row maps the writing session to its record (`hooks/tezgah_integrity.py::spawn_of`, `hooks/tezgah_task.py::slice_of`). A Claude background child maps through the `agent_id` its calls carry, which the spawn's result named (`hooks/projects-posttooluse.py`). Its SubagentStop closes the record (`hooks/tezgah_task.py::close_child`). Everything else fails open. A Claude foreground agent, dsh, Cursor and opencode open the record only once the child has ended, and Codex writes no `spawned` row.
|
|
175
|
+
Told: the owner, its globs, how to reach it - omp `write agent://<name>`, Claude `SendMessage`, else the parent - and the `tezgah-task slice done <id>` that frees it (`hooks/tezgah_gate.py::LEASE_DENY`, `hooks/tezgah_gate.py::MAIN_CHECKOUT_DENY`). Standing while the owner's record runs.
|
|
176
|
+
Off: `orchestrate-off`, whose fan-out rule it backs, and `slices-off`, which keeps no record to read (`hooks/tezgah_gate.py::decision`).
|
|
177
|
+
A spawn whose writing slices name one worktree gets a warning, never a refusal, since a reviewer panel shares a checkout freely (`hooks/tezgah_gate.py::spawn_advice`, `hooks/tezgah_task.py::worktree_clash`). The warning covers two slices in the call and one beside an open sibling. Omp, Claude and dsh carry it with the spawn's result.
|
|
178
|
+
|
|
179
|
+
### Task — the active plan's phase and path allowlist
|
|
163
180
|
|
|
164
181
|
Trigger: a write tool, or a shell command that writes a file, while an active task is set — a plan file under `.tezgah/plans/open` whose frontmatter carries a valid
|
|
165
182
|
`phase` (`tezgah_task.active`). The phase refuses a write in a reading phase (`hooks/tezgah_gate.py::TASK_PHASE_DENY`) and a shell command that writes one (`hooks/tezgah_gate.py::TASK_SHELL_DENY`,
|
|
166
|
-
against `hooks/tezgah_gate.py::SHELL_WRITE`); the allowlist refuses a target outside it (`hooks/tezgah_gate.py::TASK_SCOPE_DENY`) — a write tool's path, and a shell command's redirect or `tee` target (`hooks/tezgah_gate.py::write_paths`); and
|
|
167
|
-
(`hooks/tezgah_gate.py::TASK_RECORD_DENY`)
|
|
168
|
-
|
|
169
|
-
(`
|
|
170
|
-
Scratch meets no allowlist: a temp file or a device, such as `pytest > /tmp/check.log` (`hooks/tezgah_gate.py::task_reason`). Build output inside the repo stays held.
|
|
171
|
-
(`hooks/tezgah_gate.py::
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
183
|
+
against `hooks/tezgah_gate.py::SHELL_WRITE`); the allowlist refuses a target outside it (`hooks/tezgah_gate.py::TASK_SCOPE_DENY`) — a write tool's path, and a shell command's redirect or `tee` target (`hooks/tezgah_gate.py::write_paths`); and a write whose target IS the record, by a write tool or a shell redirect/`tee`, is refused
|
|
184
|
+
(`hooks/tezgah_gate.py::TASK_RECORD_DENY`), because only `bin/tezgah-task` writes it and that CLI makes the checks a retyped frontmatter would skip. Told:
|
|
185
|
+
the move that lifts it, made by the session itself — `tezgah-task phase implementation` or `tezgah-task allow <glob> ... --why "<reason>"` — since the orchestrator owns the plan lifecycle and the CLI refuses
|
|
186
|
+
the move until the plan's Acceptance, spike and checkpoint are in order, and records the reason for a widened scope (`allowed_why:`). The record's body has its own writers, on any open plan. `tezgah-task tick NNN <n> [--undo]` ticks Acceptance item n (`bin/tezgah-task::cmd_tick`). `tezgah-task note NNN state|next "<text>"` replaces the newest `## State` or the `## Next` body (`bin/tezgah-task::cmd_note`). Neither commits. Standing: only the record changing lifts it.
|
|
187
|
+
Scratch meets no allowlist: a temp file or a device, such as `pytest > /tmp/check.log` (`hooks/tezgah_gate.py::task_reason`). Build output inside the repo stays held.
|
|
188
|
+
Three targets are no project file, so neither the phase nor the allowlist refuses them (`hooks/tezgah_gate.py::unnamed_target`). A URI is one: a tool device (`xd://<tool>`), an agent message (`agent://X`) or a conflict resolution (`conflict://2`). A `file://` URI names a checkout file and stays held. A shell target with an unknown variable is another. `write_paths` first expands a variable from an assignment earlier on the line. So the allowlist holds `B=src; git diff > $B/x.patch` as `src/x.patch`. A variable the same line set to the checkout, such as `R=$(git rev-parse --show-toplevel)`, is read as the checkout (`hooks/tezgah_gate.py::known_tail`). The third is tezgah's own workspace under `.tezgah/`, such as research lines, lessons and briefs (`hooks/tezgah_gate.py::workspace_free`). The slice journal stays held. So does a plan file whose text sets `phase:` and every shell write into `.tezgah/plans/open/`, because such a file could make itself the active task. The record rule still refuses the active record there.
|
|
189
|
+
The gate matches a file in a linked worktree of the same repository relative to that worktree's top (`hooks/tezgah_task.py::relative`). Each writing slice gets its own worktree, so `hooks/a.py` there is in scope when `hooks/a.py` is. Only the allowlist reads a sibling: the record and workspace rules keep the main root. The worktree's admin directory must point back at it, and a worktree of another repository stays outside. Off: `task-off`
|
|
190
|
+
(`hooks/tezgah_gate.py::decision`). It fails open wherever the question cannot be answered: no active task, no readable record.
|
|
191
|
+
|
|
192
|
+
The fourth refusal under the same rule is the checkpoint. Trigger: a write, or a shell command that writes a file (`hooks/tezgah_gate.py::SHELL_WRITE`), while the task is in
|
|
175
193
|
`phase: implementation`, its record reads `checkpoint: pending <sha>`, that sha is still the checkout's HEAD and the tree is dirty (`checkpoint_reason()`
|
|
176
194
|
`hooks/tezgah_gate.py::checkpoint_reason`, `hooks/tezgah_gate.py::CHECKPOINT_DENY`). Told: the task it read, that the record holds a `pending` checkpoint, and the one command
|
|
177
|
-
that makes the boundary real -
|
|
195
|
+
that makes the boundary real - it names that command because running it *satisfies* the rule rather than taking it off. Standing: the recorded sha is the switch, not the tree - the commit
|
|
178
196
|
the refusal names moves HEAD off that sha, so the refusal clears itself, where keying on the word `pending` alone left the phase waiting on a record only
|
|
179
197
|
`bin/tezgah-task` rewrites. Off, with the rest of the rule: `task-off`. It fails open wherever the question cannot be answered: no record, no `checkpoint:` key, a
|
|
180
198
|
checkpoint that is a plain sha (the boundary is already real), a `pending` with no sha, a sha this checkout's HEAD does not name, and a repo whose HEAD cannot be read.
|
|
@@ -236,7 +254,7 @@ tail still shows that failure, under `verify-off` (`hooks/tezgah_gate.py::decisi
|
|
|
236
254
|
### Loop — the identical attempt that already failed twice
|
|
237
255
|
|
|
238
256
|
Trigger: the same call (same `call_id`) failed `LOOP_ATTEMPTS = 2` times already in the current user turn (`hooks/tezgah_gate.py::LOOP_ATTEMPTS`, `hooks/tezgah_gate.py::loop_reason`). Told: attempt N
|
|
239
|
-
of an identical call, the failure class read from the ledger (`transient`, `user`, `permanent`, `unknown`; the cap is the same for every class, and a `user` failure - a 401/403, a missing credential, a login - says to ask the user), "change the approach or stop" (`hooks/tezgah_gate.py::loop_reason`). Standing while the ledger tail still shows those
|
|
257
|
+
of an identical call, the failure class read from the ledger (`transient`, `user`, `permanent`, `unknown`; the cap is the same for every class, and a `user` failure - a 401/403, a missing credential, a login - says to take a route that needs no credential first and ask the user once only when none is left), "change the approach or stop" (`hooks/tezgah_gate.py::loop_reason`). Standing while the ledger tail still shows those
|
|
240
258
|
failures. Under `verify-off` (`hooks/tezgah_gate.py::decision`).
|
|
241
259
|
|
|
242
260
|
### Retry — the session-wide ceiling
|
|
@@ -264,6 +282,18 @@ Trigger: 25 work rows (`hooks/tezgah_gate.py::DRIFT_STEPS`) in the current user
|
|
|
264
282
|
`hooks/tezgah_gate.py::effectful`). An MCP call of any verb class counts as an effectful call too. Told: the standing constraints re-stated, or the delta since the turn began; "re-issue this call unchanged and carry on" (`hooks/tezgah_gate.py::DRIFT_DENY`,
|
|
265
283
|
`hooks/tezgah_gate.py::drift_reason`). A refusal, last in `decision` after every other rule, and its only delivery: no host's PostToolUse side carries it, and opencode gets it from the core through `bin/tezgah-gate` on its write path. Once per turn: the `drift` mark is written *before* the refusal, so the identical call passes on the next attempt, and the count behind it is the turn's own — a bounded read (`hooks/tezgah_gate.py::DRIFT_TAIL`) falls back to the turn's own start (`hooks/tezgah_integrity.py::turn_rows`) when the turn outgrew the window. Governed by `reminder-off`, not `verify-off` (`hooks/tezgah_gate.py::drift_reason`).
|
|
266
284
|
|
|
285
|
+
### Ask — an ask-tool question with no recommended option
|
|
286
|
+
|
|
287
|
+
Not a rule of `decision`: the hosts ask for this warning after `decision` lets the call through, as they ask for the graph line.
|
|
288
|
+
Trigger: an omp `ask` call or a Claude AskUserQuestion call inside a root (`hooks/tezgah_gate.py::ask_advice`).
|
|
289
|
+
The gate counts the questions and the ones that carry a recommended option (`hooks/tezgah_integrity.py::undefaulted`).
|
|
290
|
+
omp marks one with a question's `recommended` index. Claude marks one with an option label such as "(Recommended)" or "(önerilen)".
|
|
291
|
+
Told: how many questions carry no default, and that each question names one, unless the step waits on the user by nature (`hooks/tezgah_gate.py::ASK_NO_DEFAULT`).
|
|
292
|
+
A question about a step the user granted this session gets a second line: take the step, do not ask again (`hooks/tezgah_gate.py::ASK_GRANTED`).
|
|
293
|
+
A warning, never a deny: the line rides back with the call's result, as the graph line does. Every such call leaves an `ask` row with `questions` and `defaults`.
|
|
294
|
+
Claude routes the tool through its PreToolUse matcher (`hooks/hooks.json`), and omp through its `GATED` list (`hosts/omp/tezgah-hook.ts.in`).
|
|
295
|
+
Off under `verify-off` and `pretooluse-off`. The rest of the ask policy is the Stop rule's `needless ask` class ([evidence.md](evidence.md#the-stop-rule-end-to-end)).
|
|
296
|
+
|
|
267
297
|
## What the gate deliberately does not catch
|
|
268
298
|
|
|
269
299
|
Read this before filing a security-ish issue; each is a decision, not an oversight.
|
|
@@ -273,9 +303,11 @@ Read this before filing a security-ish issue; each is a decision, not an oversig
|
|
|
273
303
|
front of every server-side command cost a round-trip per effect, the user's chat approval could not lift it (only the deleted `bin/tezgah-consent` CLI
|
|
274
304
|
could), and the sessions it actually gated were the user's own deploys. The untrusted-read half went with it: a turn that read web content may now run an
|
|
275
305
|
outbound effect carrying only the taint notice, with no refusal in between. The notice itself stays (`hooks/tezgah_untrusted.py`).
|
|
306
|
+
The ask policy replaced it (plan 086, S3). It is a class table in the contract, the Stop rule's `needless ask` class and the ask-tool warning above.
|
|
307
|
+
A grant the user writes in chat ("bana sormadan pushla") lifts the ask for its class. Outward commands still run with no refusal.
|
|
276
308
|
- A write inside the root after a fetch: left to the taint notice that `hooks/tezgah_untrusted.py` rides the first effect with, because it is recoverable from
|
|
277
309
|
the snapshot.
|
|
278
|
-
- `grep -A 3 foo`: a flag with its own value shifts the token, so the search passes unnudged (`hooks/tezgah_gate.py:
|
|
310
|
+
- `grep -A 3 foo`: a flag with its own value shifts the token, so the search passes unnudged (`hooks/tezgah_gate.py:166-167`).
|
|
279
311
|
- A foreign `apply_patch` write: the PostToolUse writer records no path for that row (`hooks/tezgah_gate.py::write_paths`); and the guard skips a row an older writer left with no
|
|
280
312
|
`target`, because that row names no file (`hooks/tezgah_integrity.py::writers_elsewhere`).
|
|
281
313
|
- Which rule the user meant when a turn drifts: "a guess about intent wearing a check's clothes" (`hooks/tezgah_gate.py::drift_reason`).
|
|
@@ -320,17 +352,17 @@ widen a list a regex already covers.
|
|
|
320
352
|
## The mirror: the opencode plugin
|
|
321
353
|
|
|
322
354
|
opencode cannot run Python hooks, so `hosts/opencode/plugins/tezgah.js` is an independent JavaScript re-implementation of the same rules, dispatched from
|
|
323
|
-
`tool.execute.before` (hosts/opencode/plugins/tezgah.js:
|
|
355
|
+
`tool.execute.before` (hosts/opencode/plugins/tezgah.js:2429, exported `hosts/opencode/plugins/tezgah.js:2365`) in the gate's own order (`hosts/opencode/plugins/tezgah.js:2429-2591`); its shortcut constants restate the same regexes (`NEUTER` `hosts/opencode/plugins/tezgah.js:270`,
|
|
324
356
|
`SKIP_ENV` hosts/opencode/plugins/tezgah.js:271, `NO_VERIFY` hosts/opencode/plugins/tezgah.js:272, `GITISH` hosts/opencode/plugins/tezgah.js:273, `SKIP_TEST` hosts/opencode/plugins/tezgah.js:515). What keeps the two halves honest is a test, not a shared module:
|
|
325
357
|
`tests/test_opencode_plugin.py` drives the plugin's hooks through a node harness with a throwaway HOME, and imports the Python `tezgah_integrity.call_id` so a
|
|
326
358
|
separator or canonical-form drift in the action id fails there instead of silently in a session (`tests/test_opencode_plugin.py:1-7`, `tests/test_opencode_plugin.py:24-27`, `tests/test_opencode_plugin.py::OpenCodePlugin.test_the_id_is_the_hash_the_python_writer_computes`).
|
|
327
359
|
|
|
328
360
|
The shell rule kinds mirrored last are each pinned in both suites: the shell's write body (`SHELL_WRITE` `hosts/opencode/plugins/tezgah.js:979`,
|
|
329
361
|
`heredocBodies` `hosts/opencode/plugins/tezgah.js:985`, `shellWriteBody` `hosts/opencode/plugins/tezgah.js:1018`, the shortcut and attribution twins at
|
|
330
|
-
`hosts/opencode/plugins/tezgah.js:
|
|
331
|
-
ordering obligation (`COMMIT_CMD` `hosts/opencode/plugins/tezgah.js:
|
|
332
|
-
`hosts/opencode/plugins/tezgah.js:
|
|
333
|
-
`hosts/opencode/plugins/tezgah.js:
|
|
362
|
+
`hosts/opencode/plugins/tezgah.js:2481-2485` and `hosts/opencode/plugins/tezgah.js:1670`, the credential twin at `hosts/opencode/plugins/tezgah.js:1630-1634`) and the
|
|
363
|
+
ordering obligation (`COMMIT_CMD` `hosts/opencode/plugins/tezgah.js:1558`, `ORDER_DENY`
|
|
364
|
+
`hosts/opencode/plugins/tezgah.js:1560`, `lastVerify` `hosts/opencode/plugins/tezgah.js:1571`, `orderReason` `hosts/opencode/plugins/tezgah.js:1596`, dispatched at
|
|
365
|
+
`hosts/opencode/plugins/tezgah.js:2530-2533`). `lastVerify` folds the tail exactly as `_last_verify` does, minus the `passing_check` narrowing - this rule reads only
|
|
334
366
|
`fail`, and a row that narrowing would refuse reads `ok`/`ran` on both sides, so the outcome is identical.
|
|
335
367
|
|
|
336
368
|
The control rule has no JavaScript copy. The plugin's `CONTROL_CMD` pre-test picks the shell lines that could touch the control plane. The plugin puts each
|
|
@@ -357,16 +389,17 @@ rule's triggers are the classes `_stop_block` returns, the four `_shape_block` a
|
|
|
357
389
|
| `shortcut` | E7c: with the write tools refused, 3 of 25 armed runs wrote a test skip through a heredoc | an internal benchmark (block E7, not published) | `tests/test_gate.py::test_a_shell_write_that_disables_a_test_is_denied` |
|
|
358
390
|
| `piped` | a real omp session: 13 checks ran piped and 3 of 6 completion claims were blocked | the Piped section above | `tests/test_gate.py::test_a_piped_check_is_refused_and_pipefail_passes` |
|
|
359
391
|
| `attribution` | E7c: the same 3 of 25 runs, a credit written through a heredoc | an internal benchmark (block E7, not published) | `tests/test_gate.py::test_attribution_denies_a_credit_written_by_the_shell` |
|
|
360
|
-
| `lang` | a plan branch was created as `plan/004-admin-durum-onarimi`, and its slug is in the public history for good | `hooks/tezgah_gate.py:
|
|
361
|
-
| `race` | two sessions on one file - a parent and its subagent, or two worktrees - landing on a stale read | `hooks/tezgah_gate.py:
|
|
392
|
+
| `lang` | a plan branch was created as `plan/004-admin-durum-onarimi`, and its slug is in the public history for good | `hooks/tezgah_gate.py:355-364` | `tests/test_gate.py::test_a_turkish_branch_name_is_refused` |
|
|
393
|
+
| `race` | two sessions on one file - a parent and its subagent, or two worktrees - landing on a stale read | `hooks/tezgah_gate.py:752-759` | `tests/test_gate.py::test_a_write_to_a_file_another_session_wrote_is_refused` |
|
|
394
|
+
| `lease` | plan 086 wave 1: two slices wrote into the main checkout through relative paths; 24 sibling race refusals in real transcripts, all in a shared worktree, tree or record | the Lease section above | `tests/test_gate.py::test_a_sibling_slices_lease_refuses_its_file_and_frees_the_owner` |
|
|
362
395
|
| `task` | E7: with the unlocking command printed, an armed arm removed or disabled the gate in 25 of 25 runs; E7b: the heredoc redirect in 3 of 25 | the Task section above (the checkpoint refusal) | `tests/test_gate.py::test_a_reading_phase_refuses_a_write_inside_the_allowlist` |
|
|
363
396
|
| `secret` | E7c: the same 3 of 25 runs, a key written through a heredoc | an internal benchmark (block E7, not published) | `tests/test_gate.py::test_a_credential_written_by_a_heredoc_is_denied` |
|
|
364
|
-
| `workspace` | sessions wrote root `plans/`, `research/` and `analysis/` (observed across several unrelated projects on one machine) | `hooks/tezgah_gate.py:
|
|
365
|
-
| `plan` | a turn wrote four product files - a hook, its tests, its docs and the changelog - straight onto `main` | `hooks/tezgah_gate.py:
|
|
397
|
+
| `workspace` | sessions wrote root `plans/`, `research/` and `analysis/` (observed across several unrelated projects on one machine) | `hooks/tezgah_gate.py:1344-1348` | `tests/test_gate.py::test_untracked_root_kinds_are_refused_for_tools_and_redirects` |
|
|
398
|
+
| `plan` | a turn wrote four product files - a hook, its tests, its docs and the changelog - straight onto `main` | `hooks/tezgah_gate.py:1624-1632` | `tests/test_gate.py::test_the_third_product_write_on_main_is_refused` |
|
|
366
399
|
| `order` | FAVA: the shape found in 90% of real agent-instruction projects ("do not commit before running the tests") | the Ordering section above | `tests/test_gate.py::test_a_commit_is_refused_while_the_newest_check_failed` |
|
|
367
400
|
| `loop` | designed | the turn's own repeated failure; no incident recorded | `tests/test_gate.py::test_an_identical_failed_call_is_denied_after_the_ceiling` |
|
|
368
401
|
| `retry` | designed | the session ceiling over the same guard; no incident recorded | `tests/test_gate.py::test_a_fourth_identical_call_is_refused_whatever_the_outcome` |
|
|
369
|
-
| `drift` | an internal plan's flip rule on the 2026-09-20 move to a result-channel notice: at 27 sessions the blocked-claim rate in turns under 25 work rows read 112/140 = 0.8000, against 59/142 = 0.4155 before | `hooks/tezgah_gate.py:
|
|
402
|
+
| `drift` | an internal plan's flip rule on the 2026-09-20 move to a result-channel notice: at 27 sessions the blocked-claim rate in turns under 25 work rows read 112/140 = 0.8000, against 59/142 = 0.4155 before | `hooks/tezgah_gate.py:1517-1529` | `tests/test_gate.py::test_a_long_turn_restates_the_constraints_before_a_write` |
|
|
370
403
|
| `evidence tampered` | a terminated garbage line made `_parse` raise, and the guard then failed the Stop rule open for the turn; corrupting a `verify_fail` row was an allow route. A `verify_ok` appended with `python3 -c` licensed a claim (security-03), so a pass with no `began` row is an orphan (`hooks/tezgah_integrity.py::_pair`) | `hooks/tezgah_integrity.py::_parse` | `tests/test_integrity.py::test_a_damaged_ledger_blocks_a_done_claim_as_evidence_tampered` |
|
|
371
404
|
| `check failed` | designed | the newest check failing is its own class; no incident recorded | `tests/test_integrity.py::test_failed_check_blocks` |
|
|
372
405
|
| `partial failure` | designed | a failure the turn never resolved; no incident recorded | `tests/test_integrity.py::test_an_unresolved_failure_blocks_even_after_an_earlier_pass` |
|
|
@@ -374,6 +407,10 @@ rule's triggers are the classes `_stop_block` returns, the four `_shape_block` a
|
|
|
374
407
|
| `stale evidence` | measured 2026-09-19: reading a rewrite as a change refused honest turns | `hooks/tezgah_integrity.py::_post_write` | `tests/test_integrity.py::test_a_write_after_the_check_makes_the_check_stale` |
|
|
375
408
|
| `no verify_ok` | E2: 0 of 10 description-shaped claims refused before the `worked` trigger | `hooks/tezgah_integrity.py::_stop_block` | `tests/test_integrity.py::test_work_with_no_passing_check_is_refused_without_a_claim_word` |
|
|
376
409
|
| `no external read` | two turns: "npm 0.22.0 is missing" off a stale client, and "make NPM_TOKEN an automation token" when it already was | `hooks/tezgah_integrity.py::_external_read_row` | `tests/test_integrity.py::test_an_external_claim_with_no_read_is_refused` |
|
|
410
|
+
| `residual work` | the owner's report (2026-10-11): turns end on "Açık kalanlar / Doğrulanmadı / Sıradaki adım" instead of the work; measured 503 of 1876 final replies with such a label at a line start, 467 in turns that called a non-read tool | `hooks/tezgah_integrity.py::_residual_block` | `tests/test_integrity.py::test_a_residual_list_is_refused_once_and_the_reply_after_it_recorded` |
|
|
411
|
+
| `ready steps` | measured 2026-10-11 (research line `orchestrator-flow-audit`): 13.1% of 451 omp user turns were a bare "devam", and 192 of 281 spawns were followed by `wait` with no other call between, at 82 of them with four or more todo items open | `hooks/tezgah_integrity.py::ready_left` | `tests/test_integrity.py::test_a_ready_todo_step_left_is_refused_once` |
|
|
412
|
+
| `needless ask` | research line `orchestrator-flow-audit` S3: 33 of 48 final replies that ended on a question in the owner's omp transcripts were process questions ("Başlayayım mı?"), and 9 of 47 next prompts were a bare "devam" or "evet" | `hooks/tezgah_integrity.py::needless_ask` | `tests/test_integrity.py::test_a_process_question_at_the_end_is_refused_once` |
|
|
413
|
+
| `stopped slice` | measured 2026-10-11 (research line `subagent-resume-orchestration`): 118 of 1413 omp subagent runs ended stopped, and in the newest 20 the parent absorbed the work inline 16 times instead of resuming it | `hooks/tezgah_integrity.py::_stopped_slice` | `tests/test_integrity.py::test_a_stopped_slice_of_this_session_refuses_the_turn_once` |
|
|
377
414
|
| `placating opener` | designed | output rule 10 read at the Stop event; no incident recorded | `tests/test_integrity.py::test_sycophantic_opener_blocks` |
|
|
378
415
|
| `forbidden closer` | designed | output rule 10 read at the Stop event; no incident recorded | `tests/test_integrity.py::test_a_closer_from_the_skills_own_list_blocks` |
|
|
379
416
|
| `list cap` | designed | output rule 8 read at the Stop event; no incident recorded | `tests/test_integrity.py::test_a_list_over_the_cap_blocks_with_its_size` |
|
|
@@ -389,7 +426,7 @@ rule's triggers are the classes `_stop_block` returns, the four `_shape_block` a
|
|
|
389
426
|
to (`off(...)`), the way the shortcut and repeat rules use `verify-off` (`hooks/tezgah_gate.py::decision`). The rule name is now a record too: add its row to
|
|
390
427
|
[Rule provenance](#rule-provenance), or `bin/tezgah-docs --citations` refuses the rule for having none.
|
|
391
428
|
3. Extend `tests/test_gate.py`. It drives the real function through `tests/_probe_gate.py` (`tests/support.py::PROBE_GATE`), which also carries the `capture_log` stub
|
|
392
|
-
standing in for `tezgah_gate.capture` on the allow path (`tests/_probe_gate.py:
|
|
429
|
+
standing in for `tezgah_gate.capture` on the allow path (`tests/_probe_gate.py:21-31`). Only if the refusal envelope changes, touch
|
|
393
430
|
`tests/test_claude_adapters.py::PreToolUse` and the codex/cursor/omp hook tests.
|
|
394
431
|
4. Mirror the rule in `hosts/opencode/plugins/tezgah.js` and extend `tests/test_opencode_plugin.py` with its cases; that suite is where the two halves are
|
|
395
432
|
compared and where a shared-vocabulary drift fails.
|
|
@@ -400,10 +437,10 @@ rule's triggers are the classes `_stop_block` returns, the four `_shape_block` a
|
|
|
400
437
|
|
|
401
438
|
## Source of truth
|
|
402
439
|
|
|
403
|
-
- `hooks/tezgah_gate.py` — `hooks/tezgah_gate.py::decision`; every rule constant and refusal text `hooks/tezgah_gate.py:
|
|
440
|
+
- `hooks/tezgah_gate.py` — `hooks/tezgah_gate.py::decision`; every rule constant and refusal text `hooks/tezgah_gate.py:165-2434`; `attribution`/`attribution_edit` `hooks/tezgah_gate.py::attribution`, `hooks/tezgah_gate.py::attribution_edit`; `explored`
|
|
404
441
|
`hooks/tezgah_gate.py::explored`; `hooks/tezgah_gate.py::searched_symbol`, `hooks/tezgah_gate.py::index_slug`, `hooks/tezgah_gate.py::first_nudge`, `hooks/tezgah_gate.py::nudge_reason`, `hooks/tezgah_gate.py::graph_advice`; `hooks/tezgah_gate.py::LOOP_ATTEMPTS`, `hooks/tezgah_gate.py::loop_reason`,
|
|
405
442
|
`hooks/tezgah_gate.py::RETRY_CEILING`, `hooks/tezgah_gate.py::retry_reason`; `hooks/tezgah_gate.py::secret_command`, `hooks/tezgah_gate.py::race_reason`,
|
|
406
|
-
`hooks/tezgah_gate.py::drift_reason`, `hooks/tezgah_gate.py::effectful`, `hooks/tezgah_gate.py::_deny`; the plan rule's own section `hooks/tezgah_gate.py:
|
|
443
|
+
`hooks/tezgah_gate.py::drift_reason`, `hooks/tezgah_gate.py::effectful`, `hooks/tezgah_gate.py::_deny`; the plan rule's own section `hooks/tezgah_gate.py:1624-1821` — `hooks/tezgah_gate.py::plan_reason`, `hooks/tezgah_gate.py::_branch`, `hooks/tezgah_gate.py::_checkout`, `hooks/tezgah_gate.py::_product`, `hooks/tezgah_gate.py::PLAN_DENY`; `hooks/tezgah_gate.py::write_paths`, `hooks/tezgah_gate.py::shell_target`, `hooks/tezgah_gate.py::SHELL_AS_WRITE`; the `capture` call `hooks/tezgah_gate.py::decision` (a write tool's target, and a shell write's)
|
|
407
444
|
- `hooks/projects-pretooluse.py` — the Claude and dsh envelope; `hooks/tezgah_paths.py` — `off`, `root_for`, `cache_dir`
|
|
408
445
|
- `hooks/tezgah_integrity.py` — `hooks/tezgah_integrity.py::WRITE_TOOLS`, `hooks/tezgah_integrity.py::BASH_TOOLS`; `NEUTER`/`SKIP_ENV`/`NO_VERIFY`/`GITISH` `hooks/tezgah_integrity.py::NEUTER`, `hooks/tezgah_integrity.py::SKIP_ENV`, `hooks/tezgah_integrity.py::NO_VERIFY`, `hooks/tezgah_integrity.py::GITISH`; `HOOKS_KEY`/`GIT_VALUE_OPTS`/`CONFIG_READS`/`ROUGH_WORDS` `hooks/tezgah_integrity.py::HOOKS_KEY`, `hooks/tezgah_integrity.py::GIT_VALUE_OPTS`, `hooks/tezgah_integrity.py::CONFIG_READS`, `hooks/tezgah_integrity.py::ROUGH_WORDS`; `hooks/tezgah_integrity.py::SKIP_TEST`; `TEST_PATH`
|
|
409
446
|
`hooks/tezgah_integrity.py::TEST_PATH`; `hooks/tezgah_integrity.py::call_id`; `hooks/tezgah_integrity.py::_path`, `hooks/tezgah_integrity.py::note`, `hooks/tezgah_integrity.py::events`, `hooks/tezgah_integrity.py::writers_elsewhere`, `hooks/tezgah_integrity.py::mask`, `hooks/tezgah_integrity.py::shortcut_command`,
|
package/docs/glossary.md
CHANGED
|
@@ -10,7 +10,7 @@ are definitions, not rules: the rule text belongs to [contract](contract.md),
|
|
|
10
10
|
[gate](gate.md) and [evidence](evidence.md).
|
|
11
11
|
|
|
12
12
|
### after-block row
|
|
13
|
-
A ledger [row](#row) of kind `after_block`: the [Stop rule](#stop-rule)'s record-only verdict on
|
|
13
|
+
A ledger [row](#row) of kind `after_block`: the [Stop rule](#stop-rule)'s record-only verdict on a reply that answers its own block and is not refused again. The turn already holds a `blocked:` [claim](#claim) or refusal row. `_record_only()` (`hooks/tezgah_integrity.py::_record_only`) allows at most two refusals per [turn](#turn) and never two for one reason class. So the rule refuses the reply after a block once more only for a new class. Otherwise `stop_reason()` (`hooks/tezgah_integrity.py::stop_reason`) writes this row on Claude, Codex, Cursor and omp, with `detail` `would block: <reason class>`, `ok` or `no claim`. Not a [claim](#claim): `counters()` counts it neither as a claim nor as a reply.
|
|
14
14
|
|
|
15
15
|
### always-on
|
|
16
16
|
The part of the contract every session pays for - the invariants plus the one-line [pointer](#pointer) - built by `always_on_core()` (`hooks/tezgah_context.py::always_on_core`) out of [CORE](#core) with the [conditional rules](#conditional-rule) removed; it is not the whole contract, whose long tail is the `tezgah-contract` [skill](#skill) ([contract](contract.md)).
|
|
@@ -50,7 +50,7 @@ What one host can do with one rule surface: the gate, the Stop rule or a subagen
|
|
|
50
50
|
One session's record of what ran: the `evidence` rows of `<cache>/tezgah.db` for that session, keyed by the path its JSONL file had, `<cache>/evidence/<slug>.jsonl` (`hooks/tezgah_integrity.py::_path`, `hooks/tezgah_store.py::EVIDENCE_SCHEMA`), appended by `note()` (`hooks/tezgah_integrity.py::note`) and read by `events()` (`hooks/tezgah_integrity.py::events`), with its contract in the module docstring (`hooks/tezgah_integrity.py:2-24`) and every reader failing open. A read or write of the session first imports a JSONL file still at that path. Not the [session store](#session-store), which holds only used-tool marks.
|
|
51
51
|
|
|
52
52
|
### false completion
|
|
53
|
-
The count of [claim](#claim) rows a stop refused, `false_completion` in `counters()`, read against `claims` so the rate is meaningful (`hooks/tezgah_integrity.py::_counts`, `hooks/tezgah_integrity.py::drift_series`). Not the count of [denies](#deny): a deny is the gate refusing a call, a false completion is the Stop rule refusing the end of a turn.
|
|
53
|
+
The count of [claim](#claim) rows a stop refused for their evidence, `false_completion` in `counters()`, read against `claims` so the rate is meaningful (`hooks/tezgah_integrity.py::_counts`, `hooks/tezgah_integrity.py::drift_series`). A refusal for the reply's shape or for the work it leaves (`residual work`) made no false claim, so it does not count. Not the count of [denies](#deny): a deny is the gate refusing a call, a false completion is the Stop rule refusing the end of a turn.
|
|
54
54
|
|
|
55
55
|
### gate
|
|
56
56
|
The pre-tool refusal layer, `decision()` (`hooks/tezgah_gate.py::decision`), which runs only inside a [root](#root) (`hooks/tezgah_gate.py::decision`) and returns a reason or nothing ([gate](gate.md)). Not the [Stop rule](#stop-rule), which runs after the reply.
|
|
@@ -158,10 +158,10 @@ The ledger field naming the untrusted channel a result came through - `web`, `mc
|
|
|
158
158
|
The value of one status [mark](#mark): `on`, `ready`, `off` or `info` (`hooks/tezgah_context.py::health_segments`), drawn as `✓ ○ ✗` or nothing by `GLYPHS` (`hooks/tezgah_context.py::GLYPHS`) and mapped to a colour by `COLORS` (`hooks/tezgah_context.py::COLORS`), with `off` winning over the observable test because a kill switch is visible everywhere ([status-line](status-line.md)). Not a glyph, which is only how the state is drawn.
|
|
159
159
|
|
|
160
160
|
### step kind
|
|
161
|
-
One of the row kinds that count as a step of work - `run`, `edit`, `verify`, `verify_ok`, `verify_fail`, `interrupted` (`STEP_KINDS`, `hooks/tezgah_integrity.py::STEP_KINDS`), summed into `steps` and into the drift threshold, while `deny`, `nudge`, `claim` and `turn` are the machinery around the work (`hooks/tezgah_integrity.py:
|
|
161
|
+
One of the row kinds that count as a step of work - `run`, `edit`, `verify`, `verify_ok`, `verify_fail`, `interrupted` (`STEP_KINDS`, `hooks/tezgah_integrity.py::STEP_KINDS`), summed into `steps` and into the drift threshold, while `deny`, `nudge`, `claim` and `turn` are the machinery around the work (`hooks/tezgah_integrity.py:1420-1422`). `interrupted` is the one that is not a failure: the host said the call was stopped, so the row carries no `exit` and no `fail_class`, no reader folds it as a failed check and no attempt is spent on it (`hooks/tezgah_integrity.py:1426-1429`). Not any row.
|
|
162
162
|
|
|
163
163
|
### Stop rule
|
|
164
|
-
The end-of-turn refusal, `stop_reason()` (`hooks/tezgah_integrity.py::stop_reason`), run by the Stop hooks of Claude, Codex, Cursor and omp (`hooks/projects-stop.py::main`, `hosts/codex/hook.py::main`, `hosts/cursor/hook.py::dispatch`, `hosts/omp/hook.py::handle`): it blocks on a placating opener or a banned sign-off on the last prose line, on a completion claim the newest check does not support, on a check that passed before the newest write the gate saw change the tree,
|
|
164
|
+
The end-of-turn refusal, `stop_reason()` (`hooks/tezgah_integrity.py::stop_reason`), run by the Stop hooks of Claude, Codex, Cursor and omp (`hooks/projects-stop.py::main`, `hosts/codex/hook.py::main`, `hosts/cursor/hook.py::dispatch`, `hosts/omp/hook.py::handle`): it blocks on a placating opener or a banned sign-off on the last prose line, on a completion claim the newest check does not support, on a check that passed before the newest write the gate saw change the tree, on a turn that recorded work with no passing check, or on a working turn that ends on a list of what is left with an item no blocker accounts for (`_stop_block()`, `hooks/tezgah_integrity.py::_stop_block`). An explicit "doğrulanmadı" clears a check-repair refusal only after a check that ran and failed since the newest change (`hooks/tezgah_integrity.py::_admitted`). A claim in a turn with no work is judged against the whole session, and a read-only or VCS-bookkeeping turn after a pass on an unchanged tree is exempt ([evidence](evidence.md#the-stop-rule-end-to-end)). Not the [gate](#gate), which runs before a call ([evidence](evidence.md)).
|
|
165
165
|
|
|
166
166
|
### surface
|
|
167
167
|
A place a host can show tezgah's output - a status line, a TUI widget, a `systemMessage` - and therefore the reason the same [marks](#mark) render differently per host (`hooks/tezgah_context.py::health_segments`, `hooks/tezgah_context.py::render_line`); each host's surfaces are [hosts](hosts.md)'s. Not the host itself.
|
package/docs/hosts.md
CHANGED
|
@@ -20,9 +20,9 @@ surface can honestly report this session; the two skill-read marks (`pony`,
|
|
|
20
20
|
| claude | SessionStart, SubagentStart, PostCompact, UserPromptSubmit, PreToolUse, PostToolUse, PostToolUseFailure, Stop, SubagentStop (record-only, `subagent_end`) - `hooks/hooks.json:2-32` | the managed block in `~/.claude/CLAUDE.md` (`CLAUDE_RULES`), Claude's global memory file; the SessionStart/PostCompact hook rows declare `TEZGAH_CORE_IN_FILE` (`hooks/hooks.json:4,10`) and the hook drops the core when the block is there, so the contract is paid for once (`hooks/projects-auto-init.py::core_is_in_a_file`; the static `output-styles/tezgah.md` mirror (`output-styles/tezgah.md:1`) is the hookless build's copy) | UserPromptSubmit -> `additionalContext` (`hooks/hooks.json:12-14`, `hooks/projects-auto-init.py::main`) | `statusLine` command in `~/.claude/settings.json` (`bin/tezgah-setup::wire_claude_statusline`), ANSI, forwards Orca first (`statusline.py:47-55`) | all seven: the transcript parse covers the two skill reads, `graph`, `orch` and the three shell kinds - `consult`, `research` and the judgement callers (`statusline.py::claude_used`, `observable=None` at `statusline.py::observable`) |
|
|
21
21
|
| codex | SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, SubagentStart, PostCompact, Stop (`bin/tezgah-setup::CODEX_HOOK_EVENTS`; named in `hosts/codex/hook.py:6-7`) | hook: the normalized events inject `context_for` (`hosts/codex/hook.py::main`) | UserPromptSubmit -> `additionalContext` (`hosts/codex/hook.py::main`) | no custom footer item (`tui.status_line` is a closed enum) - the line rides `systemMessage`, plain, at SessionStart and Stop (`hosts/codex/hook.py:10-12`, `hosts/codex/hook.py::main`) | five tool-use (`TOOL_USE_MEASURES` at `hosts/codex/hook.py::main`) |
|
|
22
22
|
| cursor | 13 events: sessionStart, preToolUse, postToolUse, postToolUseFailure, subagentStart, subagentStop, beforeSubmitPrompt, stop, beforeMCPExecution, afterShellExecution, afterMCPExecution, afterFileEdit, afterAgentResponse (`bin/tezgah-setup::CURSOR_HOOK_EVENTS`) | hook: sessionStart -> `additional_context` (`hosts/cursor/hook.py::dispatch`) | beforeSubmitPrompt -> `additional_context` plus `{"continue": true}` (`hosts/cursor/hook.py::dispatch`) | `statusLine` in `~/.cursor/cli-config.json` -> `tezgah-statusline --cursor` (`bin/tezgah-setup::install_cursor`), ANSI (`statusline.py:122-124`) | five tool-use (`statusline.py::observable`) |
|
|
23
|
-
| opencode | no lifecycle events; plugin hooks: config, permission.ask, tool.execute.before, shell.env, experimental.session.compacting, chat.message, tool.execute.after (`hosts/opencode/plugins/tezgah.js:
|
|
23
|
+
| opencode | no lifecycle events; plugin hooks: config, permission.ask, tool.execute.before, shell.env, experimental.session.compacting, chat.message, tool.execute.after (`hosts/opencode/plugins/tezgah.js:2372-2737`) - `tool.execute.before` carries the whole gate, `tool.execute.after` the channel on the row, the provenance label on the result and the taint notice on the next effect (`untrustedSource` `hosts/opencode/plugins/tezgah.js:1942`, `labelResult` `:1993`) | static only: generated `instructions` files, because there is no session-start injection point (`hosts/opencode/plugins/tezgah.js:3-6`, `bin/tezgah-setup::opencode_contract_text`) | chat.message pushes a synthetic part (`hosts/opencode/plugins/tezgah.js:2652-2703`) | TUI plugin `tezgah-tui.tsx` declared in `tui.json`, runs `tezgah-status` on the event bus (`hosts/opencode/tui/tezgah-tui.tsx:1-11`; wiring `bin/tezgah-setup::install_opencode`) | all seven: skill reads and kinds are classified in process (`hosts/opencode/plugins/tezgah.js:2227-2263`) |
|
|
24
24
|
| dsh | SessionStart, SubagentStart, UserPromptSubmit, PreToolUse, PostToolUse (`hosts/dsh/hooks.json:2-19`); no Stop rule, because the bridge drops every call's outcome and no check there can show a pass | hook: the same Claude-family scripts, named by `configPath` (`bin/tezgah-setup::dsh_patch_block`) | UserPromptSubmit, through the bridge (`hosts/dsh/hooks.json:9-11`) | web-profile plugin: authenticated `GET /api/tezgah.status` -> `tezgah-status` (`hosts/dsh/statusline/lib/index.js:15,66-104`) | five tool-use: the route passes `--observable=consult,research,graph,orch,judge` (`hosts/dsh/statusline/lib/index.js:16-19,84,96`) |
|
|
25
|
-
| omp | session_start, session_compact, session_switch, turn_end, before_agent_start, tool_call, tool_result, session_stop (`hosts/omp/tezgah-hook.ts.in:
|
|
25
|
+
| omp | session_start, session_compact, session_switch, turn_end, before_agent_start, tool_call, tool_result, session_stop (`hosts/omp/tezgah-hook.ts.in:596,655,674,679,681,701,720,755`) | both: static `RULES.md` (`bin/tezgah-setup::STATIC_RULES`, `bin/tezgah-setup::rules_block`) and a session payload carrying only what a static file cannot know (`hosts/omp/hook.py::handle`) | before_agent_start returns a hidden message (`hosts/omp/tezgah-hook.ts.in:681-699`) | extension draws `ctx.ui.setWidget` below the editor, `setStatus` as fallback (`hosts/omp/tezgah-hook.ts.in:170-203`); ANSI line from `hosts/omp/hook.py::status_line` | all seven: the embedded runner filters skill reads before asking python (`hosts/omp/tezgah-hook.ts.in:257-264`) and no `observable` is passed (`hosts/omp/hook.py::status_line`) |
|
|
26
26
|
|
|
27
27
|
The middle five marks (`consult`, `research`, `graph`, `orch`, `judge`) are tool-use marks:
|
|
28
28
|
any adapter that sees its host's tool calls can light them by calling
|
|
@@ -225,9 +225,9 @@ and say which document it came from.
|
|
|
225
225
|
|
|
226
226
|
A skill read is observable only where the host gives a channel that does not cost
|
|
227
227
|
a process per read: Claude parses its transcript (`statusline.py::claude_used`),
|
|
228
|
-
opencode classifies in process (`hosts/opencode/plugins/tezgah.js:
|
|
228
|
+
opencode classifies in process (`hosts/opencode/plugins/tezgah.js:2227-2263`),
|
|
229
229
|
omp filters in its embedded runner before it asks python
|
|
230
|
-
(`hosts/omp/tezgah-hook.ts.in:
|
|
230
|
+
(`hosts/omp/tezgah-hook.ts.in:257-264`). On codex, cursor and dsh a read is not
|
|
231
231
|
observable at that price, so their surfaces pass the five tool-use measures as
|
|
232
232
|
`observable` and the two skill marks render dim (`info`, no glyph) instead of
|
|
233
233
|
claiming the skill was never opened (`hooks/tezgah_context.py::TOOL_USE_MEASURES`, and the
|
|
@@ -314,7 +314,7 @@ In order, each step verified by the one below it:
|
|
|
314
314
|
`hosts/omp/hook.py` for `{"event": "post_compact"}`; the block is sent with
|
|
315
315
|
`pi.sendMessage(..., {deliverAs: "nextTurn"})`, and the summary rides as
|
|
316
316
|
`compact_summary` so `remember_compaction` writes the compaction row
|
|
317
|
-
(`hosts/omp/tezgah-hook.ts.in:
|
|
317
|
+
(`hosts/omp/tezgah-hook.ts.in:647-664`, `hosts/omp/hook.py::handle`).
|
|
318
318
|
Subagent sessions are skipped, because the post-compact block starts the
|
|
319
319
|
indexer.
|
|
320
320
|
- **cursor**: not re-injected - a limitation. Cursor's only compaction hook,
|
|
@@ -348,7 +348,7 @@ In order, each step verified by the one below it:
|
|
|
348
348
|
`hosts/dsh/hooks.json` until the bridge carries outcomes.
|
|
349
349
|
- **opencode carries the untrusted-content control in the plugin**, not by
|
|
350
350
|
importing `hooks/tezgah_untrusted.py`, which a plugin cannot do in process: the
|
|
351
|
-
channel is decided in JS (`untrustedSource` `hosts/opencode/plugins/tezgah.js:
|
|
351
|
+
channel is decided in JS (`untrustedSource` `hosts/opencode/plugins/tezgah.js:1942`,
|
|
352
352
|
the tier's argv reader at `:1902-1911`), the label and the taint notice ride the
|
|
353
353
|
result the hook is handed (`labelResult` `:1993`). A shared corpus in
|
|
354
354
|
`tests/test_opencode_plugin.py::OpenCodePlugin.test_the_classifier_agrees_with_the_python_half_on_a_shared_corpus` drives both halves over the same calls and
|
|
@@ -391,7 +391,7 @@ In order, each step verified by the one below it:
|
|
|
391
391
|
- **opencode has no session-start hook**, so its always-on file must carry the
|
|
392
392
|
pointer every hook host appends, and the per-session index, agent and contract
|
|
393
393
|
refreshes ride the first `chat.message` instead (`bin/tezgah-setup::refresh_contract`,
|
|
394
|
-
`hosts/opencode/plugins/tezgah.js:
|
|
394
|
+
`hosts/opencode/plugins/tezgah.js:2686-2701`). It denies the native `skill`
|
|
395
395
|
tool - the generated router replaces the injected skill list
|
|
396
396
|
(`bin/tezgah-setup::install_opencode`) - and `permission.ask` may never be emitted by a
|
|
397
397
|
given build, which is why `tool.execute.before` stays the enforcing half
|
|
@@ -411,7 +411,7 @@ In order, each step verified by the one below it:
|
|
|
411
411
|
`hosts/cursor/hooks.json:8` - neither named them until 2026-09-19, so a
|
|
412
412
|
PowerShell call on those two reached no hook at all while the shared rules
|
|
413
413
|
already knew the spelling), and omp's `GATED` list
|
|
414
|
-
(`hosts/omp/tezgah-hook.ts.in:
|
|
414
|
+
(`hosts/omp/tezgah-hook.ts.in:270-273`). A host this list does not cover is a
|
|
415
415
|
host whose shell the gate cannot see.
|
|
416
416
|
- **An MCP effect reaches the gate on every host that can route it**. Claude
|
|
417
417
|
and dsh run it through a PreToolUse group of its own in `hooks/hooks.json` and
|
package/docs/index.json
CHANGED
|
@@ -267,7 +267,9 @@
|
|
|
267
267
|
"what do init, claim, migrate and source write",
|
|
268
268
|
"how does a session learn that a research line is broken",
|
|
269
269
|
"what lights the research mark and how is the layer switched off",
|
|
270
|
-
"why may a new research line not be opened while another is unfinished"
|
|
270
|
+
"why may a new research line not be opened while another is unfinished",
|
|
271
|
+
"what does tezgah-research next print for a product flow, and when is a decision due for re-measure",
|
|
272
|
+
"how does an exploratory line run a campaign of new ideas (arena, stage funnel, tournament, board, dedup, yield)"
|
|
271
273
|
],
|
|
272
274
|
"answers_tr": [
|
|
273
275
|
"bir araştırma hattı nerede yaşar ve ne tutar",
|
|
@@ -275,11 +277,14 @@
|
|
|
275
277
|
"init, claim, migrate ve source ne yazar",
|
|
276
278
|
"bir oturum bir araştırma hattının bozuk olduğunu nasıl öğrenir",
|
|
277
279
|
"araştırma işaretini ne yakar ve katman nasıl kapatılır",
|
|
278
|
-
"başka biri bitmemişken neden yeni bir araştırma hattı açılamaz"
|
|
280
|
+
"başka biri bitmemişken neden yeni bir araştırma hattı açılamaz",
|
|
281
|
+
"tezgah-research next bir ürün akışı için ne yazdırır ve bir karar ne zaman yeniden ölçülmeli",
|
|
282
|
+
"keşif (exploratory) hattı yeni fikir kampanyasını nasıl yürütür (arena, aşama hunisi, turnuva, board, dedup, yield)"
|
|
279
283
|
],
|
|
280
284
|
"sources": [
|
|
281
285
|
"hooks/tezgah_research.py",
|
|
282
286
|
"bin/tezgah-research",
|
|
287
|
+
"hooks/tezgah_product.py",
|
|
283
288
|
"hooks/tezgah_policy.py",
|
|
284
289
|
"hooks/tezgah_context.py",
|
|
285
290
|
"skills/research/SKILL.md",
|
|
@@ -294,12 +299,18 @@
|
|
|
294
299
|
"answers": [
|
|
295
300
|
"what is a skill here and how does it reach a host",
|
|
296
301
|
"which skills ship and what does each do",
|
|
297
|
-
"how do I add a skill without breaking the router or the install report"
|
|
302
|
+
"how do I add a skill without breaking the router or the install report",
|
|
303
|
+
"which skill designs a metric dashboard and where do its data-view rules live",
|
|
304
|
+
"does the local skill search reach design-library's nested entries",
|
|
305
|
+
"which skill carries a new metric dashboard or feature from research to re-measure"
|
|
298
306
|
],
|
|
299
307
|
"answers_tr": [
|
|
300
308
|
"burada beceri nedir ve bir hosta nasıl ulaşır",
|
|
301
309
|
"hangi beceriler gelir ve her biri ne yapar",
|
|
302
|
-
"yönlendiriciyi veya kurulum raporunu bozmadan nasıl beceri eklerim"
|
|
310
|
+
"yönlendiriciyi veya kurulum raporunu bozmadan nasıl beceri eklerim",
|
|
311
|
+
"metrik dashboard tasarımını hangi beceri yapar ve veri görünümü kuralları nerede",
|
|
312
|
+
"yerel beceri araması design-library'nin iç girdilerine ulaşır mı",
|
|
313
|
+
"yeni bir metrik dashboard'u veya özelliği araştırmadan yeniden ölçüme hangi beceri taşır"
|
|
303
314
|
],
|
|
304
315
|
"sources": [
|
|
305
316
|
"bin/tezgah-setup",
|
|
@@ -309,7 +320,10 @@
|
|
|
309
320
|
"commands/",
|
|
310
321
|
"NOTICE",
|
|
311
322
|
"tests/test_setup.py",
|
|
312
|
-
"tests/test_skills.py"
|
|
323
|
+
"tests/test_skills.py",
|
|
324
|
+
"hooks/tezgah_skill_pick.py",
|
|
325
|
+
"skills/design-contract/data-views.md",
|
|
326
|
+
"skills/product-flow/SKILL.md"
|
|
313
327
|
]
|
|
314
328
|
},
|
|
315
329
|
{
|
|
@@ -320,12 +334,14 @@
|
|
|
320
334
|
"answers": [
|
|
321
335
|
"what does a feature-level audit cover that a screen review misses",
|
|
322
336
|
"what are the five matrices and the capability-change proposal",
|
|
323
|
-
"how is the feature-audit skill reached and what evidence backs it"
|
|
337
|
+
"how is the feature-audit skill reached and what evidence backs it",
|
|
338
|
+
"how do I specify a feature before it is built and audit it against that spec"
|
|
324
339
|
],
|
|
325
340
|
"answers_tr": [
|
|
326
341
|
"özellik düzeyinde bir denetim, ekran incelemesinin kaçırdığı neyi kapsar",
|
|
327
342
|
"beş matris ve yetenek değişikliği önerisi nedir",
|
|
328
|
-
"özellik denetimi becerisine nasıl ulaşılır ve onu hangi kanıt destekler"
|
|
343
|
+
"özellik denetimi becerisine nasıl ulaşılır ve onu hangi kanıt destekler",
|
|
344
|
+
"bir özelliği yapılmadan önce nasıl tanımlarım ve yapıldıktan sonra o tanıma göre nasıl denetlerim"
|
|
329
345
|
],
|
|
330
346
|
"sources": [
|
|
331
347
|
"skills/feature-audit/SKILL.md",
|
|
@@ -369,7 +385,8 @@
|
|
|
369
385
|
"where does an installed version unpack and how do I roll back",
|
|
370
386
|
"something is broken what do I check first",
|
|
371
387
|
"how do I turn on taste signal capture and measure my corrections",
|
|
372
|
-
"how does taste learn my coding preferences and inject them"
|
|
388
|
+
"how does taste learn my coding preferences and inject them",
|
|
389
|
+
"how do I resume or clean up a delegated slice after a crash or a quota stop"
|
|
373
390
|
],
|
|
374
391
|
"answers_tr": [
|
|
375
392
|
"bunu bir makineye nasıl kurar, doğrular ve yükseltirim",
|
|
@@ -378,7 +395,8 @@
|
|
|
378
395
|
"kurulu bir sürüm nereye açılır ve nasıl geri alırım",
|
|
379
396
|
"bir şey bozuk, önce neyi kontrol etmeliyim",
|
|
380
397
|
"zevk sinyali kaydını nasıl açar ve düzeltmelerimi nasıl ölçerim",
|
|
381
|
-
"taste kodlama tercihlerimi nasıl öğrenir ve oturuma nasıl ekler"
|
|
398
|
+
"taste kodlama tercihlerimi nasıl öğrenir ve oturuma nasıl ekler",
|
|
399
|
+
"bir çökme veya kota durmasından sonra devredilen bir dilimi nasıl sürdürür veya temizlerim"
|
|
382
400
|
],
|
|
383
401
|
"sources": [
|
|
384
402
|
"bin/tezgah-setup",
|
|
@@ -393,7 +411,9 @@
|
|
|
393
411
|
"hooks/tezgah_embed.py",
|
|
394
412
|
"hooks/tezgah_taste.py",
|
|
395
413
|
"hooks/tezgah_taste_ledger.py",
|
|
396
|
-
"bin/tezgah-taste"
|
|
414
|
+
"bin/tezgah-taste",
|
|
415
|
+
"hooks/tezgah_task.py",
|
|
416
|
+
"bin/tezgah-task"
|
|
397
417
|
]
|
|
398
418
|
},
|
|
399
419
|
{
|
package/docs/judge.md
CHANGED
|
@@ -25,13 +25,16 @@ transient failure - a timeout, a connection error, a 5xx - gets exactly one more
|
|
|
25
25
|
attempt. A 4xx, a session CLI's non-zero exit and a malformed reply never do
|
|
26
26
|
(`_transient`, `hooks/tezgah_judge.py::_transient`). A hook caller asks for one
|
|
27
27
|
attempt and a wall-clock `deadline` instead (`ask`, `hooks/tezgah_judge.py::ask`).
|
|
28
|
-
A session CLI answers in 4-10 s (measured 2026-10-07), so the skill hint's
|
|
28
|
+
A session CLI answers in 4-10 s (measured 2026-10-07, provider and model not recorded), so the skill hint's
|
|
29
29
|
4 s deadline usually ends first. `TEZGAH_TYPESAFE_URL`, `TEZGAH_JEV_OPENROUTER_URL`,
|
|
30
30
|
`TEZGAH_CLOUDFLARE_URL`, `TEZGAH_OPENROUTER_URL` and
|
|
31
31
|
`TEZGAH_OMP_BIN`/`TEZGAH_CLAUDE_BIN` are the test seams. Each URL override goes
|
|
32
32
|
through `override` (`hooks/tezgah_judge.py::override`), which refuses plain http
|
|
33
33
|
off this machine.
|
|
34
34
|
|
|
35
|
+
Each measured figure on this page measures only the provider and model it names,
|
|
36
|
+
and stays unmeasured for any other default.
|
|
37
|
+
|
|
35
38
|
Every caller reads an answer through the same accessors rather than reaching
|
|
36
39
|
into the raw reply, so a Choice and a Noul are read one way for all five
|
|
37
40
|
(`choice`, `hooks/tezgah_judge.py::choice`; `noul`,
|
|
@@ -123,7 +126,8 @@ that drops their tools or takes a system prompt. They run with the least their
|
|
|
123
126
|
wrote nothing. cursor-agent in ask mode refused the same shell call.
|
|
124
127
|
Measured 2026-10-07, a call cost $0.007 on claude and $0.015 on omp. Measured
|
|
125
128
|
2026-10-09 on one choice question, opencode read 28,646 input tokens ($0.004 by
|
|
126
|
-
its own report), cursor-agent 17,095 and codex 20,919.
|
|
129
|
+
its own report), cursor-agent 17,095 and codex 20,919. These figures name the CLI
|
|
130
|
+
that answered, and no record names its model. The session's own
|
|
127
131
|
credential pays, OAuth subscription included. The answer and usage come from the
|
|
128
132
|
CLI's JSON output (`_session_request`, `hooks/tezgah_judge.py::_session_request`).
|
|
129
133
|
`_cli_answer` (`hooks/tezgah_judge.py::_cli_answer`) reads the last three. Only
|
|
@@ -213,10 +217,12 @@ computes and prints the arithmetic it paid on every run (`PRICE_PER_MILLION`,
|
|
|
213
217
|
unit-selection shape it ships was measured on two reproduced screens: 100% of the
|
|
214
218
|
control lines at a 94% read on a 356-line table, and 100% at a 74% read on a
|
|
215
219
|
31-line screen, against 73.5% at a 26% read for the per-line shape it replaced.
|
|
220
|
+
The record of these three figures names no provider and no model (provider not
|
|
221
|
+
recorded).
|
|
216
222
|
The threshold those numbers were taken at is `SELECTED_AT`, and the tool prints
|
|
217
223
|
the measurement with every run (`MEASURED`, `bin/tezgah-triage::MEASURED`;
|
|
218
224
|
`SELECTED_AT`, `bin/tezgah-triage::SELECTED_AT`). The retry covers a rate that was
|
|
219
|
-
measured rather than assumed - 0 of 184 live calls returned `None` - so the second
|
|
225
|
+
measured rather than assumed - 0 of 184 live calls returned `None` (provider not recorded) - so the second
|
|
220
226
|
attempt costs a healthy call nothing (`_transient`,
|
|
221
227
|
`hooks/tezgah_judge.py::_transient`); `docs/operations.md` records the run. Each
|
|
222
228
|
caller also records one `judge` row of cost on the ledger when a session id is
|
package/docs/models.md
CHANGED
|
@@ -70,6 +70,37 @@ The orchestrator agent keeps `model: inherit`: it is the main thread's own agent
|
|
|
70
70
|
|
|
71
71
|
omp's bundled agents are routed through the same record (omp's own docs: `omp://task-agent-discovery.md`, *Bundled agents*): `scout` on the explore slot, `sonic` on the cheap slot, `task` on the standard slot, and `reviewer`/`security-reviewer` left to inherit, because a review is frontier work. A user's own value for any of them wins (see the ownership rule above). Those two are the gap this leaves: the record carries no entry for them, so a session on a cheap model reviews on that model - routing them is one more `BUNDLED` entry, not decided here.
|
|
72
72
|
|
|
73
|
+
A credential is not a balance, so the chain is live. A family whose provider
|
|
74
|
+
answered a limit leaves every chain until its reset (`funded_families`,
|
|
75
|
+
`hooks/tezgah_models.py::down_families`). The record is `model-health.json`
|
|
76
|
+
in the cache dir. The reset comes from the provider's own answer. That is a
|
|
77
|
+
named `reset at` time or a `retry-after-ms`, else six hours (`limit_until`).
|
|
78
|
+
A named reset already past also holds six hours, since the provider's time zone
|
|
79
|
+
is not stated.
|
|
80
|
+
`tezgah-route --health` probes each other family with a credential. OpenRouter
|
|
81
|
+
answers through its key endpoint, which costs no tokens. A family omp runs gets
|
|
82
|
+
one short prompt on its cheap row (`probe_family`). The command then records the
|
|
83
|
+
downs and re-applies omp's chain. Session start and each prompt start it in the
|
|
84
|
+
background when it is due (`health_due`, `start_health`). It is due when it was
|
|
85
|
+
never checked, when it is six hours old, or when a recorded reset has passed. The mode's own family is never
|
|
86
|
+
probed, since it leads the chain anyway. `tezgah-route --fallback any` lets a
|
|
87
|
+
chain fall back to another vendor's family once it is up. A family omp runs on
|
|
88
|
+
an account, such as zai, comes before OpenRouter, which bills per token
|
|
89
|
+
(`hooks/tezgah_models.py::_chain`). The probe uses the consult argv, so it loads
|
|
90
|
+
no tools, rules, skills or extensions.
|
|
91
|
+
Measured 2026-10-11: zai answered `429 Weekly/Monthly Limit Exhausted. Your limit
|
|
92
|
+
will reset at 2026-10-12 02:02:35`, so it stays out of the chain until then.
|
|
93
|
+
A subagent slice that stops on a quota or rate limit pushes the same record at
|
|
94
|
+
once (`hooks/tezgah_models.py::note_limit`). Its close calls it
|
|
95
|
+
(`hooks/tezgah_task.py::close_slice`). omp's transcript names the provider. A
|
|
96
|
+
Claude subagent runs on Anthropic unless its model names another vendor. A
|
|
97
|
+
quota is held until the answer's reset, else six hours. A rate limit is held
|
|
98
|
+
until its reset or retry-after, else ten minutes. Then the push re-applies omp's
|
|
99
|
+
chain. The mode's own family stays first in every chain, so a hold moves
|
|
100
|
+
only the fallbacks after it. Both writers re-read the record under a lock
|
|
101
|
+
before they write (`hooks/tezgah_models.py::_health_locked`), so neither drops
|
|
102
|
+
the other's hold.
|
|
103
|
+
|
|
73
104
|
## What is not routed yet
|
|
74
105
|
|
|
75
106
|
- **Claude Code's built-in subagents** (Explore, general-purpose, Plan) run on the session model. `CLAUDE_CODE_SUBAGENT_MODEL` and a settings `env` block exist but carry no effort, and whether a project agent with a built-in's name replaces it is not stated where it can be cited - so nothing is generated on a guess. What would close it: a documented per-agent effort for built-ins, or a confirmed name-collision rule.
|
|
@@ -97,7 +128,7 @@ fixed:
|
|
|
97
128
|
2. Otherwise the brief - redacted with the ledger's own reader
|
|
98
129
|
(`redact`, `hooks/tezgah_integrity.py::redact`) - goes to the judge as one Choice over three tiers (`TIER_QUESTION`,
|
|
99
130
|
`hooks/tezgah_models.py::TIER_QUESTION`). The judge asks the session's own model first ([judge](judge.md)).
|
|
100
|
-
Measured with Jev on 40 English briefs labelled by
|
|
131
|
+
Measured with Jev (model `jev-latest`, carrier not recorded) on 40 English briefs labelled by
|
|
101
132
|
the same session that wrote the rubric (2026-09-30, twice): under-route 0.025,
|
|
102
133
|
accuracy 0.925 and 0.900, 392 ms median, about 656 input tokens per call; a
|
|
103
134
|
keyword rule on the same set under-routed 0.100. The session model was not measured on that set.
|
|
@@ -106,7 +137,9 @@ fixed:
|
|
|
106
137
|
specialist? A yes names that agent on its own tier (`AGENT_SLOT`,
|
|
107
138
|
`hooks/tezgah_models.py::AGENT_SLOT`). Before this question, sessions
|
|
108
139
|
spawned the five specialists 0 times in 121 delegations (2026-10-07 to 10-09).
|
|
109
|
-
Clef agreed on 9 of 10 briefs this session labelled (2026-10-09
|
|
140
|
+
Clef agreed on 9 of 10 briefs this session labelled (2026-10-09, model not
|
|
141
|
+
recorded). Each judge figure on this page measures only the provider and model
|
|
142
|
+
it names, and stays unmeasured for any other default.
|
|
110
143
|
3. With no judgement (`judge-off`, no key, a failed call) `--phase review` and
|
|
111
144
|
`--phase research` name the reviewer and the researcher (`PHASE_AGENT`,
|
|
112
145
|
`hooks/tezgah_models.py::PHASE_AGENT`). Another phase picks the tier from the
|