@r1z4x/tezgah 1.4.0 → 1.5.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 +71 -1
- package/agents/tezgah-reviewer.md +19 -10
- package/bin/tezgah-docs +3 -3
- package/bin/tezgah-route +5 -2
- package/bin/tezgah-setup +24 -1
- package/bin/tezgah-status +3 -1
- package/bin/tezgah-task +130 -37
- package/bin/tezgah-taste +49 -31
- package/bin/tezgah-triage +12 -7
- package/docs/clarity-baseline.json +6 -6
- package/docs/contract.md +21 -11
- package/docs/environment.md +3 -1
- package/docs/evidence.md +4 -3
- package/docs/glossary.md +1 -1
- package/docs/judge.md +93 -27
- package/docs/operations.md +23 -12
- package/docs/skills.md +1 -1
- package/docs/vision.md +10 -5
- package/hooks/tezgah_agents.py +22 -11
- package/hooks/tezgah_context.py +40 -6
- package/hooks/tezgah_judge.py +245 -79
- package/hooks/tezgah_paths.py +165 -27
- package/hooks/tezgah_policy.py +35 -8
- package/hooks/tezgah_replay.py +26 -15
- package/hooks/tezgah_task.py +146 -40
- package/hooks/tezgah_taste.py +2 -2
- package/hooks/tezgah_taste_ledger.py +13 -4
- package/hosts/codex/hook.py +5 -0
- package/package.json +1 -1
- package/skills/harness/SKILL.md +4 -0
- package/skills/plan-add/SKILL.md +23 -7
- package/skills/plan-add/render_table.py +3 -2
- package/skills/plan-status/SKILL.md +12 -4
- package/skills/plan-sync/SKILL.md +19 -9
- package/skills/tezgah-contract/SKILL.md +25 -12
package/bin/tezgah-taste
CHANGED
|
@@ -39,9 +39,11 @@ design T1P). Each prompt after a writing turn - from the capture rows in the
|
|
|
39
39
|
from this repository's omp and Claude transcripts - becomes one structured signal
|
|
40
40
|
built by code: the prompt, the files written, the agent's edits and the user's
|
|
41
41
|
own edit between two agent writes. One typed decision per signal, required from
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
the Jev model (`only=("jev",)`: TypeSafe, OpenRouter System One, Cloudflare
|
|
43
|
+
Workers AI or a compatible endpoint - whichever carrier the judge seam resolves):
|
|
44
|
+
preference, defect or none; the category; the scope; and how it relates to each
|
|
45
|
+
learning held. A decision any other provider gave is recorded `unverified` and
|
|
46
|
+
never applied. A defect goes to the `defects`
|
|
45
47
|
table, never into the ledger. The ledger itself is `hooks/tezgah_taste_ledger.py`. A
|
|
46
48
|
learning that becomes active gets one readable line from a generative provider
|
|
47
49
|
(a `text` question; the user's own words when none answers).
|
|
@@ -320,7 +322,7 @@ def classify(texts, model, only=None):
|
|
|
320
322
|
|
|
321
323
|
The rule rides the state, so each request pays for it once and the
|
|
322
324
|
questions carry only their own prompt."""
|
|
323
|
-
labels, tokens_in, tokens_out, used = [], 0, 0, model
|
|
325
|
+
labels, tokens_in, tokens_out, used, spent = [], 0, 0, model, 0.0
|
|
324
326
|
for start in range(0, len(texts), BATCH):
|
|
325
327
|
chunk = texts[start:start + BATCH]
|
|
326
328
|
questions = {"p%d" % i: {"type": "choice", "criteria": CRITERIA,
|
|
@@ -332,24 +334,36 @@ def classify(texts, model, only=None):
|
|
|
332
334
|
tokens_in += result["usage"]["input_tokens"]
|
|
333
335
|
tokens_out += result["usage"]["output_tokens"]
|
|
334
336
|
used = result.get("model", used)
|
|
337
|
+
one = result_cost(result)
|
|
338
|
+
spent = None if spent is None or one is None else spent + one
|
|
335
339
|
for i in range(len(chunk)):
|
|
336
340
|
label = tezgah_judge.choice(result, "p%d" % i)
|
|
337
341
|
labels.append(label if label in CLASSES else None)
|
|
338
342
|
return labels, {"model": used, "input_tokens": tokens_in,
|
|
339
|
-
"output_tokens": tokens_out,
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
def cost(model, tokens_in, tokens_out):
|
|
344
|
-
"""Dollars at the model's listed price per 1M tokens, None when unlisted.
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
343
|
+
"output_tokens": tokens_out,
|
|
344
|
+
"cost_usd": None if spent is None else round(spent, 6)}
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def cost(model, tokens_in, tokens_out, provider=None):
|
|
348
|
+
"""Dollars at the model's listed price per 1M tokens, None when unlisted. A
|
|
349
|
+
typed carrier (`tezgah_judge.input_price`) is priced by the carrier that
|
|
350
|
+
answered: Clef's price is not Jev's."""
|
|
351
|
+
typed = tezgah_judge.input_price(provider)
|
|
352
|
+
price = (typed, 0.0) if typed is not None else JEV_PRICE \
|
|
353
|
+
if model == tezgah_judge.MODEL else (
|
|
354
|
+
tezgah_models.overlay().get("prices", {}).get(model)
|
|
355
|
+
or tezgah_models.SNAPSHOT.get(model))
|
|
348
356
|
if not (isinstance(price, (list, tuple)) and len(price) == 2):
|
|
349
357
|
return None
|
|
350
358
|
return round((tokens_in * price[0] + tokens_out * price[1]) / 1e6, 6)
|
|
351
359
|
|
|
352
360
|
|
|
361
|
+
def result_cost(result):
|
|
362
|
+
"""One judge result's cost, priced by the provider that answered it."""
|
|
363
|
+
return cost(result.get("model"), result["usage"]["input_tokens"],
|
|
364
|
+
result["usage"]["output_tokens"], result.get("provider"))
|
|
365
|
+
|
|
366
|
+
|
|
353
367
|
def usage_line(usage):
|
|
354
368
|
money = ("$%.6f" % usage["cost_usd"] if usage["cost_usd"] is not None
|
|
355
369
|
else "cost unknown (no listed price)")
|
|
@@ -370,7 +384,7 @@ def cmd_measure(args):
|
|
|
370
384
|
if not tezgah_judge.available():
|
|
371
385
|
return misuse("no judge: no credential resolves or judge-off is set; nothing sent")
|
|
372
386
|
got, usage = classify([text for text, _gold in pairs], args.model,
|
|
373
|
-
only=("
|
|
387
|
+
only=("jev",) if args.typed else None)
|
|
374
388
|
confusion = {g: {p: 0 for p in CLASSES} for g in CLASSES}
|
|
375
389
|
unjudged = 0
|
|
376
390
|
for (_text, gold), pred in zip(pairs, got):
|
|
@@ -628,12 +642,12 @@ def picked(result, qid, default):
|
|
|
628
642
|
|
|
629
643
|
|
|
630
644
|
def decide(sig, learnings, fallback=True):
|
|
631
|
-
"""(decision, result): the typed decision,
|
|
645
|
+
"""(decision, result): the typed decision, the Jev model required; when it cannot
|
|
632
646
|
answer, the seam's own providers, recorded unverified - only on a signal's
|
|
633
647
|
first try, since an unverified answer is never applied and a rerun would pay
|
|
634
648
|
for it again."""
|
|
635
649
|
state, questions = decision_request(sig, learnings)
|
|
636
|
-
result = tezgah_judge.ask(state, questions, only=("
|
|
650
|
+
result = tezgah_judge.ask(state, questions, only=("jev",), timeout=60)
|
|
637
651
|
if result is None and fallback:
|
|
638
652
|
result = tezgah_judge.ask(state, questions, timeout=120)
|
|
639
653
|
if result is None:
|
|
@@ -643,7 +657,7 @@ def decide(sig, learnings, fallback=True):
|
|
|
643
657
|
"scope": picked(result, "scope", "repository"),
|
|
644
658
|
"relations": {lr["id"]: picked(result, "rel_" + lr["id"], "unrelated")
|
|
645
659
|
for lr in learnings},
|
|
646
|
-
"verified": result.get("provider")
|
|
660
|
+
"verified": tezgah_judge.is_jev(result.get("provider"))}
|
|
647
661
|
return dec, result
|
|
648
662
|
|
|
649
663
|
|
|
@@ -678,7 +692,7 @@ def learn_refusal(repo):
|
|
|
678
692
|
"""Why `learn` sends and writes nothing here, or None - read before the
|
|
679
693
|
store lock, so a refused run leaves no lock file behind."""
|
|
680
694
|
if tezgah_judge.tp.off("judge-off") or not (
|
|
681
|
-
tezgah_judge.available() or tezgah_judge.named(("
|
|
695
|
+
tezgah_judge.available() or tezgah_judge.named(("jev",))):
|
|
682
696
|
return "no judge: no credential resolves or judge-off is set; nothing sent"
|
|
683
697
|
import tezgah_context
|
|
684
698
|
if ".no-taste" in tezgah_context.repo_marks(repo)[1]:
|
|
@@ -692,16 +706,16 @@ def cmd_learn(args):
|
|
|
692
706
|
else capture_signals(repo))
|
|
693
707
|
decided = {}
|
|
694
708
|
for r in tl.rows(repo, "decisions"):
|
|
695
|
-
decided[r.get("id")] = decided.get(r.get("id")) or r.get("provider")
|
|
696
|
-
# a typed decision is final; an unverified one is retried,
|
|
709
|
+
decided[r.get("id")] = decided.get(r.get("id")) or tezgah_judge.is_jev(r.get("provider"))
|
|
710
|
+
# a typed decision is final; an unverified one is retried, the Jev model only
|
|
697
711
|
todo = [s for s in signals if not decided.get(s["id"])]
|
|
698
712
|
if args.limit is not None:
|
|
699
713
|
todo = todo[:args.limit]
|
|
700
714
|
ledgers = tl.load(repo)
|
|
701
715
|
before = {k for k, v in tl.every(ledgers).items() if v["state"] == "active"}
|
|
702
|
-
counts = {"signals": len(todo), "
|
|
716
|
+
counts = {"signals": len(todo), "jev": 0, "unverified": 0, "unanswered": 0,
|
|
703
717
|
"preference": 0, "defect": 0, "none": 0}
|
|
704
|
-
tokens = [0, 0]
|
|
718
|
+
tokens, spent = [0, 0], 0.0
|
|
705
719
|
for sig in todo:
|
|
706
720
|
dec, result = decide(sig, held(ledgers),
|
|
707
721
|
fallback=not args.no_fallback and sig["id"] not in decided)
|
|
@@ -710,6 +724,8 @@ def cmd_learn(args):
|
|
|
710
724
|
continue
|
|
711
725
|
tokens[0] += result["usage"]["input_tokens"]
|
|
712
726
|
tokens[1] += result["usage"]["output_tokens"]
|
|
727
|
+
one = result_cost(result)
|
|
728
|
+
spent = None if spent is None or one is None else spent + one
|
|
713
729
|
day = tl.now_day()
|
|
714
730
|
tl.append(repo, "decisions", {
|
|
715
731
|
"id": sig["id"], "day": day, "at": sig.get("at"), "session": sig["session"],
|
|
@@ -721,7 +737,7 @@ def cmd_learn(args):
|
|
|
721
737
|
if not dec["verified"]:
|
|
722
738
|
counts["unverified"] += 1
|
|
723
739
|
continue
|
|
724
|
-
counts["
|
|
740
|
+
counts["jev"] += 1
|
|
725
741
|
counts[dec["kind"][0] if dec["kind"][0] in CLASSES else "none"] += 1
|
|
726
742
|
if dec["kind"][0] == "defect":
|
|
727
743
|
tl.append(repo, "defects",
|
|
@@ -743,16 +759,18 @@ def cmd_learn(args):
|
|
|
743
759
|
for learning in tl.every(ledgers).values():
|
|
744
760
|
states[learning["state"]] = states.get(learning["state"], 0) + 1
|
|
745
761
|
report = dict(counts, states=states, input_tokens=tokens[0], output_tokens=tokens[1],
|
|
746
|
-
cost_usd=
|
|
762
|
+
cost_usd=None if spent is None else round(spent, 6))
|
|
747
763
|
if args.json:
|
|
748
764
|
print(json.dumps(report, indent=2))
|
|
749
765
|
return 0
|
|
750
|
-
print("signals %d:
|
|
751
|
-
counts["signals"], counts["
|
|
766
|
+
print("signals %d: jev %d, unverified %d, unanswered %d" % (
|
|
767
|
+
counts["signals"], counts["jev"], counts["unverified"], counts["unanswered"]))
|
|
752
768
|
print("decided: preference %d, defect %d, none %d" % (
|
|
753
769
|
counts["preference"], counts["defect"], counts["none"]))
|
|
754
770
|
print("ledger: " + (", ".join("%s %d" % kv for kv in sorted(states.items())) or "empty"))
|
|
755
|
-
|
|
771
|
+
money = report["cost_usd"]
|
|
772
|
+
print("typed decisions: %d input tokens, %s" % (
|
|
773
|
+
tokens[0], "$%s" % money if money is not None else "cost unknown (no listed price)"))
|
|
756
774
|
return 0
|
|
757
775
|
|
|
758
776
|
|
|
@@ -808,8 +826,8 @@ def cmd_label(args):
|
|
|
808
826
|
sample of unlabelled typed decisions - drawn without regard to what was
|
|
809
827
|
injected, so the calibration bound rests on an unselected sample."""
|
|
810
828
|
repo = store_or_exit(args)
|
|
811
|
-
decided = {r["id"]: r for r in tl.rows(repo, "decisions",
|
|
812
|
-
|
|
829
|
+
decided = {r["id"]: r for r in tl.rows(repo, "decisions", *tl.jev_rows())
|
|
830
|
+
if r.get("id")}
|
|
813
831
|
labelled = {r.get("id") for r in tl.rows(repo, "labels")}
|
|
814
832
|
if args.id:
|
|
815
833
|
if args.label not in CLASSES:
|
|
@@ -873,7 +891,7 @@ def main(argv):
|
|
|
873
891
|
measure.add_argument("--model", default=tezgah_judge.MODEL)
|
|
874
892
|
measure.add_argument("--json", action="store_true")
|
|
875
893
|
measure.add_argument("--typed", action="store_true",
|
|
876
|
-
help="require
|
|
894
|
+
help="require the Jev model (the decision T1P's ledger trusts)")
|
|
877
895
|
rate = sub.add_parser("rate")
|
|
878
896
|
rate.add_argument("--in", dest="inp", required=True)
|
|
879
897
|
rate.add_argument("--limit", type=int)
|
|
@@ -883,7 +901,7 @@ def main(argv):
|
|
|
883
901
|
learn.add_argument("--host", choices=("omp", "claude", "all"), default="all")
|
|
884
902
|
learn.add_argument("--limit", type=int)
|
|
885
903
|
learn.add_argument("--no-fallback", action="store_true",
|
|
886
|
-
help="
|
|
904
|
+
help="the Jev model only: never pay another provider for a decision")
|
|
887
905
|
listing = sub.add_parser("list")
|
|
888
906
|
listing.add_argument("--all", action="store_true")
|
|
889
907
|
for name in ("show", "accept", "reject", "edit"):
|
package/bin/tezgah-triage
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
A `browser_snapshot` / `mobile_list_elements_on_screen` result is the whole screen
|
|
5
5
|
as text - one measured 40-row work-order table came back as 13,045 characters in
|
|
6
6
|
356 lines - and the analyze-app loop pays for all of it to find the dozen lines
|
|
7
|
-
that carry the controls. This flattens the snapshot, asks
|
|
7
|
+
that carry the controls. This flattens the snapshot, asks the judge seam (the
|
|
8
|
+
session's own CLI or the Jev model through any carrier) about the
|
|
8
9
|
units of the tree (a row, a table cell, a control on its own) in ONE batched
|
|
9
10
|
request, and prints the line ids under the selected units with their original
|
|
10
11
|
element refs.
|
|
@@ -90,10 +91,13 @@ MEASURED = ("100% of the control lines at a 94% read on a reproduced 356-line "
|
|
|
90
91
|
"screen")
|
|
91
92
|
CUT = 96
|
|
92
93
|
USAGE = "tezgah-triage --select FILE --task TEXT | --states FILE [--component TEXT]"
|
|
93
|
-
NO_CREDENTIAL = ("no judge: no session CLI (omp
|
|
94
|
-
"and no third party the `fallback`
|
|
95
|
-
"
|
|
96
|
-
"
|
|
94
|
+
NO_CREDENTIAL = ("no judge: no session CLI (omp, claude, opencode, cursor or codex, "
|
|
95
|
+
"the one this session runs under), and no third party the `fallback` "
|
|
96
|
+
"setting allows - the Jev model through TypeSafe (TYPESAFE_API_KEY or "
|
|
97
|
+
"~/.config/typesafe/key), OpenRouter System One or the OpenRouter chat "
|
|
98
|
+
"fallback (OPENROUTER_API_KEY or ~/.config/openrouter/key), Cloudflare "
|
|
99
|
+
"Workers AI, or a compatible endpoint (JEV_API_BASE_URL), the carrier "
|
|
100
|
+
"picked by TEZGAH_JEV_PROVIDER or config `jev` - or the judge-off "
|
|
97
101
|
"switch is on")
|
|
98
102
|
TRIAGE_OFF = "the triage-off switch is on"
|
|
99
103
|
FAILED = ("the call failed - a timeout, a refusal, or a reply that is not the "
|
|
@@ -197,10 +201,11 @@ def trim(line):
|
|
|
197
201
|
|
|
198
202
|
def judge_line(result):
|
|
199
203
|
usage = result["usage"]
|
|
204
|
+
# the carrier's own price (Clef's is not Jev's); Jev's for a chat stand-in
|
|
205
|
+
price = tezgah_judge.input_price(result.get("provider")) or PRICE_PER_MILLION
|
|
200
206
|
print("judge: %d input tokens, %d output, %d ms; $%.6f at $%.3f/1M input"
|
|
201
207
|
% (usage["input_tokens"], usage["output_tokens"], result["latency_ms"],
|
|
202
|
-
usage["input_tokens"] *
|
|
203
|
-
PRICE_PER_MILLION))
|
|
208
|
+
usage["input_tokens"] * price / 1000000, price))
|
|
204
209
|
|
|
205
210
|
|
|
206
211
|
def units(numbered):
|
|
@@ -18,10 +18,10 @@
|
|
|
18
18
|
"semicolons": 9
|
|
19
19
|
},
|
|
20
20
|
"docs/contract.md": {
|
|
21
|
-
"long_sentences":
|
|
22
|
-
"passive":
|
|
21
|
+
"long_sentences": 42,
|
|
22
|
+
"passive": 25,
|
|
23
23
|
"replace_words": 0,
|
|
24
|
-
"semicolons":
|
|
24
|
+
"semicolons": 30
|
|
25
25
|
},
|
|
26
26
|
"docs/environment.md": {
|
|
27
27
|
"long_sentences": 3,
|
|
@@ -61,9 +61,9 @@
|
|
|
61
61
|
},
|
|
62
62
|
"docs/judge.md": {
|
|
63
63
|
"long_sentences": 22,
|
|
64
|
-
"passive":
|
|
64
|
+
"passive": 14,
|
|
65
65
|
"replace_words": 0,
|
|
66
|
-
"semicolons":
|
|
66
|
+
"semicolons": 13
|
|
67
67
|
},
|
|
68
68
|
"docs/layers.md": {
|
|
69
69
|
"long_sentences": 20,
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"semicolons": 9
|
|
79
79
|
},
|
|
80
80
|
"docs/operations.md": {
|
|
81
|
-
"long_sentences":
|
|
81
|
+
"long_sentences": 66,
|
|
82
82
|
"passive": 21,
|
|
83
83
|
"replace_words": 3,
|
|
84
84
|
"semicolons": 42
|
package/docs/contract.md
CHANGED
|
@@ -38,7 +38,9 @@ rules and all.
|
|
|
38
38
|
The skill suggestion is the one surface a judgement writes rather than a constant.
|
|
39
39
|
The roster reaches a session as an index of host-truncated one-liners, so which
|
|
40
40
|
entry to look at first is the turn's own weak spot; one batched judgement request
|
|
41
|
-
answers it -
|
|
41
|
+
answers it - the session's own CLI, else the Jev model through whichever carrier
|
|
42
|
+
resolves (TypeSafe, OpenRouter System One, Cloudflare Workers AI, a compatible
|
|
43
|
+
endpoint), else the OpenRouter chat fallback
|
|
42
44
|
(`credential()`, `hooks/tezgah_judge.py::credential`) - a Choice across the installed
|
|
43
45
|
skill names plus `none`, and one Noul
|
|
44
46
|
asking whether the turn wants a skill at all - and the winner is appended after
|
|
@@ -158,11 +160,14 @@ never told this; and a ledger with no such row costs no bytes. `verify-off` drop
|
|
|
158
160
|
it with the integrity rule it restates.
|
|
159
161
|
|
|
160
162
|
The on-demand contract also carries detail the core has no budget for. One is
|
|
161
|
-
the review rule
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
163
|
+
the review rule. A plan gets one review over its whole diff, in at most two
|
|
164
|
+
rounds. Round one fans out one reviewer per dimension. Only a confirmed critical
|
|
165
|
+
or major finding opens round two, and `tezgah-task review` refuses a third. A
|
|
166
|
+
change with an executable oracle - a checker, a gate rule, a parser - spends the
|
|
167
|
+
same budget. Its round one is four direction-led reviewers, and each claim
|
|
168
|
+
becomes a fixture that runs before it counts. That population is a tentative
|
|
169
|
+
default, measured on two research lines with a written flip
|
|
170
|
+
(`skills/tezgah-contract/SKILL.md:519-550`).
|
|
166
171
|
|
|
167
172
|
## Composition order of the always-on core
|
|
168
173
|
|
|
@@ -272,12 +277,17 @@ sentence of the rule.
|
|
|
272
277
|
`PROMPT_HINTS` (`hooks/tezgah_context.py::PROMPT_HINTS`) is one compiled pattern per key
|
|
273
278
|
— `spec`, `consult`, `research`, `product`, `graph`, `fanout` — and `classify_prompt()` returns the keys
|
|
274
279
|
a prompt matches (`hooks/tezgah_context.py::classify_prompt`). `fanout` arms on the
|
|
275
|
-
shape of the request, not a word. Two
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
280
|
+
shape of the request, not a word. Two list entries on their own lines arm it.
|
|
281
|
+
An entry starts with `1.`, `1)`, `1 -`, `1:`, `a)` or a bullet. An inline
|
|
282
|
+
`1. ... 2. ...` arms it too. So do three files named as one list, "First, ...
|
|
283
|
+
Second, ..." and three imperative clauses in one sentence. An explicit
|
|
284
|
+
subagent, parallel or worktree ask arms it as well. The owner no longer has to
|
|
285
|
+
say "do it with subagents, in parallel". On that turn only, the matching
|
|
286
|
+
paragraphs are appended after the reminder
|
|
279
287
|
(`hooks/tezgah_context.py::context_for`); a session that never asks such a question
|
|
280
|
-
pays the one-line pointer instead.
|
|
288
|
+
pays the one-line pointer instead. A later match in the same session pays one
|
|
289
|
+
line. The fan-out line still names the action
|
|
290
|
+
(`hooks/tezgah_context.py::armed_again`). The patterns carry Turkish stems because the
|
|
281
291
|
user writes Turkish, and a plain prompt arms nothing. The same prompt arms the
|
|
282
292
|
same rules on every host (`tests/test_context.py::ArmingConformance.test_all_hosts_arm_the_same_rules`) and each advisory
|
|
283
293
|
rule keeps a pointer line in the always-on text (`tests/test_context.py::ArmingConformance.test_every_advisory_rule_has_an_always_on_pointer`).
|
package/docs/environment.md
CHANGED
|
@@ -56,6 +56,7 @@ the kill switches are files, not variables, and live in
|
|
|
56
56
|
| `TEZGAH_ORCA_BIN` | `hooks/tezgah_orca.py::cli` | `ORCA_CLI_COMMAND`, then `$ORCA_CLI_BIN_DIR/orca`, then `orca` on PATH (`orca-ide` on Linux outside Orca), then the macOS app's CLI | The Orca CLI `tezgah-status --orca` and `tezgah-setup --status` ask. A missing path is no CLI, never a fallback: the test suite points it at one. |
|
|
57
57
|
| `TEZGAH_OMP_BIN` | `hooks/tezgah_paths.py::omp_bin` | `omp` on PATH | The omp binary the installer, the model table, consult's session-model lookup and the judgement seam's session call run; tests point it at a stand-in. |
|
|
58
58
|
| `TEZGAH_CLAUDE_BIN` | `claude_bin`, `hooks/tezgah_paths.py::claude_bin` | `claude` on PATH | The Claude Code CLI `--install` registers the plugin with (`claude plugin`) and the judgement seam asks in a Claude Code session; the test suite points it at a path that does not exist, or at a stand-in. |
|
|
59
|
+
| `TEZGAH_OPENCODE_BIN`, `TEZGAH_CURSOR_BIN`, `TEZGAH_CODEX_BIN` | `hooks/tezgah_paths.py::opencode_bin`, `hooks/tezgah_paths.py::cursor_bin`, `hooks/tezgah_paths.py::codex_bin` | `opencode`, `cursor-agent`, `codex` on PATH or a per-user bin dir | The CLI the judgement seam asks in that host's session; tests point them at stand-ins. |
|
|
59
60
|
| `TEZGAH_DSH_BIN` | `bin/tezgah-setup::reconcile_json_servers` | the profile-local entry, then `bin/tezgah-dsh`, then npx | How the installer invokes the dsh CLI. |
|
|
60
61
|
| `TEZGAH_INDEX_BIN` | `bin/tezgah-dsh:19`, `bin/tezgah-dsh.cmd` | `~/.config/tezgah/bin/tezgah-index` | The index worker `tezgah-dsh` warms before it starts dsh. |
|
|
61
62
|
| `TEZGAH_STATUS_BIN` | `hosts/dsh/statusline/lib/index.js:24`, `hosts/opencode/tui/tezgah-tui.tsx` | `~/.config/tezgah/bin/tezgah-status` | The status renderer the dsh status line and the opencode TUI call. |
|
|
@@ -73,7 +74,7 @@ the kill switches are files, not variables, and live in
|
|
|
73
74
|
|
|
74
75
|
| Variable | Read by | Default | Effect |
|
|
75
76
|
|---|---|---|---|
|
|
76
|
-
| `OMPCODE`, `CLAUDECODE` | `hooks/tezgah_paths.py::session_cli
|
|
77
|
+
| `OMPCODE`, `CLAUDECODE`, `OPENCODE`, `CURSOR_AGENT`, `CURSOR_VERSION`, `CODEX_THREAD_ID` | `hooks/tezgah_paths.py::session_cli` (all), `bin/consult`, `hooks/tezgah_models.py::worker_model` (the first two) | set by the host | Name the host session: the judgement seam asks that host's CLI first, in the order listed (omp sets both of the first two, so `OMPCODE` wins; Cursor sets `CURSOR_AGENT` in its shell tool and `CURSOR_VERSION` in its hooks; Codex sets `CODEX_THREAD_ID` in its shell tool only). The test suite unsets all of them. |
|
|
77
78
|
| `TYPESAFE_API_KEY` | `hooks/tezgah_judge.py::key`, `hooks/tezgah_paths.py` | `~/.config/typesafe/key` | The judgement seam's first third-party credential, used as `fallback` in `config.json` allows ([judge](judge.md)); omp also reads it for its own judge. |
|
|
78
79
|
| `OPENROUTER_API_KEY` | `hooks/tezgah_judge.py::openrouter_key`, `bin/consult`, `bin/codegen`, `hooks/tezgah_models.py` | `~/.config/openrouter/key` | The seam's chat fallback, consult's and codegen's `openrouter` provider, and omp's `any` family. |
|
|
79
80
|
| `DEEPSEEK_API_KEY`, `INCEPTION_API_KEY` | `bin/consult`, `bin/codegen`, `hooks/tezgah_replay.py::label_model` | `~/.config/deepseek/key`, `~/.config/inception/key` | The `deepseek` and `inception` providers of consult and codegen; `DEEPSEEK_API_KEY` alone is also the replay sheet's `--provider deepseek` rater. |
|
|
@@ -82,6 +83,7 @@ the kill switches are files, not variables, and live in
|
|
|
82
83
|
| `TEZGAH_OPENROUTER_URL` | `hooks/tezgah_judge.py::openrouter_url` | OpenRouter's chat-completions URL | Repoints the seam's chat fallback (tests). Plain `http` is refused unless the host is this machine. |
|
|
83
84
|
| `TEZGAH_DEEPSEEK_URL` | `hooks/tezgah_replay.py::_deepseek` | `https://api.deepseek.com/chat/completions` | Repoints the replay sheet's `--provider deepseek` rater (tests). Plain `http` is refused unless the host is this machine. |
|
|
84
85
|
| `TEZGAH_JUDGE_MODEL` | `hooks/tezgah_judge.py::fallback_model` | the cheap `any` row of the models table | The model the seam's chat fallback asks. |
|
|
86
|
+
| `TEZGAH_JUDGE_CLI` | `hooks/tezgah_paths.py::session_cli` | `judge_cli` in `config.json`, else `auto` | The session CLI the judgement seam asks: `omp`, `claude`, `opencode`, `cursor` or `codex`, or `auto` to read the host's marker. A named CLI that is not installed means no session CLI, never another one. |
|
|
85
87
|
|
|
86
88
|
## consult and codegen
|
|
87
89
|
|
package/docs/evidence.md
CHANGED
|
@@ -785,11 +785,12 @@ one rater's totals would show the other rater those labels.
|
|
|
785
785
|
|
|
786
786
|
`replay --label-model --prompt <file>` is rater 2 when that rater is a model. It sends each sheet
|
|
787
787
|
row to the judge seam (`hooks/tezgah_judge.py`) as one `choice` question and writes
|
|
788
|
-
`labels-model.jsonl` beside the sheet. It redacts the row's text first (`ti.redact`)
|
|
789
|
-
only when
|
|
788
|
+
`labels-model.jsonl` beside the sheet. It redacts the row's text first (`ti.redact`). It runs
|
|
789
|
+
only when a Jev carrier answers: TypeSafe, OpenRouter System One, Cloudflare Workers AI or a
|
|
790
|
+
compatible endpoint. `--provider jev` names it, and the old name `typesafe` still works. The prompt file holds each set's instructions and criteria. A reply
|
|
790
791
|
with no label from those criteria leaves its row unlabelled. The command writes each label as it
|
|
791
792
|
arrives, so a run that stops early keeps what it paid for. A second run asks only the unlabelled rows. Every run prints the input tokens and their cost. The sheet rows leave the machine for
|
|
792
|
-
|
|
793
|
+
the Jev carrier's endpoint. The key and the corpus stay on it. Kappa against a model rater
|
|
793
794
|
measures human-model agreement, not inter-rater reliability.
|
|
794
795
|
|
|
795
796
|
`--provider deepseek --model deepseek-v4-pro` asks the same questions from the same prompt file
|
package/docs/glossary.md
CHANGED
|
@@ -98,7 +98,7 @@ A mark consumed by its own first use, so the thing it protects happens once: the
|
|
|
98
98
|
An opt-out file in the repo tree - `.no-ponytail`, `.no-adhd`, `.no-graph`, `.no-lessons`, `.no-taste` - collected by `repo_marks()` walking up to the enclosing root (`hooks/tezgah_context.py::repo_marks`). Not a [kill switch](#kill-switch): a mark travels with the repo, a switch with the machine.
|
|
99
99
|
|
|
100
100
|
### plan
|
|
101
|
-
A piece of work spanning sessions, one markdown file with frontmatter under `<repo root>/.tezgah/plans/open/`, moved to `.tezgah/plans/done/` when closed, up to three of them injected by `open_plans()` (`hooks/tezgah_context.py::open_plans`) and its work happening on a `plan/NNN-slug` branch. Not a TODO in code.
|
|
101
|
+
A piece of work spanning sessions, one markdown file with frontmatter under `<repo root>/.tezgah/plans/open/`, moved to `.tezgah/plans/done/` when closed, up to three of them injected by `open_plans()` (`hooks/tezgah_context.py::open_plans`) and its work happening on a `plan/NNN-slug` branch. An optional `after:` names the plans it waits on, and `tezgah-task status` reads it with the ticked Acceptance boxes (`hooks/tezgah_task.py::queue`). Not a TODO in code, and not the host's session todo list, which no reader syncs with it.
|
|
102
102
|
|
|
103
103
|
### plan table
|
|
104
104
|
The status table in `.tezgah/plans/README.md` (`.tezgah/plans/README.md:9`), owned by the `plan-status` skill (`skills/plan-status/SKILL.md:1`) and among the first blocks the [budget](#budget) gives up because it lives on disk (`hooks/tezgah_context.py::DROP_ORDER`). Not the injected list of open plans.
|
package/docs/judge.md
CHANGED
|
@@ -14,9 +14,10 @@ the kill-switch paragraph and nothing else (`CORE`,
|
|
|
14
14
|
One seam, five callers, one provider order, one egress boundary, one price, one
|
|
15
15
|
redirect guard. The module is stdlib only and total - a failure is a `None`, never
|
|
16
16
|
an exception, because a hook imports it and a hook that raises takes a session
|
|
17
|
-
down (`_request`, `hooks/tezgah_judge.py::_request`).
|
|
18
|
-
|
|
19
|
-
`hooks/tezgah_judge.py::
|
|
17
|
+
down (`_request`, `hooks/tezgah_judge.py::_request`). The carriers' endpoints are
|
|
18
|
+
module constants: `URL` (`hooks/tezgah_judge.py::URL`), `JEV_OPENROUTER_URL`
|
|
19
|
+
(`hooks/tezgah_judge.py::JEV_OPENROUTER_URL`) and `CLOUDFLARE_URL`
|
|
20
|
+
(`hooks/tezgah_judge.py::CLOUDFLARE_URL`). One opener serves every call (`OPENER`,
|
|
20
21
|
`hooks/tezgah_judge.py::OPENER`), and it refuses a redirect that leaves the
|
|
21
22
|
endpoint's host, so a `Location` cannot carry the bearer (`guarded_opener`,
|
|
22
23
|
`hooks/tezgah_paths.py`, the one copy `bin/consult` and `bin/codegen` use too). A
|
|
@@ -25,15 +26,18 @@ attempt. A 4xx, a session CLI's non-zero exit and a malformed reply never do
|
|
|
25
26
|
(`_transient`, `hooks/tezgah_judge.py::_transient`). A hook caller asks for one
|
|
26
27
|
attempt and a wall-clock `deadline` instead (`ask`, `hooks/tezgah_judge.py::ask`).
|
|
27
28
|
A session CLI answers in 4-10 s (measured 2026-10-07), so the skill hint's
|
|
28
|
-
4 s deadline usually ends first. `TEZGAH_TYPESAFE_URL`, `
|
|
29
|
-
|
|
29
|
+
4 s deadline usually ends first. `TEZGAH_TYPESAFE_URL`, `TEZGAH_JEV_OPENROUTER_URL`,
|
|
30
|
+
`TEZGAH_CLOUDFLARE_URL`, `TEZGAH_OPENROUTER_URL` and
|
|
31
|
+
`TEZGAH_OMP_BIN`/`TEZGAH_CLAUDE_BIN` are the test seams. Each URL override goes
|
|
32
|
+
through `override` (`hooks/tezgah_judge.py::override`), which refuses plain http
|
|
33
|
+
off this machine.
|
|
30
34
|
|
|
31
35
|
Every caller reads an answer through the same accessors rather than reaching
|
|
32
36
|
into the raw reply, so a Choice and a Noul are read one way for all five
|
|
33
37
|
(`choice`, `hooks/tezgah_judge.py::choice`; `noul`,
|
|
34
38
|
`hooks/tezgah_judge.py::noul`). A `text` question is the one shape that asks
|
|
35
39
|
for prose. `bin/tezgah-taste` asks it for a learning's readable line, and only
|
|
36
|
-
a generative provider answers one.
|
|
40
|
+
a generative provider answers one. No Jev carrier is sent one (`text`,
|
|
37
41
|
`hooks/tezgah_judge.py::text`). All three are total: a missing or
|
|
38
42
|
wrongly-typed answer is a `None`, never an exception.
|
|
39
43
|
|
|
@@ -44,9 +48,11 @@ are untouched, so a model answer can never license a "done" (`STEP_KINDS`,
|
|
|
44
48
|
|
|
45
49
|
The state leaves the machine. By default it goes to the session's own vendor,
|
|
46
50
|
through the session's own CLI and credential ([below](#the-providers-and-their-order)).
|
|
47
|
-
A third party
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
A third party reads it only as the `fallback` setting allows. The third parties
|
|
52
|
+
are the Jev carriers' hosts - `api.typesafe.ai`, `openrouter.ai`,
|
|
53
|
+
`api.cloudflare.com` or a user's `JEV_API_BASE_URL` - and the chat endpoint
|
|
54
|
+
(`OPENROUTER_URL`, `hooks/tezgah_judge.py::OPENROUTER_URL`).
|
|
55
|
+
For the triage the state is the screen's own text, so whoever
|
|
50
56
|
answers reads any personal data on it. For the docs fallback it is the query.
|
|
51
57
|
Nothing else goes: no session id, no
|
|
52
58
|
workspace path, no environment. The seam does not rewrite the state, because
|
|
@@ -89,18 +95,74 @@ background `learn` that armed taste starts.
|
|
|
89
95
|
|
|
90
96
|
`providers()` (`hooks/tezgah_judge.py::providers`) is the order `ask()` tries.
|
|
91
97
|
The next one is asked only when the previous failed. First comes the session's own
|
|
92
|
-
CLI (`tp.session_cli`, `hooks/tezgah_paths.py::session_cli`)
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
98
|
+
CLI (`tp.session_cli`, `hooks/tezgah_paths.py::session_cli`). The host's marker
|
|
99
|
+
names it, in this order:
|
|
100
|
+
|
|
101
|
+
| CLI | Marker | Set by |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| `omp` | `OMPCODE` | omp, which sets `CLAUDECODE` too |
|
|
104
|
+
| `claude` | `CLAUDECODE` | Claude Code |
|
|
105
|
+
| `opencode` | `OPENCODE` | opencode, in its own process |
|
|
106
|
+
| `cursor` (runs `cursor-agent`) | `CURSOR_AGENT`, `CURSOR_VERSION` | Cursor: the first in its shell tool, the second in its hooks |
|
|
107
|
+
| `codex` | `CODEX_THREAD_ID` | Codex in its shell tool; tezgah's Codex hook sets it from the session id |
|
|
108
|
+
|
|
109
|
+
A marked host whose binary is missing has no session CLI. No other CLI stands
|
|
110
|
+
in. `TEZGAH_JUDGE_CLI`, else `judge_cli` in `~/.config/tezgah/config.json`,
|
|
111
|
+
names one of the five instead. `auto`, the default, reads the marker. A named CLI
|
|
112
|
+
that is not installed is no session CLI either.
|
|
113
|
+
|
|
114
|
+
The CLI runs headless in an empty temp dir. Its flags are `SESSION_ARGV`
|
|
115
|
+
(`hooks/tezgah_judge.py::SESSION_ARGV`). The omp and claude runs drop tools,
|
|
116
|
+
rules, skills, extensions, MCP servers and settings. The other three have no flag
|
|
117
|
+
that drops their tools or takes a system prompt. They run with the least their
|
|
118
|
+
`--help` offers: opencode `--pure`, cursor-agent `--mode ask`, codex
|
|
119
|
+
`-s read-only --ephemeral`. Their prompt starts with the system prompt.
|
|
120
|
+
`opencode run` ran a shell command its prompt asked for in a probe on
|
|
121
|
+
2026-10-09. So it also gets an inline config that denies every tool
|
|
122
|
+
(`SESSION_ENV`, `hooks/tezgah_judge.py::SESSION_ENV`). With it, the same probe
|
|
123
|
+
wrote nothing. cursor-agent in ask mode refused the same shell call.
|
|
124
|
+
Measured 2026-10-07, a call cost $0.007 on claude and $0.015 on omp. Measured
|
|
125
|
+
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. The session's own
|
|
127
|
+
credential pays, OAuth subscription included. The answer and usage come from the
|
|
128
|
+
CLI's JSON output (`_session_request`, `hooks/tezgah_judge.py::_session_request`).
|
|
129
|
+
`_cli_answer` (`hooks/tezgah_judge.py::_cli_answer`) reads the last three. Only
|
|
130
|
+
omp and claude name the model. Then come the
|
|
131
|
+
third parties: first the chosen Jev carriers, each a real System One transport
|
|
132
|
+
(`_jev_call`, `hooks/tezgah_judge.py::_jev_call`), then the OpenRouter chat
|
|
133
|
+
fallback. `JEV_CARRIERS` (`hooks/tezgah_judge.py::JEV_CARRIERS`) names the six.
|
|
134
|
+
Two of them are not Jev. Clef and Clef-flash are Cloudflare's own decision
|
|
135
|
+
models. They take the same System One request. They answer in the same typed
|
|
136
|
+
shape, so a caller that needs a typed answer takes them like Jev.
|
|
137
|
+
|
|
138
|
+
| Carrier | Endpoint | Credential (env, else file, stripped) | Body |
|
|
139
|
+
|---|---|---|---|
|
|
140
|
+
| `typesafe` | `https://api.typesafe.ai/v1/systemone` | `TYPESAFE_API_KEY`, else `~/.config/typesafe/key`, else the active omp login (`~/.omp/agent/agent.db`, read-only; `typesafe_key`, `hooks/tezgah_paths.py::typesafe_key`) | `{state, model, questions}` |
|
|
141
|
+
| `jev-openrouter` | `https://openrouter.ai/api/v1/systemone` | `OPENROUTER_API_KEY`, else `~/.config/openrouter/key` | the same, a bare `jev-*` id sent as `typesafe/<id>` |
|
|
142
|
+
| `jev-cloudflare` | `https://api.cloudflare.com/client/v4/accounts/<id>/ai/run` | `JEV_CLOUDFLARE_API_TOKEN`, else `CLOUDFLARE_API_TOKEN`, else `~/.config/cloudflare/token`; the account id from `CLOUDFLARE_ACCOUNT_ID`, else `~/.config/cloudflare/account_id` | `{model: "typesafe/jev", input: {state, questions}}`; the reply is read bare or inside `result` |
|
|
143
|
+
| `jev-compatible` | `JEV_API_BASE_URL`, used verbatim | `JEV_API_KEY` | TypeSafe's, the model from `TEZGAH_JEV_MODEL` if set |
|
|
144
|
+
| `clef`, `clef-flash` | `https://api.cloudflare.com/client/v4/accounts/<id>/ai/run/@cf/cloudflare/<name>` | the Cloudflare token and account id above | `{model: "<name>", state, questions}`; the reply read like Cloudflare's above |
|
|
145
|
+
|
|
146
|
+
`JEV_API_BASE_URL` and `JEV_API_KEY` are the names the public jev-mcp server
|
|
147
|
+
reads, so one export serves both. `TEZGAH_JEV_PROVIDER` chooses the carrier,
|
|
148
|
+
else `JEV_PROVIDER`, else `jev` in `~/.config/tezgah/config.json`, else `auto`
|
|
149
|
+
(`jev_choice`, `hooks/tezgah_paths.py::jev_choice`). The values are `auto`,
|
|
150
|
+
`typesafe`, `openrouter`, `cloudflare`, `compatible`, `clef` and `clef-flash`.
|
|
151
|
+
`tezgah-setup --jev <value>` writes the config key
|
|
152
|
+
(`set_jev`, `bin/tezgah-setup::set_jev`). `auto`
|
|
153
|
+
asks every carrier that resolves, in the table's order (`jev_carriers`,
|
|
154
|
+
`hooks/tezgah_paths.py::jev_carriers`). The seam asks a named carrier without
|
|
155
|
+
its credential nothing, and nothing stands in for it: the call records
|
|
156
|
+
`no credential (jev=<value>)` as a failure. `tezgah-status --judge` prints the
|
|
157
|
+
choice and the carriers it resolves to (`jev_summary`,
|
|
158
|
+
`hooks/tezgah_judge.py::jev_summary`). A cost row prices the carrier that
|
|
159
|
+
answered (`input_price`, `hooks/tezgah_judge.py::input_price`). Per 1M input
|
|
160
|
+
tokens: $0.042 for Jev, $0.24 for Clef, $0.09 for Clef-flash. Output is free.
|
|
161
|
+
|
|
162
|
+
OpenRouter's chat fallback reads the OpenRouter key (`openrouter_key`,
|
|
163
|
+
`hooks/tezgah_judge.py::openrouter_key`).
|
|
102
164
|
It asks the table's cheap row unless `TEZGAH_JUDGE_MODEL` names another
|
|
103
|
-
(`fallback_model`, `hooks/tezgah_judge.py::fallback_model`). The key
|
|
165
|
+
(`fallback_model`, `hooks/tezgah_judge.py::fallback_model`). The key files are
|
|
104
166
|
read because a hook runs where `~/.zshenv` never did. A chat answer is asked for in prose
|
|
105
167
|
(`CHAT_SYSTEM`, `hooks/tezgah_judge.py::CHAT_SYSTEM`) and filtered to the ids
|
|
106
168
|
asked, a wrongly typed answer dropped (`_chat_answers`,
|
|
@@ -111,8 +173,8 @@ asked, a wrongly typed answer dropped (`_chat_answers`,
|
|
|
111
173
|
|
|
112
174
|
| `fallback` | Session CLI present | No session CLI |
|
|
113
175
|
|---|---|---|
|
|
114
|
-
| `vendor` (default) | the session CLI only; when it fails, no judgement |
|
|
115
|
-
| `any` | the session CLI, then
|
|
176
|
+
| `vendor` (default) | the session CLI only; when it fails, no judgement | the Jev carriers, then the chat fallback |
|
|
177
|
+
| `any` | the session CLI, then the Jev carriers, then the chat fallback | the Jev carriers, then the chat fallback |
|
|
116
178
|
| `none` | the session CLI only | nobody: `available()` is false |
|
|
117
179
|
|
|
118
180
|
Nothing that stands in is silent. A result carries `provider`, the `model`
|
|
@@ -124,7 +186,7 @@ hint's cost row and the router's `route` row also carry `judge=<provider>/<model
|
|
|
124
186
|
|
|
125
187
|
A caller that needs one provider in particular passes `only` (`named`,
|
|
126
188
|
`hooks/tezgah_judge.py::named`). `learn`'s decision has to come from a typed
|
|
127
|
-
model, so it passes `only=("
|
|
189
|
+
model, so it passes `only=("jev",)`, every Jev carrier that resolves. It gets `None` rather than another
|
|
128
190
|
provider's answer. The caller named the provider, so the `vendor` order does not
|
|
129
191
|
apply. `none` still keeps every third party out.
|
|
130
192
|
|
|
@@ -173,8 +235,9 @@ known (`note`, `hooks/tezgah_integrity.py::note`), counted by the row's kind
|
|
|
173
235
|
costs a fraction of a cent, so a cache is not worth its state file, and a naive
|
|
174
236
|
one would make two runs of the same command disagree. Beside the last-use
|
|
175
237
|
record, the seam keeps a failure marker (`DOWN_FOR`, 300 s, in `hooks/tezgah_judge.py`). A
|
|
176
|
-
|
|
177
|
-
provider, endpoint and credential down for five minutes.
|
|
238
|
+
401, 402, 403, 5xx or a session CLI's non-zero exit marks that
|
|
239
|
+
provider, endpoint and credential down for five minutes. A 403 is OpenRouter's
|
|
240
|
+
answer to a key past its limit. Until the marker
|
|
178
241
|
expires, that provider is skipped and sent no request. A dead key or an empty account then costs
|
|
179
242
|
one refusal per five minutes, not one per prompt. A rotated key gets a new
|
|
180
243
|
marker, so the seam asks it at once.
|
|
@@ -193,8 +256,11 @@ known (`note`, `hooks/tezgah_integrity.py::note`), counted by the row's kind
|
|
|
193
256
|
|
|
194
257
|
## Source of truth
|
|
195
258
|
|
|
196
|
-
- `hooks/tezgah_judge.py` — the seam:
|
|
197
|
-
the answer accessors, the retry class and the
|
|
259
|
+
- `hooks/tezgah_judge.py` — the seam: the carriers' endpoints and bodies,
|
|
260
|
+
`available()`, `ask()`, the answer accessors, the retry class and the
|
|
261
|
+
provider-down marker.
|
|
262
|
+
- `hooks/tezgah_paths.py` — the key channels (`typesafe_key`, `jev_carriers`)
|
|
263
|
+
and the carrier choice (`jev_choice`).
|
|
198
264
|
- `bin/tezgah-triage` — the snapshot triage and the per-state matrix; prints the
|
|
199
265
|
unit it selected, the characters it saved and the cost it paid.
|
|
200
266
|
- `bin/tezgah-docs` — the page router; its index match, then its one fallback
|