@r1z4x/tezgah 1.7.0 → 1.8.2
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 +61 -1
- package/MANIFEST +2 -0
- package/bin/tezgah-decisions +6 -14
- package/bin/tezgah-design +21 -5
- package/bin/tezgah-lessons +4 -1
- package/bin/tezgah-research +15 -7
- package/bin/tezgah-route +22 -7
- package/bin/tezgah-task +320 -75
- package/bin/tezgah-taste +8 -5
- package/docs/architecture.md +21 -7
- package/docs/environment.md +3 -1
- package/docs/evidence.md +9 -1
- package/docs/gate.md +24 -7
- package/docs/models.md +40 -12
- package/docs/operations.md +31 -1
- package/docs/research.md +3 -2
- package/docs/testing.md +18 -11
- package/hooks/projects-auto-init.py +29 -1
- package/hooks/tezgah_agents.py +24 -7
- package/hooks/tezgah_context.py +67 -35
- package/hooks/tezgah_gate.py +430 -64
- package/hooks/tezgah_integrity.py +108 -41
- package/hooks/tezgah_models.py +90 -30
- package/hooks/tezgah_paths.py +44 -6
- package/hooks/tezgah_policy.py +3 -3
- package/hooks/tezgah_research.py +9 -5
- package/hooks/tezgah_task.py +319 -67
- package/hooks/tezgah_taste.py +8 -4
- package/hosts/codex/hook.py +23 -3
- package/hosts/cursor/hook.py +11 -0
- package/hosts/opencode/plugins/tezgah.js +171 -4
- package/package.json +1 -1
- package/skills/plan-add/SKILL.md +23 -9
- package/skills/plan-add/render_table.py +8 -3
- package/skills/tezgah-contract/SKILL.md +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,63 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [1.8.2] - 2026-10-13
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **The v1.8.1 release builds.** A packaging test still pinned the serial suite step that 1.8.1 removed from CI. So the release workflow stopped before it built the artifact. `tests/impacted.py` no longer crashes on a deleted test module. Each neuter fall-through module runs in its own HOME, as the control does.
|
|
14
|
+
|
|
15
|
+
## [1.8.1] - 2026-10-13
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- **The neuter matrix runs in CI, and in minutes.** Each mutant first runs the one test that killed it last time, kept in `tests/neuter_killers.json`. A full search runs only when that test passes. A cached run took 240 s, against about an hour before. CI runs it on a push that changes a guarded file, and weekly. A session checks a new rule's mutant with `--only <name>`.
|
|
20
|
+
- **The suite is faster.** It starts the largest modules first. Slow tests share their expensive installs per class, and one test now covers each duplicated path. A change under `benchmarks/arm-bench/` runs that lab's module, not all 100.
|
|
21
|
+
- **CI runs the suite once per Python.** The serial `unittest discover` step is gone. The sharded job runs the same modules, and both caught the same failures. A push to `main` now takes about 11 minutes, not 30.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- **A checkout under the temp dir is not scratch.** The gate treated any path under the temp dir as scratch. So the task scope, the race rule and the main-checkout rule skipped it, and a plan worktree there let a write outside its scope pass. A file inside a git checkout now meets those rules, and a plain temp file stays scratch.
|
|
26
|
+
- **The test suite runs on CI runners.** A fixture had no git identity for the record commit that `close` now makes. The opencode capture test read only the SQLite ledger, so it missed the rows node 20 writes to the legacy file.
|
|
27
|
+
|
|
28
|
+
## [1.8.0] - 2026-10-13
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- **Typed worker output and a recall hop.** Tier workers and `tezgah-tester` now end their report with an `Output to orchestrator` block. It comes after any other shape the session asks for. A worker lists changed files, the commit sha and the `ESCALATE:` reason. For each check it gives the command, the exit code and the last lines of its output file. The tester gives pass, fail and skip counts and the first failure verbatim. A report that misses a field, or an `ESCALATE:`, gets one narrower re-brief of the same role. Only a second miss goes to `tezgah-frontier`, with the original brief and a fresh start. This ships as text only. An h05 spike on omp+tezgah spawned no subagent, so the arm-bench pilot could not measure it. No default changed.
|
|
33
|
+
- **A route joins the workers it actually started.** `tezgah-route --report` used to drop a route when the session spawned unrouted workers of its type. It also dropped a route whose one task call started several workers. A route now owns the first task call of its type after it. One failed first check in that call fails the route. A task call more than 30 minutes after its route stays unjoined. Two routes before one shared task call stay unjoined, since the report cannot tell whose worker is whose. On this machine 6 of the 10 real omp routes now join, up from 1.
|
|
34
|
+
- **On Claude Code, `tezgah-task` now reads the session id.** Claude's shells now carry `TEZGAH_SESSION`, as omp's do. So the review guard counts only this session's slice records. A bare name in `slice board`, `slice done` or `slice resume` now matches only this session's or unowned records.
|
|
35
|
+
- **A key passed to a script as an environment prefix is no longer refused.** `API_KEY="$(cat key)" python3 tool.py > run.log` now passes, since the key reaches only the program's environment. Only a script runner given a script file qualifies. A trace or verbose option, a startup or trace variable, `/proc/*/environ` or a function defined on the line holds it. A later printer that reads a key assigned earlier on the line is now refused too. The opencode plugin answers the same.
|
|
36
|
+
- **One `.tezgah` per repository, in its main checkout.** A session or CLI in a linked worktree now reads and writes the main checkout's `.tezgah`. That covers the session context, research lines, decisions, lessons, taste and the design contract. Before, `git worktree remove` deleted research lines and lessons written there. A worktree's session also never saw the lessons or open plans. `tezgah-research import` moves a line left in an old worktree workspace.
|
|
37
|
+
- **Any `plan/NNN-*` branch answers to plan NNN.** A plan's slice worktrees on their own `plan/NNN-*` branches now meet that plan's phase and allowlist. When every phased plan is held by a worktree, the main checkout has no active task. It used to take the first held plan's scope.
|
|
38
|
+
- **`tezgah-task close NNN done` refuses an open Acceptance item.** It names each unticked item that carries no `UNMET: <reason>`. `--resolved` does not lift it, and `discarded` still closes freely. Close also records the `landed:` commit once main holds the work, and commits the moved record in the private `.tezgah` repository.
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- **Claude Code, Codex and Cursor routes can join their workers.** On Claude and Codex, SubagentStart writes a `subagent_start` row. It names the child's id and type, and it goes into the parent's ledger. Claude's SessionStart exports `TEZGAH_SESSION` through `CLAUDE_ENV_FILE`. On Codex, `tezgah-route` writes under the shell's `CODEX_THREAD_ID`. On Cursor it writes under `CURSOR_CONVERSATION_ID`. Cursor's subagentStart opens the child's ledger with a row that names the parent conversation. The report counts that link only after the child writes a row of its own. `cursor-agent` fires no subagentStart, so its routes stay unjoined, and so do dsh routes.
|
|
43
|
+
- **Two plans at once, one worktree each.** A linked worktree on a plan's own `branch:` now answers to that plan's phase, allowlist and checkpoint. This holds for a session inside the worktree and for a write into it from anywhere else. `tezgah-task start` keeps another plan's phase only when both plans run in their own worktrees on disjoint scopes. Otherwise it clears the others as before. The main checkout reads the first phased plan no worktree holds. A worktree on any other branch reads the main checkout's task. `allow` and `phase --any-path` refuse a scope that a held plan could share. The CLI takes a pending checkpoint from the plan's own worktree, and its refusal names `git -C <worktree>`. `tick`, `note`, `spike`, `review` and `close` run from any worktree. `review` records the plan worktree's HEAD, and the verification steps print `git -C <worktree> diff`.
|
|
44
|
+
- **`tezgah-task spike NNN "<answer>"`.** It writes `spike_recorded:` on an open plan, active or not. `phase implementation` then passes, with no stop, hand edit and restart.
|
|
45
|
+
- **A forced-delegation arm and 20 harder tasks for arm-bench.** `omp+tezgah-delegate` hands every edit to the worker `tezgah-route` names, so a run exercises the role text. One paid arming run verified it. `block.py` takes task globs and `--dry-run`. Tasks x01-x20 cover hidden invariants, sites a grep misses, spec edge clauses and long sweeps. On deepseek-v4-flash they passed 99 of 100 runs, so they stay as a regression corpus.
|
|
46
|
+
- **The Stop rule holds a done claim over an in-progress todo item.** The new `open todo` class names the open items. Finish them, mark them done, or type each one with its blocker. A step a running slice works on passes. A Cursor `merge: true` list is partial, so the class skips it.
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
- **A route row never lands in another host's ledger.** A host started from another host's agent shell inherits that host's session id. `tezgah-route` now writes no row when its three session variables name different sessions. Such a route counts as unjoined.
|
|
51
|
+
- **A Codex subagent's repeat limits count only its own attempts**, as on Claude. The root's next try no longer fails for the subagent's attempts.
|
|
52
|
+
- **A shell line is read target by target.** Each redirect and `tee` target meets the phase, allowlist and checkpoint of the plan it lands under. Some lines also meet the task where the session runs. That holds for a line with more write shapes than read targets. It also holds for a positional writer such as `cp` or `sed -i`. So `tee <plan worktree>/a README.md` no longer escapes a reading phase. Two redirects into one plan worktree meet only that plan.
|
|
53
|
+
- **Two edits cannot give a plan a phase.** The gate judged an edit to an open plan by its new string alone. So `xphase: implementation` and then `xphase` -> `phase` both passed. It now judges the plan's text as it will read after the write.
|
|
54
|
+
- **The plan record is the record in any case and from any checkout.** The gate refuses a write to `.TEZGAH/PLANS/open/<plan>.md` as a record write. It refuses a write to any phased record from a session in a linked worktree too.
|
|
55
|
+
- **A slice keeps its own plan.** Its tag comes from its `plan/NNN` branch first. Next comes `plan NNN` in its brief or shared context, then its earlier tag. A re-open with no brief keeps the tag.
|
|
56
|
+
- **`tezgah-task close` run inside a landed worktree keeps that worktree.** The session's own directory is no longer removed from under it.
|
|
57
|
+
- **arm-bench's arming proof reads the evidence store.** `session_rows` and `stop_classes` read the old JSONL ledger, which the hooks no longer write. So every arm recorded 0 and `{}`, armed or not. Both now read the SQLite store, as `counters` does. A fixture copy also leaves out stale `__pycache__` and bytecode.
|
|
58
|
+
- **The task rule holds repository paths only.** Some writes land under no git checkout and no configured root, such as a user's `~/.config/runpod/key`. Those no longer meet the active plan's phase, allowlist or checkpoint. The secret rule and every other rule still read them. A target in another repository is still refused. Any doubt holds the write. That covers a line that moves the directory or reads `$PWD`, and a line that makes a link. It also covers a target whose parent does not exist yet and a file with a second hard link. Host agent and instruction files and shell rc files stay held too, in any letter case.
|
|
59
|
+
- **`tezgah-task` exits cleanly when its reader leaves early.** `tezgah-task stop | head -1` prints no traceback. It finishes its work and keeps its own exit code, buffered or under `PYTHONUNBUFFERED=1`.
|
|
60
|
+
- **Nothing inside `.tezgah` treats it as a project.** The workspace keeps its own `.git`, so git named `.tezgah` itself as the top level there. Session start then set up `.tezgah/.tezgah`, agents and a graph index inside it. The plans table renderer wrote `.tezgah/.tezgah/plans/README.md`. Every resolver now maps a path inside `.tezgah` to the repository around it.
|
|
61
|
+
- **Plan records survive concurrent writers.** `tick`, `note`, field writes and `close` hold a lock on the plans directory and replace the file atomically. A reader saw a torn record in 29 of 40 rounds before. `tick` now finds its item under the lock.
|
|
62
|
+
- **The slice journal closes a landed slice whose worktree `close` removed.** `reconcile` no longer names the main checkout's HEAD as a removed worktree's last commit.
|
|
63
|
+
- **`close` deletes a squash-merged plan branch.** It used `git branch -d`, which refuses a squash merge, and dropped the error. It now deletes with `-D` only after proving the branch landed, and names a branch it keeps.
|
|
64
|
+
- **Two worktrees can commit to the private `.tezgah` repository at once.** In a fixture, a busy `index.lock` failed 11 of 19 close commits. Close still exited 0. Commits there now wait out a short lock, and close exits 1 when its record commit fails.
|
|
65
|
+
|
|
9
66
|
## [1.7.0] - 2026-10-12
|
|
10
67
|
|
|
11
68
|
### Added
|
|
@@ -1104,7 +1161,10 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
1104
1161
|
the copy could not find `hooks/` and died on import. It now links into the
|
|
1105
1162
|
installed tree, and also installs the command as `tezgah`.
|
|
1106
1163
|
|
|
1107
|
-
[Unreleased]: https://github.com/r1z4x/tezgah/compare/v1.
|
|
1164
|
+
[Unreleased]: https://github.com/r1z4x/tezgah/compare/v1.8.2...HEAD
|
|
1165
|
+
[1.8.2]: https://github.com/r1z4x/tezgah/releases/tag/v1.8.2
|
|
1166
|
+
[1.8.1]: https://github.com/r1z4x/tezgah/releases/tag/v1.8.1
|
|
1167
|
+
[1.8.0]: https://github.com/r1z4x/tezgah/releases/tag/v1.8.0
|
|
1108
1168
|
[1.7.0]: https://github.com/r1z4x/tezgah/releases/tag/v1.7.0
|
|
1109
1169
|
[1.6.0]: https://github.com/r1z4x/tezgah/releases/tag/v1.6.0
|
|
1110
1170
|
[1.5.1]: https://github.com/r1z4x/tezgah/releases/tag/v1.5.1
|
package/MANIFEST
CHANGED
package/bin/tezgah-decisions
CHANGED
|
@@ -46,7 +46,6 @@ Exit codes: 0 every record is whole (or there are none), 1 a record was refused
|
|
|
46
46
|
import json
|
|
47
47
|
import os
|
|
48
48
|
import re
|
|
49
|
-
import subprocess
|
|
50
49
|
import sys
|
|
51
50
|
|
|
52
51
|
HERE = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
|
@@ -67,19 +66,12 @@ RECORD = re.compile(r"^(\d{3})-[^/]+\.md$")
|
|
|
67
66
|
|
|
68
67
|
|
|
69
68
|
def repo_root(path):
|
|
70
|
-
"""The repository this run is about:
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
out = subprocess.run(["git", "-C", path, "rev-parse", "--show-toplevel"],
|
|
77
|
-
capture_output=True, text=True)
|
|
78
|
-
if out.returncode == 0 and out.stdout.strip():
|
|
79
|
-
return out.stdout.strip()
|
|
80
|
-
except OSError:
|
|
81
|
-
pass
|
|
82
|
-
return os.path.realpath(path)
|
|
69
|
+
"""The repository this run is about: its main checkout, the one whose
|
|
70
|
+
`.tezgah` holds the records from a linked worktree or from inside `.tezgah`
|
|
71
|
+
too (`tezgah_task.main_repo`, the root bin/tezgah-task keeps plans under),
|
|
72
|
+
else the directory the CLI was called in - a decision recorded under a tree
|
|
73
|
+
git does not know is still a decision."""
|
|
74
|
+
return tt.main_repo(path) or os.path.realpath(path)
|
|
83
75
|
|
|
84
76
|
|
|
85
77
|
def decision_record(text):
|
package/bin/tezgah-design
CHANGED
|
@@ -30,6 +30,10 @@ import os
|
|
|
30
30
|
import re
|
|
31
31
|
import sys
|
|
32
32
|
|
|
33
|
+
HERE = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
|
34
|
+
sys.path.insert(0, os.path.join(HERE, "hooks"))
|
|
35
|
+
from tezgah_paths import linked_main, project_top # noqa: E402
|
|
36
|
+
|
|
33
37
|
# The state set every interactive control and every data view owes: seven per
|
|
34
38
|
# control, eight per data view, thirteen distinct states - `loading` and `error`
|
|
35
39
|
# are the two the halves share, and the shipped lists name them twice rather than
|
|
@@ -818,10 +822,14 @@ def _print_json(payload):
|
|
|
818
822
|
|
|
819
823
|
|
|
820
824
|
def derive_cli(opts):
|
|
821
|
-
root = os.path.abspath(opts.get("repo") or "")
|
|
825
|
+
root = project_top(os.path.abspath(opts.get("repo") or ""))
|
|
822
826
|
if not os.path.isdir(root):
|
|
823
827
|
raise Usage("--repo must name a directory")
|
|
824
|
-
|
|
828
|
+
# the tokens are this checkout's; the contract goes to the repository's one
|
|
829
|
+
# workspace, the main checkout's `.tezgah` (plan 097), which a worktree's
|
|
830
|
+
# removal does not take with it
|
|
831
|
+
ws = linked_main(root) or root
|
|
832
|
+
out = opts.get("out") or os.path.join(ws, ".tezgah", "design-contract.md")
|
|
825
833
|
if os.path.exists(out):
|
|
826
834
|
# A contract that exists is the repository's own decision: it carries
|
|
827
835
|
# the component inventory someone filled in, so overwriting it would
|
|
@@ -829,7 +837,7 @@ def derive_cli(opts):
|
|
|
829
837
|
# deliberate `rm` first.
|
|
830
838
|
raise Usage("the contract already exists at %s: a contract is edited, "
|
|
831
839
|
"not overwritten - remove it first or pass --out elsewhere"
|
|
832
|
-
% os.path.
|
|
840
|
+
% os.path.abspath(out))
|
|
833
841
|
tokens, source, sources = derive(root)
|
|
834
842
|
if tokens is None:
|
|
835
843
|
if opts.get("json"):
|
|
@@ -850,7 +858,7 @@ def derive_cli(opts):
|
|
|
850
858
|
fh.write(text)
|
|
851
859
|
except OSError as exc:
|
|
852
860
|
raise Usage("cannot write %s: %s" % (out, exc))
|
|
853
|
-
shown = os.path.relpath(out,
|
|
861
|
+
shown = os.path.relpath(out, ws)
|
|
854
862
|
if opts.get("json"):
|
|
855
863
|
_print_json({"ok": True, "out": shown, "source": source,
|
|
856
864
|
"colors": len(data["tokens"]["colors"]),
|
|
@@ -858,9 +866,11 @@ def derive_cli(opts):
|
|
|
858
866
|
"type_scale": data["tokens"]["type_scale"],
|
|
859
867
|
"components": len(data["components"])})
|
|
860
868
|
else:
|
|
869
|
+
# absolute: from a worktree the contract is the main checkout's, which a
|
|
870
|
+
# `.tezgah/...` read from the worktree's cwd does not name (review N1)
|
|
861
871
|
sys.stdout.write(
|
|
862
872
|
"wrote %s (source: %s, %d colour roles, %s, %d type steps)\n"
|
|
863
|
-
% (
|
|
873
|
+
% (os.path.abspath(out), source, len(data["tokens"]["colors"]),
|
|
864
874
|
"%spx unit" % data["tokens"]["space_unit"]
|
|
865
875
|
if data["tokens"]["space_unit"] else "no spacing unit found",
|
|
866
876
|
len(data["tokens"]["type_scale"])))
|
|
@@ -876,6 +886,12 @@ def check_cli(opts):
|
|
|
876
886
|
measured = opts.get("measured")
|
|
877
887
|
if not contract or not measured:
|
|
878
888
|
raise Usage("check wants --contract FILE and --measured FILE|-")
|
|
889
|
+
if (not os.path.isabs(contract) and not os.path.exists(contract)
|
|
890
|
+
and contract.replace(os.sep, "/").startswith(".tezgah/")):
|
|
891
|
+
# a worktree's `.tezgah/...` is the main checkout's workspace (plan 097,
|
|
892
|
+
# review N1): the path the Stop refusal and the reviewer prompt name
|
|
893
|
+
cwd = os.getcwd()
|
|
894
|
+
contract = os.path.join(linked_main(cwd) or cwd, contract)
|
|
879
895
|
data, reason = read_contract(contract)
|
|
880
896
|
if data is None:
|
|
881
897
|
raise Usage(reason)
|
package/bin/tezgah-lessons
CHANGED
|
@@ -112,7 +112,10 @@ def merged(lines, groups):
|
|
|
112
112
|
|
|
113
113
|
def main(argv):
|
|
114
114
|
ap = argparse.ArgumentParser(prog="tezgah-lessons", description=__doc__.split("\n")[0])
|
|
115
|
-
|
|
115
|
+
# a linked worktree's lessons are the main checkout's (plan 097)
|
|
116
|
+
from tezgah_paths import linked_main
|
|
117
|
+
here = tc.repo_root(os.getcwd())
|
|
118
|
+
ap.add_argument("--root", default=linked_main(here) or here)
|
|
116
119
|
ap.add_argument("--fixture")
|
|
117
120
|
args = ap.parse_args(argv)
|
|
118
121
|
lines = tezgah_lessons.lines(args.root)
|
package/bin/tezgah-research
CHANGED
|
@@ -131,20 +131,27 @@ import tezgah_product as tp_product # noqa: E402
|
|
|
131
131
|
import tezgah_research as tr # noqa: E402
|
|
132
132
|
|
|
133
133
|
|
|
134
|
-
def
|
|
134
|
+
def checkout_root(path):
|
|
135
|
+
"""The checkout `path` is in: git's top level, the project around it inside
|
|
136
|
+
`.tezgah`, else the directory itself."""
|
|
135
137
|
try:
|
|
136
138
|
out = subprocess.run(["git", "-C", path, "rev-parse", "--show-toplevel"],
|
|
137
139
|
capture_output=True, text=True)
|
|
138
140
|
if out.returncode == 0 and out.stdout.strip():
|
|
139
|
-
|
|
140
|
-
# inside `.tezgah` git answers with the private repository; the line
|
|
141
|
-
# belongs to the project around it
|
|
142
|
-
return os.path.dirname(top) if os.path.basename(top) == tp.WORKSPACE else top
|
|
141
|
+
return tp.project_top(out.stdout.strip())
|
|
143
142
|
except OSError:
|
|
144
143
|
pass
|
|
145
144
|
return os.path.realpath(path)
|
|
146
145
|
|
|
147
146
|
|
|
147
|
+
def repo_root(path):
|
|
148
|
+
"""The checkout whose `.tezgah` holds the lines: the main checkout, from a
|
|
149
|
+
linked worktree too, as for bin/tezgah-task - a line written into a
|
|
150
|
+
worktree's own `.tezgah` went with `git worktree remove` (plan 097)."""
|
|
151
|
+
top = checkout_root(path)
|
|
152
|
+
return tp.linked_main(top) or top
|
|
153
|
+
|
|
154
|
+
|
|
148
155
|
def misuse(message):
|
|
149
156
|
"""Misuse exits 2, so a caller can tell it from a line that fails the checks."""
|
|
150
157
|
print("tezgah-research: %s" % message, file=sys.stderr)
|
|
@@ -355,7 +362,7 @@ def cmd_check(args):
|
|
|
355
362
|
report = {}
|
|
356
363
|
if known:
|
|
357
364
|
report = tr.check(repo, rest[0] if rest else None, strict=strict,
|
|
358
|
-
all_lines=all_lines)
|
|
365
|
+
all_lines=all_lines, code=checkout_root(os.getcwd()))
|
|
359
366
|
for slug, row in report.items():
|
|
360
367
|
for err in row["errors"]:
|
|
361
368
|
problems += 1
|
|
@@ -1004,7 +1011,8 @@ def cmd_import(args):
|
|
|
1004
1011
|
def cmd_all(args):
|
|
1005
1012
|
if args:
|
|
1006
1013
|
return misuse("--all takes no arguments")
|
|
1007
|
-
|
|
1014
|
+
# every checkout's own `.tezgah`, marking the one this runs in
|
|
1015
|
+
for row in tr.across(checkout_root(os.getcwd())):
|
|
1008
1016
|
print(row)
|
|
1009
1017
|
return 0
|
|
1010
1018
|
|
package/bin/tezgah-route
CHANGED
|
@@ -26,7 +26,10 @@ model through TypeSafe, OpenRouter System One, Cloudflare Workers AI or a
|
|
|
26
26
|
compatible endpoint (TEZGAH_JEV_PROVIDER picks one), else the OpenRouter chat
|
|
27
27
|
fallback - so write it in English, the language the judge reads best.
|
|
28
28
|
|
|
29
|
-
Every routed call writes one `route` row to the session's ledger
|
|
29
|
+
Every routed call writes one `route` row to the session's ledger, named by
|
|
30
|
+
TEZGAH_SESSION (omp, opencode, Claude), CODEX_THREAD_ID (Codex) or
|
|
31
|
+
CURSOR_CONVERSATION_ID (Cursor); when two of them name different sessions (a host
|
|
32
|
+
started from another host's shell) it writes none. The row holds the
|
|
30
33
|
tier, agent, what set the tier (`via`: rule|jev|phase|default), the static
|
|
31
34
|
table's tier when --phase was given, the worker's model, and `override` (user: an
|
|
32
35
|
omp entry the user owns; mode: no entry, so the session model). `--report` joins
|
|
@@ -36,8 +39,9 @@ order only where no spawn could be swapped - and counts a route it cannot prove
|
|
|
36
39
|
as `unjoined`. That outcome is what the V2-as-default
|
|
37
40
|
decision waits for (docs/models.md).
|
|
38
41
|
|
|
39
|
-
A
|
|
40
|
-
|
|
42
|
+
A report missing an output field, or an `ESCALATE:`, gets one narrower re-brief
|
|
43
|
+
of the same role naming that field or judgement; only a second miss is restarted
|
|
44
|
+
on tezgah-frontier with the original brief, never continued. Exit codes: 0 routed / current, 1 the
|
|
41
45
|
table is stale or flagged (`--check`) or the refresh failed, 2 misuse.
|
|
42
46
|
"""
|
|
43
47
|
import argparse
|
|
@@ -58,8 +62,10 @@ def main(argv):
|
|
|
58
62
|
parser = argparse.ArgumentParser(
|
|
59
63
|
prog="tezgah-route",
|
|
60
64
|
description="Name the tier worker a delegated task goes to, and keep the model table.",
|
|
61
|
-
epilog="A
|
|
62
|
-
"the same
|
|
65
|
+
epilog="A report missing an output field, or an `ESCALATE:`, gets one "
|
|
66
|
+
"narrower re-brief of the same role naming that field or judgement; "
|
|
67
|
+
"only a second miss is restarted on tezgah-frontier with the original "
|
|
68
|
+
"brief, never continued.")
|
|
63
69
|
parser.add_argument("brief", nargs="*", help="the delegation brief, in English")
|
|
64
70
|
parser.add_argument("--phase", choices=sorted(tm.PHASE_TIER),
|
|
65
71
|
help="the static table's row when no judgement is available")
|
|
@@ -154,6 +160,16 @@ def main(argv):
|
|
|
154
160
|
ask = tezgah_judge.ask if tezgah_judge.available() else None
|
|
155
161
|
out = tm.route(brief, args.phase, ask)
|
|
156
162
|
judged = out.pop("judged")
|
|
163
|
+
# The session the row goes under: omp, opencode and Claude (CLAUDE_ENV_FILE)
|
|
164
|
+
# export TEZGAH_SESSION, a Codex shell carries CODEX_THREAD_ID (the root
|
|
165
|
+
# thread its hooks' session_id names), a Cursor agent shell
|
|
166
|
+
# CURSOR_CONVERSATION_ID (its hooks' conversation_id). A host started from
|
|
167
|
+
# another host's agent shell inherits that host's id and markers alike, so
|
|
168
|
+
# two different ids name no host: no row, and the route is never written
|
|
169
|
+
# into another host's ledger.
|
|
170
|
+
ids = {os.environ.get(v) for v in ("TEZGAH_SESSION", "CODEX_THREAD_ID",
|
|
171
|
+
"CURSOR_CONVERSATION_ID")} - {None, ""}
|
|
172
|
+
session = ids.pop() if len(ids) == 1 else None
|
|
157
173
|
if judged:
|
|
158
174
|
usage = judged.get("usage")
|
|
159
175
|
usage = usage if isinstance(usage, dict) else {}
|
|
@@ -161,8 +177,7 @@ def main(argv):
|
|
|
161
177
|
judged.get("model", tezgah_judge.MODEL), out["tier"],
|
|
162
178
|
usage.get("input_tokens"), usage.get("output_tokens"),
|
|
163
179
|
judged.get("latency_ms"))
|
|
164
|
-
ti.note(
|
|
165
|
-
session = os.environ.get("TEZGAH_SESSION")
|
|
180
|
+
ti.note(session, "judge", detail)
|
|
166
181
|
if session: # the omp read in worker_model costs a process; skip it with no ledger
|
|
167
182
|
ti.note(session, "route", tm.route_detail(out, args.phase,
|
|
168
183
|
*tm.worker_model(out["agent"])))
|