@r1z4x/tezgah 1.0.0 → 1.2.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 +92 -1
- package/MANIFEST +10 -0
- package/bin/tezgah-agents +10 -1
- package/bin/tezgah-docs +1 -1
- package/bin/tezgah-doctor +11 -0
- package/bin/tezgah-research +31 -14
- package/bin/tezgah-setup +154 -29
- package/bin/tezgah-skill +59 -0
- package/bin/tezgah-skill.py +59 -0
- package/bin/tezgah-status +62 -1
- package/bin/tezgah-taste +504 -5
- package/bin/tezgah-triage +5 -3
- package/docs/README.md +1 -0
- package/docs/architecture.md +5 -2
- package/docs/citations-baseline.json +1 -1
- package/docs/clarity-baseline.json +9 -9
- package/docs/contract.md +68 -26
- package/docs/environment.md +6 -3
- package/docs/evidence.md +7 -0
- package/docs/gate.md +13 -7
- package/docs/glossary.md +1 -1
- package/docs/hosts.md +48 -1
- package/docs/index.json +34 -3
- package/docs/judge.md +75 -60
- package/docs/models.md +14 -8
- package/docs/operations.md +80 -25
- package/docs/orca.md +126 -0
- package/docs/research.md +38 -3
- package/docs/skills.md +74 -0
- package/docs/status-line.md +3 -1
- package/docs/testing.md +33 -21
- package/docs/vision.md +12 -8
- package/hooks/projects-posttooluse.py +7 -0
- package/hooks/projects-pretooluse.py +17 -5
- package/hooks/tezgah_agents.py +135 -13
- package/hooks/tezgah_components.py +3 -0
- package/hooks/tezgah_context.py +84 -22
- package/hooks/tezgah_gate.py +133 -17
- package/hooks/tezgah_integrity.py +60 -13
- package/hooks/tezgah_judge.py +288 -66
- package/hooks/tezgah_models.py +41 -5
- package/hooks/tezgah_orca.py +144 -0
- package/hooks/tezgah_paths.py +40 -6
- package/hooks/tezgah_policy.py +109 -35
- package/hooks/tezgah_research.py +104 -8
- package/hooks/tezgah_skill_pick.py +459 -8
- package/hooks/tezgah_stack.py +382 -0
- package/hooks/tezgah_stopspec.py +2 -2
- package/hooks/tezgah_taste.py +35 -9
- package/hooks/tezgah_taste_ledger.py +628 -0
- package/hosts/codex/hook.py +12 -1
- package/hosts/cursor/hook.py +12 -4
- package/hosts/omp/hook.py +18 -6
- package/hosts/omp/tezgah-hook.ts.in +20 -7
- package/hosts/opencode/plugins/tezgah.js +28 -15
- package/output-styles/tezgah.md +34 -19
- package/package.json +1 -1
- package/skills/i-have-adhd/SKILL.md +2 -2
- package/skills/research/SKILL.md +60 -17
- package/skills/tezgah-contract/SKILL.md +55 -13
package/docs/judge.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Judge: the judgement seam, its five callers and its switches
|
|
2
2
|
|
|
3
|
-
The judgement seam is `hooks/tezgah_judge.py
|
|
4
|
-
|
|
5
|
-
an agent to read. Read this page when you are about to call one of its four
|
|
3
|
+
The judgement seam is `hooks/tezgah_judge.py`. It asks the session's own model,
|
|
4
|
+
through the host's CLI, for a batched structured judgement over a state tezgah
|
|
5
|
+
would otherwise pay an agent to read. Read this page when you are about to call one of its four
|
|
6
6
|
callers, when you need to say what leaves the machine, or when you have to tell a
|
|
7
7
|
user how to switch it off. It is an on-demand capability, not a rule: no paragraph
|
|
8
8
|
of it is injected into a session, and a session that never asks pays one clause in
|
|
@@ -11,7 +11,7 @@ the kill-switch paragraph and nothing else (`CORE`,
|
|
|
11
11
|
|
|
12
12
|
## What it is
|
|
13
13
|
|
|
14
|
-
One seam, five callers, one
|
|
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
17
|
down (`_request`, `hooks/tezgah_judge.py::_request`). One endpoint and one key path
|
|
@@ -21,31 +21,34 @@ are module constants (`URL`, `hooks/tezgah_judge.py::URL`; `KEY_FILE`,
|
|
|
21
21
|
endpoint's host, so a `Location` cannot carry the bearer (`guarded_opener`,
|
|
22
22
|
`hooks/tezgah_paths.py`, the one copy `bin/consult` and `bin/codegen` use too). A
|
|
23
23
|
transient failure - a timeout, a connection error, a 5xx - gets exactly one more
|
|
24
|
-
attempt
|
|
25
|
-
`hooks/tezgah_judge.py::_transient`). A hook caller asks for one
|
|
26
|
-
wall-clock `deadline` instead (`ask`, `hooks/tezgah_judge.py::ask`).
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
Every caller reads an answer through the same
|
|
24
|
+
attempt. A 4xx, a session CLI's non-zero exit and a malformed reply never do
|
|
25
|
+
(`_transient`, `hooks/tezgah_judge.py::_transient`). A hook caller asks for one
|
|
26
|
+
attempt and a wall-clock `deadline` instead (`ask`, `hooks/tezgah_judge.py::ask`).
|
|
27
|
+
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`, `TEZGAH_OPENROUTER_URL`
|
|
29
|
+
and `TEZGAH_OMP_BIN`/`TEZGAH_CLAUDE_BIN` are the test seams.
|
|
30
|
+
|
|
31
|
+
Every caller reads an answer through the same accessors rather than reaching
|
|
32
32
|
into the raw reply, so a Choice and a Noul are read one way for all five
|
|
33
33
|
(`choice`, `hooks/tezgah_judge.py::choice`; `noul`,
|
|
34
|
-
`hooks/tezgah_judge.py::noul`).
|
|
35
|
-
|
|
34
|
+
`hooks/tezgah_judge.py::noul`). A `text` question is the one shape that asks
|
|
35
|
+
for prose. `bin/tezgah-taste` asks it for a learning's readable line, and only
|
|
36
|
+
a generative provider answers one. TypeSafe is never sent one (`text`,
|
|
37
|
+
`hooks/tezgah_judge.py::text`). All three are total: a missing or
|
|
38
|
+
wrongly-typed answer is a `None`, never an exception.
|
|
36
39
|
|
|
37
40
|
A judgement is an aid, never the claim. It ranks units or names a page; the agent
|
|
38
41
|
still reads the selected refs and owns the finding, and the ledger's step kinds
|
|
39
42
|
are untouched, so a model answer can never license a "done" (`STEP_KINDS`,
|
|
40
43
|
`hooks/tezgah_integrity.py::STEP_KINDS`).
|
|
41
44
|
|
|
42
|
-
The state leaves the machine.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
The state leaves the machine. By default it goes to the session's own vendor,
|
|
46
|
+
through the session's own CLI and credential ([below](#the-providers-and-their-order)).
|
|
47
|
+
A third party - `api.typesafe.ai` or `openrouter.ai` (`OPENROUTER_URL`,
|
|
48
|
+
`hooks/tezgah_judge.py::OPENROUTER_URL`) - reads it only as the `fallback`
|
|
49
|
+
setting allows. For the triage the state is the screen's own text, so whoever
|
|
50
|
+
answers reads any personal data on it. For the docs fallback it is the query.
|
|
51
|
+
Nothing else goes: no session id, no
|
|
49
52
|
workspace path, no environment. The seam does not rewrite the state, because
|
|
50
53
|
sending it is the point. Two callers rewrite what they send. `bin/tezgah-route`
|
|
51
54
|
sends its brief through the ledger's own redactor (`redact`,
|
|
@@ -69,7 +72,7 @@ why its redaction is not optional. The switches below are the off buttons.
|
|
|
69
72
|
| `bin/tezgah-docs` | the docs page fallback: only when the keyword index placed nothing, one Choice over the pages with `none` offered (`judge_pick`, `bin/tezgah-docs::judge_pick`; the question wording is `ASK`, `bin/tezgah-docs::ASK`) | reads as no judgement; with none (no credential, `judge-off`, `docs-judge-off`, or a call that failed or came back without one of its options) the pages are ranked by shared words instead (`ranked`, BM25 in `hooks/tezgah_rank.py` over each page's title and answers and their Turkish phrasings) and the top three printed; a query sharing no word with any page, or a judged `none`, exits 1. With the opt-in embedding feature on, the ranking is fused with a static embedding (`hooks/tezgah_embed.py`), which places every page, so only a judged `none` exits 1 |
|
|
70
73
|
| `hooks/tezgah_skill_pick.py` | the prompt-path skill hint: a Choice over the roster skills plus one Noul (`judge`, `hooks/tezgah_skill_pick.py::judge`, with the criteria cut from each skill's own clauses, `clause`, `hooks/tezgah_skill_pick.py::clause`), behind a threshold (`GATE`, `hooks/tezgah_skill_pick.py::GATE`), one attempt and a 4 s wall-clock deadline (`ASK_DEADLINE`, `hooks/tezgah_skill_pick.py::ASK_DEADLINE`), the prompt redacted and cut at 2,000 characters | returns `""`; the turn loses the hint |
|
|
71
74
|
| `bin/tezgah-route` | the tier router: after the deterministic overrides, one Choice over the three tiers for a delegation brief (`route`, `hooks/tezgah_models.py::route`; `TIER_QUESTION`, `hooks/tezgah_models.py::TIER_QUESTION`) - see [models](models.md) | the static phase table, else the middle tier |
|
|
72
|
-
| `bin/tezgah-taste` | the coding-taste measurement: `measure` and `rate` label the prompts a user sent after a writing turn, one Choice per prompt batched into one request, each prompt redacted first (`classify`, `bin/tezgah-taste::classify`) | with no credential or `judge-off` it exits 2 before any request
|
|
75
|
+
| `bin/tezgah-taste` | the coding-taste measurement and learning: `measure` and `rate` label the prompts a user sent after a writing turn, one Choice per prompt batched into one request, each prompt redacted first (`classify`, `bin/tezgah-taste::classify`). `learn` asks one typed decision per signal and requires TypeSafe (`decide`, `bin/tezgah-taste::decide`). For a learning that turns active it asks one `text` question of a generative provider (`write_line`, `bin/tezgah-taste::write_line`) | with no credential or `judge-off` it exits 2 before any request. A failed call leaves its prompts unlabelled. A decision another provider gave is recorded `unverified` and never applied. A missing line keeps the user's own words |
|
|
73
76
|
|
|
74
77
|
The third caller is the one no shell row can see: it runs on the prompt path,
|
|
75
78
|
caches one answer per `(session, prompt)` (`_remember`,
|
|
@@ -77,37 +80,48 @@ caches one answer per `(session, prompt)` (`_remember`,
|
|
|
77
80
|
`hooks/tezgah_skill_pick.py::suggest`), and asks at all only when its own marker is
|
|
78
81
|
armed. The other four are bin tools a session runs by name.
|
|
79
82
|
|
|
80
|
-
## The
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
(`openrouter_key
|
|
94
|
-
|
|
95
|
-
`hooks/tezgah_judge.py::
|
|
96
|
-
|
|
97
|
-
`hooks/tezgah_judge.py::
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
`hooks/
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
`
|
|
83
|
+
## The providers and their order
|
|
84
|
+
|
|
85
|
+
`providers()` (`hooks/tezgah_judge.py::providers`) is the order `ask()` tries.
|
|
86
|
+
The next one is asked only when the previous failed. First comes the session's own
|
|
87
|
+
CLI (`tp.session_cli`, `hooks/tezgah_paths.py::session_cli`): `omp` when
|
|
88
|
+
`OMPCODE` is set, else `claude` when `CLAUDECODE` is. It runs headless with no
|
|
89
|
+
tools, rules, skills, extensions, MCP servers or settings (`SESSION_ARGV`,
|
|
90
|
+
`hooks/tezgah_judge.py::SESSION_ARGV`). Measured 2026-10-07, a call cost $0.007
|
|
91
|
+
on claude and $0.015 on omp. The session's own credential pays, OAuth subscription
|
|
92
|
+
included. The model and usage are read from the CLI's JSON output
|
|
93
|
+
(`_session_request`, `hooks/tezgah_judge.py::_session_request`). Then come the
|
|
94
|
+
third parties. TypeSafe reads `TYPESAFE_API_KEY`, else `~/.config/typesafe/key`
|
|
95
|
+
(`key`, `hooks/tezgah_judge.py::key`). OpenRouter reads `OPENROUTER_API_KEY`, else
|
|
96
|
+
`~/.config/openrouter/key` (`openrouter_key`, `hooks/tezgah_judge.py::openrouter_key`).
|
|
97
|
+
It asks the table's cheap row unless `TEZGAH_JUDGE_MODEL` names another
|
|
98
|
+
(`fallback_model`, `hooks/tezgah_judge.py::fallback_model`). The key file is
|
|
99
|
+
read because a hook runs where `~/.zshenv` never did. A chat answer is asked for in prose
|
|
100
|
+
(`CHAT_SYSTEM`, `hooks/tezgah_judge.py::CHAT_SYSTEM`) and filtered to the ids
|
|
101
|
+
asked, a wrongly typed answer dropped (`_chat_answers`,
|
|
102
|
+
`hooks/tezgah_judge.py::_chat_answers`).
|
|
103
|
+
|
|
104
|
+
`fallback` in `~/.config/tezgah/config.json` (`fallback_policy`,
|
|
105
|
+
`hooks/tezgah_paths.py::fallback_policy`) decides who may stand in:
|
|
106
|
+
|
|
107
|
+
| `fallback` | Session CLI present | No session CLI |
|
|
108
|
+
|---|---|---|
|
|
109
|
+
| `vendor` (default) | the session CLI only; when it fails, no judgement | TypeSafe, then OpenRouter |
|
|
110
|
+
| `any` | the session CLI, then TypeSafe, then OpenRouter | TypeSafe, then OpenRouter |
|
|
111
|
+
| `none` | the session CLI only | nobody: `available()` is false |
|
|
112
|
+
|
|
113
|
+
Nothing that stands in is silent. A result carries `provider`, the `model`
|
|
114
|
+
the reply named and `fallback` - None when the session answered, else why it did
|
|
115
|
+
not. That answer, and a refusal with its failures, is said on stderr as one
|
|
116
|
+
`tezgah-judge:` line. It is also kept as the last-use record (`_record`,
|
|
117
|
+
`hooks/tezgah_judge.py::_record`), which `tezgah-status --judge` prints. The skill
|
|
118
|
+
hint's cost row and the router's `route` row also carry `judge=<provider>/<model>`.
|
|
119
|
+
|
|
120
|
+
A caller that needs one provider in particular passes `only` (`named`,
|
|
121
|
+
`hooks/tezgah_judge.py::named`). `learn`'s decision has to come from a typed
|
|
122
|
+
model, so it passes `only=("typesafe",)`. It gets `None` rather than another
|
|
123
|
+
provider's answer. The caller named the provider, so the `vendor` order does not
|
|
124
|
+
apply. `none` still keeps every third party out.
|
|
111
125
|
|
|
112
126
|
## The switches
|
|
113
127
|
|
|
@@ -152,11 +166,11 @@ known (`note`, `hooks/tezgah_integrity.py::note`), counted by the row's kind
|
|
|
152
166
|
3. **No seam-level answer cache, and no caller cache beyond the prompt-keyed one
|
|
153
167
|
that exists** (`_remember`, `hooks/tezgah_skill_pick.py::_remember`). A judgement
|
|
154
168
|
costs a fraction of a cent, so a cache is not worth its state file, and a naive
|
|
155
|
-
one would make two runs of the same command disagree.
|
|
156
|
-
keeps
|
|
157
|
-
call that ends on a 401, 402 or
|
|
158
|
-
credential down for five minutes. Until the marker
|
|
159
|
-
|
|
169
|
+
one would make two runs of the same command disagree. Beside the last-use
|
|
170
|
+
record, the seam keeps a failure marker (`DOWN_FOR`, 300 s, in `hooks/tezgah_judge.py`). A
|
|
171
|
+
call that ends on a 401, 402, 5xx or a session CLI's non-zero exit marks that
|
|
172
|
+
provider, endpoint and credential down for five minutes. Until the marker
|
|
173
|
+
expires, that provider is skipped and sent no request. A dead key or an empty account then costs
|
|
160
174
|
one refusal per five minutes, not one per prompt. A rotated key gets a new
|
|
161
175
|
marker, so the seam asks it at once.
|
|
162
176
|
4. **No SDK, no `requests`, no async, no local model.** Each would trade one of
|
|
@@ -185,8 +199,9 @@ known (`note`, `hooks/tezgah_integrity.py::note`), counted by the row's kind
|
|
|
185
199
|
- `hooks/tezgah_skill_pick.py` — the prompt-path skill hint, its threshold, its
|
|
186
200
|
per-prompt cache and its `skill-suggest-on` marker.
|
|
187
201
|
- `hooks/tezgah_policy.py` — the `**Kill switches:**` paragraph the session
|
|
188
|
-
receives; `hooks/tezgah_paths.py` — where a switch file is read,
|
|
189
|
-
redirect guard (`guarded_opener`) the seam,
|
|
202
|
+
receives; `hooks/tezgah_paths.py` — where a switch file is read, the session CLI
|
|
203
|
+
and the `fallback` setting, and the redirect guard (`guarded_opener`) the seam,
|
|
204
|
+
consult and codegen share; `bin/tezgah-status --judge` — the last-use record.
|
|
190
205
|
- `hooks/tezgah_integrity.py` — the ledger and the `judge` counter;
|
|
191
206
|
`hooks/tezgah_context.py` — the status mark and the class carve-out.
|
|
192
207
|
- `tests/test_judge.py`, `tests/test_triage.py`, `tests/test_docs_router.py` —
|
package/docs/models.md
CHANGED
|
@@ -20,7 +20,12 @@ and every slot a model per family (`SLOTS`, `hooks/tezgah_models.py::SLOTS`). Th
|
|
|
20
20
|
three tier workers - `tezgah-cheap`, `tezgah-standard`, `tezgah-frontier` - are
|
|
21
21
|
generated in every tezgah root because routing needs no capability (`ROLES`,
|
|
22
22
|
`hooks/tezgah_agents.py::ROLES`; their brief, `_worker_body`,
|
|
23
|
-
`hooks/tezgah_agents.py::_worker_body`).
|
|
23
|
+
`hooks/tezgah_agents.py::_worker_body`). So are the five specialists:
|
|
24
|
+
`tezgah-tester`, `tezgah-docs`, `tezgah-security`, `tezgah-ui` and
|
|
25
|
+
`tezgah-researcher` (`hooks/tezgah_agents.py::SPECIALISTS`). A specialist's
|
|
26
|
+
description names its slot from `AGENT_SLOT`. The tier a host shows and the
|
|
27
|
+
model line it runs therefore cannot disagree, and `tezgah-agents --list` prints
|
|
28
|
+
both. A cheaper worker that meets work above its tier
|
|
24
29
|
answers `ESCALATE: <why>`, and the router restarts the task on `tezgah-frontier`
|
|
25
30
|
with the original brief rather than handing the failed trajectory up: continuing a
|
|
26
31
|
cheap trajectory on a frontier model was the most expensive option measured
|
|
@@ -30,10 +35,10 @@ cheap trajectory on a frontier model was the most expensive option measured
|
|
|
30
35
|
|
|
31
36
|
| slot | agents | anthropic | zai (omp's own provider) | openai | any (OpenRouter id) |
|
|
32
37
|
|---|---|---|---|---|
|
|
33
|
-
| cheap | tezgah-cheap | Opus 5.5 @low | `zai/glm-5.3-flash` | GPT-6.1 Sol @low | GLM-5.3 Flash |
|
|
38
|
+
| cheap | tezgah-cheap, tezgah-tester | Opus 5.5 @low | `zai/glm-5.3-flash` | GPT-6.1 Sol @low | GLM-5.3 Flash |
|
|
34
39
|
| explore | omp's bundled `scout` | Opus 5.5 @medium | `zai/glm-5.3-flash` | GPT-6.1 Sol @low | DeepSeek V4.1 Flash |
|
|
35
|
-
| standard | tezgah-standard | Opus 5.5 @medium | `zai/glm-5.3` | GPT-6.1 Sol @high | GPT-6.1 Sol @high |
|
|
36
|
-
| frontier | tezgah-frontier, tezgah-reviewer | Opus 5.5 @high | `zai/glm-5.3` | GPT-6 Astra @high | Opus 5.5 @high |
|
|
40
|
+
| standard | tezgah-standard, tezgah-docs, tezgah-ui | Opus 5.5 @medium | `zai/glm-5.3` | GPT-6.1 Sol @high | GPT-6.1 Sol @high |
|
|
41
|
+
| frontier | tezgah-frontier, tezgah-reviewer, tezgah-security, tezgah-researcher | Opus 5.5 @high | `zai/glm-5.3` | GPT-6 Astra @high | Opus 5.5 @high |
|
|
37
42
|
|
|
38
43
|
Why these, briefly. Inside the Anthropic family the lever is effort, not model:
|
|
39
44
|
Opus 5.5 and Sonnet 5.5 read cache at the same $0.20 per 1M tokens, and on
|
|
@@ -56,7 +61,7 @@ invoices).
|
|
|
56
61
|
| Claude Code | anthropic | the agent file's alias and `effort:` (`_model_lines`): an alias follows the provider and keeps the main session's variant, where a full id breaks on Bedrock, Vertex and a gateway (code.claude.com/docs/en/sub-agents) |
|
|
57
62
|
| Codex | openai | `model` and `model_reasoning_effort` in `.codex/agents/*.toml` |
|
|
58
63
|
| opencode | any | a selector `--refresh` found in `opencode models` for the slot's model under any configured provider (`resolve_opencode`); none found, no model line, so the agent inherits. The slot's effort rides the JSON agent config as `reasoningEffort` (opencode.ai/docs/agents) |
|
|
59
|
-
| omp | anthropic when the session default is an Anthropic model, any otherwise (`omp_mode`); a non-Anthropic default is `any`, never a silent fall back to the Anthropic column | `task.agentModelOverrides`, which omp resolves before the agent or bundled model (omp's own docs: `omp://task-agent-discovery.md`, *Model and structured-output precedence*); every slot is written, the frontier row included, so no agent in the row runs on the session default; each override is a **
|
|
64
|
+
| omp | anthropic when the session default is an Anthropic model, any otherwise (`omp_mode`); a non-Anthropic default is `any`, never a silent fall back to the Anthropic column | `task.agentModelOverrides`, which omp resolves before the agent or bundled model (omp's own docs: `omp://task-agent-discovery.md`, *Model and structured-output precedence*); every slot is written, the frontier row included, so no agent in the row runs on the session default; each override is a **fallback chain** (`omp_overrides`, `_chain`): the mode's family first, then what `fallback` in `~/.config/tezgah/config.json` allows (`tp.fallback_policy`) over the families this machine holds a credential for (`funded_families` reads omp's auth store and the provider env keys; a family it cannot see is left out, never guessed in) - `vendor`, the default, keeps only the session vendor's model (`openrouter/anthropic/claude-opus-5.5:<effort>` behind an Anthropic session, `OPENROUTER_ID`), so a 429 never moves an Opus session's subagent onto GPT or GLM; `any` adds every other funded family, rotated per agent; `none` writes the first entry alone - omp splits the chain into per-spawn patterns (omp's own docs: `omp://settings.md (its `retry.fallbackChains` section)`), so one provider's 429 falls through instead of killing the spawn, measured before this: 24 of 26 post-routing 429s hit a subagent's first request, all on one Opus account every slot shared; `apply_omp` also writes `modelRoles.plan` and `.slow` on that frontier row (same record, same ownership) so plan mode designs on it, and their fallbacks go to `retry.fallbackChains.<role>` under the same `fallback` rule, the only chain keys tezgah owns (`default` and provider-keyed chains stay the user's); `apply_omp` writes through the installer's own binary and agent dir, from HOME so a repository's `.omp/config.yml` never leaks machine-wide, and touches only the entries tezgah wrote (recorded in `~/.config/tezgah/models.json` as `omp_written`; a machine written before that record is adopted by its value, `_ours_by_shape`); a changed `fallback` reaches omp on the next `tezgah-route --refresh` or `tezgah-setup --install` |
|
|
60
65
|
| dsh | - | none: no model setting was found to write |
|
|
61
66
|
|
|
62
67
|
The orchestrator agent keeps `model: inherit`: it is the main thread's own agent.
|
|
@@ -88,11 +93,12 @@ fixed:
|
|
|
88
93
|
undone on. A 12-brief fixture pins it: 8 high-stakes briefs route to
|
|
89
94
|
frontier, 4 near-miss controls do not (`tests/test_models.py`).
|
|
90
95
|
2. Otherwise the brief - redacted with the ledger's own reader
|
|
91
|
-
(`redact`, `hooks/tezgah_integrity.py::redact`) - goes to
|
|
92
|
-
`hooks/tezgah_models.py::TIER_QUESTION`).
|
|
96
|
+
(`redact`, `hooks/tezgah_integrity.py::redact`) - goes to the judge as one Choice over three tiers (`TIER_QUESTION`,
|
|
97
|
+
`hooks/tezgah_models.py::TIER_QUESTION`). The judge asks the session's own model first ([judge](judge.md)).
|
|
98
|
+
Measured with Jev on 40 English briefs labelled by
|
|
93
99
|
the same session that wrote the rubric (2026-09-30, twice): under-route 0.025,
|
|
94
100
|
accuracy 0.925 and 0.900, 392 ms median, about 656 input tokens per call; a
|
|
95
|
-
keyword rule on the same set under-routed 0.100.
|
|
101
|
+
keyword rule on the same set under-routed 0.100. The session model was not measured on that set.
|
|
96
102
|
3. With no judgement (`judge-off`, no key, a failed call) `--phase` picks the tier
|
|
97
103
|
from the static table (`PHASE_TIER`, `hooks/tezgah_models.py::PHASE_TIER`), and
|
|
98
104
|
with no phase the middle tier is used.
|
package/docs/operations.md
CHANGED
|
@@ -23,7 +23,7 @@ before an install is considered.
|
|
|
23
23
|
| `--refresh` | Re-render every always-on contract artifact of the armed hosts - the managed blocks of `CLAUDE.md`, Codex's `AGENTS.md` and omp's `RULES.md`, opencode's contract and skill routers - after a policy edit or a kill switch; prints the paths it refreshed, else `contract is current`. Before the first install there is no hash record, so it changes nothing (`refresh_contract`, `bin/tezgah-setup::refresh_contract`). |
|
|
24
24
|
| `--uninstall` | Remove everything tezgah installed — per host the wiring it wrote, plus (a full run) the Claude plugin copy with its registry rows, the generated config state, the kill switches, the caches and the versioned install tree — then verify the removal; nonzero exit while anything tezgah wrote survives (`bin/tezgah-setup::verify_uninstall`, `bin/tezgah-setup::uninstall`). |
|
|
25
25
|
| `--adopt` | Move pre-tezgah wiring aside instead of deleting it; alone it stops there, with `--install` it runs first (`bin/tezgah-setup::main`, `bin/tezgah-setup::adopt`). |
|
|
26
|
-
| `--sync` | Copy this checkout over
|
|
26
|
+
| `--sync` | Copy this checkout over the Claude plugin [copy](glossary.md#plugin-copy) `installed_plugins.json` names, or over every copy when it names none (`bin/tezgah-setup::sync`, `bin/tezgah-setup::live_plugin_copies`). An older version dir is Claude's cache to collect, so it is left alone. Each copy is built in a staging tree beside the plugin cache and swapped in with two renames, its `.git` carried over, so a failed or killed sync leaves the previous copy whole (`bin/tezgah-setup::_swap`). Every copied file is 0644, or 0755 when the tree marks it executable, whatever mode the source has (`bin/tezgah-setup::_copy_normalised`). |
|
|
27
27
|
| `--hook-entries` | Print, as JSON, the tezgah-owned hook entries this tree arms per armed host - what `tezgah update` asks the new tree for (`bin/tezgah-setup::hook_entries`). |
|
|
28
28
|
| `--status [PATH]` | Print the armed/used checklist for PATH (default cwd) and stop — the same line as `bin/tezgah-status` (`bin/tezgah-setup::main`, `hooks/tezgah_context.py::health_lines`). It also prints the report's `config.json hosts (...) match the hosts wired (...)` row when the recorded list and the wiring on disk disagree (`hosts_row`), and nothing when they agree: install health is checked here, not only in a full report. |
|
|
29
29
|
| `--agents [PATH]` | Regenerate PATH's per-repo subagent set (default cwd); outside a [root](glossary.md#root) it prints `no agents generated` (`bin/tezgah-setup::main`). |
|
|
@@ -158,7 +158,7 @@ or `default`), a common block (config, the code-graph binary, the consult option
|
|
|
158
158
|
`orx`, `npx`, the artifacts dir, `git`, the manifest, a stale-contract line), one
|
|
159
159
|
block per host, then the budget (`bin/tezgah-setup::report`). Each block is
|
|
160
160
|
`host_checks_<host>` (`bin/tezgah-setup::HOST_CHECKS`), and those rows are the source of truth for
|
|
161
|
-
"is this host armed" (`AGENTS.md:
|
|
161
|
+
"is this host armed" (`AGENTS.md:147-148`) — not the presence of a directory, and
|
|
162
162
|
not the status line. A row can read ` MISS ` too: claude's `plugin copy current`
|
|
163
163
|
row fails on a machine with no copy at all, because an absent copy is not a
|
|
164
164
|
current one (`bin/tezgah-setup::host_checks_claude`).
|
|
@@ -284,8 +284,8 @@ A **full** run also takes everything the wiring was serving from:
|
|
|
284
284
|
`bin/tezgah-setup` plus the `current` flip (`remove_install_tree()`,
|
|
285
285
|
`bin/tezgah-setup::remove_install_tree`).
|
|
286
286
|
|
|
287
|
-
Kept in every run: the user's own files, every
|
|
288
|
-
|
|
287
|
+
Kept in every run: the user's own files, every host-file backup under
|
|
288
|
+
`~/.config/tezgah/backups/` (the timestamped ones too), `~/.config/tezgah/adopted/` (the only copy of the
|
|
289
289
|
predecessor wiring adopt moved aside), and every repository's `.tezgah/`
|
|
290
290
|
research state. A **partial** run also keeps config.json and the install tree,
|
|
291
291
|
because the hosts still armed read both at runtime, and says so. It also leaves
|
|
@@ -372,6 +372,7 @@ does not hold.
|
|
|
372
372
|
| `tezgah-doctor` | nothing: sizes, session and event counts, whether opencode is running, the two context-hygiene settings, the `.codegraph` bytes and index presence per repository, and the three host state dirs (`bin/tezgah-doctor::collect`, `bin/tezgah-doctor::report`) |
|
|
373
373
|
| `tezgah-doctor --clean` | vacuums opencode's database, and only when opencode is not running (`bin/tezgah-doctor::vacuum_db`, `bin/tezgah-doctor::main`); and deletes tezgah's own hook state not modified for `--retention-days` (default 30) - the ledgers under `evidence/`, `turns/`, `sessions/`, `classify.log`, `context-drops.log` and `debug.log`, in the cache and its sandbox fallback - keeping the current session's (`$TEZGAH_SESSION`) files whatever their age (`sweep_state`) |
|
|
374
374
|
| `tezgah-doctor --coverage` | every tracked file the codegraph index does not hold, in two classes — a supported extension the index is missing, and a shebang-only script with no `.py` twin — with the file counts it read, so an empty result cannot read as "everything is covered" |
|
|
375
|
+
| `tezgah-doctor --stack` | per host: the context files in load order, the skill dirs and which copy wins, the hook rows per event with their owner, the MCP servers, and every conflict; exits 1 on one ([hosts](hosts.md#precedence-what-each-host-loads-and-in-which-order), `hooks/tezgah_stack.py::report`) |
|
|
375
376
|
| `tezgah-doctor --prune-sessions DAYS` | deletes sessions idle longer than DAYS through `opencode session delete`, then vacuums; skipped when opencode is running or its CLI is missing (`bin/tezgah-doctor::prune_sessions`, `bin/tezgah-doctor::main`) |
|
|
376
377
|
|
|
377
378
|
`VACUUM` alone cannot shrink that database — its pages are all live — so
|
|
@@ -384,15 +385,18 @@ extension the engine has no parser for and an extensionless script it cannot key
|
|
|
384
385
|
on at all. The extensionless case is why `bin/*` carries `.py` twins, and the
|
|
385
386
|
report counts a tracked `bin/x` as covered when the index holds `bin/x.py`.
|
|
386
387
|
|
|
387
|
-
## Opt-in taste capture
|
|
388
|
+
## Opt-in taste: capture, learn, apply
|
|
388
389
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
390
|
+
Taste learns the user's coding preferences from their own corrections. It is
|
|
391
|
+
taste-1's architecture as Command Code documents it, ported to tezgah (research
|
|
392
|
+
line taste-architecture, design T1P). An LLM-free structured signal goes to a
|
|
393
|
+
typed decision step, then a symbolic ledger, then a meta loop. It stays off
|
|
394
|
+
until the user arms it with a `taste-on` file in `~/.config/tezgah/` (`enabled`,
|
|
395
|
+
`hooks/tezgah_taste.py::enabled`). The off path costs two stats. Armed, it
|
|
396
|
+
writes only into a repository that already has a `.tezgah/` directory. A
|
|
397
|
+
`.no-taste` mark turns it off for one repository
|
|
398
|
+
(`hooks/tezgah_context.py::repo_marks`). A workspace of repo-provided data gets
|
|
399
|
+
nothing.
|
|
396
400
|
|
|
397
401
|
Rows go to `<repo>/.tezgah/taste/signals.jsonl` through the ledger's own writer
|
|
398
402
|
(`_write`, `hooks/tezgah_taste.py::_write`). A user turn adds a `prompt` row,
|
|
@@ -408,22 +412,69 @@ ledger row. A rollback therefore never reads it as a pre-state (`capture_after`,
|
|
|
408
412
|
`hooks/tezgah_snapshot.py::capture_after`). A file outside the repository gets no
|
|
409
413
|
after blob.
|
|
410
414
|
opencode reaches the same call through a process (`noteTaste`,
|
|
411
|
-
`hosts/opencode/plugins/tezgah.js:
|
|
415
|
+
`hosts/opencode/plugins/tezgah.js:2380-2390`).
|
|
412
416
|
|
|
413
417
|
```sh
|
|
414
418
|
touch ~/.config/tezgah/taste-on # arm; rm it to disarm
|
|
419
|
+
bin/tezgah-taste learn # decide the new capture signals, update the ledger
|
|
420
|
+
bin/tezgah-taste learn --from-transcripts # bootstrap from this repo's omp/Claude history
|
|
421
|
+
bin/tezgah-taste list # learnings: state, rule/hint, confidence, scope
|
|
422
|
+
bin/tezgah-taste accept|reject|show ID # the user's call on one learning
|
|
423
|
+
bin/tezgah-taste edit ID --text "..." # reword it; an edited line is never rewritten
|
|
424
|
+
bin/tezgah-taste export # accepted learnings into AGENTS.md
|
|
425
|
+
bin/tezgah-taste label; bin/tezgah-taste label ID preference # the calibration sample
|
|
426
|
+
bin/tezgah-taste calibrate # the bound a rule needs
|
|
427
|
+
bin/tezgah-taste gate # the pooled before/after test
|
|
415
428
|
bin/tezgah-taste mine --host omp --stats # sessions, writes, prompts after a writing turn
|
|
416
|
-
bin/tezgah-taste
|
|
417
|
-
bin/tezgah-taste measure --samples S --labels L # judge precision/recall against labels
|
|
418
|
-
bin/tezgah-taste rate --in mined.jsonl # preference corrections per writing turn
|
|
429
|
+
bin/tezgah-taste measure --samples S --labels L [--typed] # judge precision/recall
|
|
419
430
|
```
|
|
420
431
|
|
|
421
|
-
`
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
432
|
+
`learn` turns each prompt that followed a writing turn into one signal, and code
|
|
433
|
+
builds it (`capture_signals`, `bin/tezgah-taste::capture_signals`). A signal
|
|
434
|
+
holds the prompt, the files written, the agent's edits, and the user's own edit
|
|
435
|
+
between two agent writes. That last one compares the after blob with the next
|
|
436
|
+
write's pre-state, or with the file on disk (`user_diff`,
|
|
437
|
+
`bin/tezgah-taste::user_diff`). `learn` asks TypeSafe for one typed decision per
|
|
438
|
+
signal (`decide`, `bin/tezgah-taste::decide`, [judge](judge.md)). The decision
|
|
439
|
+
names preference, defect or none. It picks one of eleven fixed categories and a
|
|
440
|
+
scope. It also says how the signal relates to each learning held: supports,
|
|
441
|
+
contradicts, narrows or unrelated. When another provider answers, `learn` writes
|
|
442
|
+
the decision to `decisions.jsonl` as `unverified` and never applies it. A defect
|
|
443
|
+
goes to `defects.jsonl` and never into the ledger.
|
|
444
|
+
|
|
445
|
+
The ledger is `<repo>/.tezgah/taste/ledger.json`, plus
|
|
446
|
+
`~/.config/tezgah/taste/ledger.json` for user-scope learnings
|
|
447
|
+
(`hooks/tezgah_taste_ledger.py`). Confidence is (r+1)/(r+s+2) over supporting
|
|
448
|
+
and contradicting evidence. Each piece counts at its decision probability, and
|
|
449
|
+
the ledger forgets it per day at its scope's rate (`confidence`,
|
|
450
|
+
`hooks/tezgah_taste_ledger.py::confidence`). A learning turns active in its
|
|
451
|
+
second session. A contradiction flags an active learning `conflicted` and stores
|
|
452
|
+
the contrary claim `quarantined`. Neither overwrites the other. `narrows` adds a
|
|
453
|
+
narrower learning beside the broader one. When the user corrects an injected
|
|
454
|
+
learning's category in the same session, that learning loses confidence
|
|
455
|
+
(`apply`, `hooks/tezgah_taste_ledger.py::apply`). The hooks only read the
|
|
456
|
+
ledgers. `learn`, `accept`, `reject` and `edit` rewrite them one at a time on
|
|
457
|
+
this machine, under a lock on `~/.config/tezgah/taste/ledger.lock` (`locked`,
|
|
458
|
+
`hooks/tezgah_taste_ledger.py::locked`). The lock spans every repository,
|
|
459
|
+
because each command also rewrites the user ledger. A second one exits 2 and
|
|
460
|
+
names the holder. Every taste row goes through the evidence ledger's locked
|
|
461
|
+
writer (`append`, `hooks/tezgah_taste_ledger.py::append`).
|
|
462
|
+
|
|
463
|
+
Session start injects the active learnings (`block`,
|
|
464
|
+
`hooks/tezgah_taste_ledger.py::block`). A rule needs confidence 0.8 and a
|
|
465
|
+
Clopper-Pearson lower bound of 0.8 on preference precision, measured on the
|
|
466
|
+
labelled sample (`calibration`, `hooks/tezgah_taste_ledger.py::calibration`). A
|
|
467
|
+
hint needs 0.6. A learning whose evidence paths are all gone is withheld. A
|
|
468
|
+
write also carries the in-scope learnings it has not yet shown in the session,
|
|
469
|
+
on every host's post-tool channel (`write_note`, `hooks/tezgah_taste.py::write_note`).
|
|
470
|
+
`gate` compares preference corrections per writing turn before and after the
|
|
471
|
+
first injection, by when each turn happened. Once both arms hold 980 turns and
|
|
472
|
+
the after-rate is not lower
|
|
473
|
+
at one-sided p < 0.1, it writes `gate.json` and injection stops (`gate`,
|
|
474
|
+
`hooks/tezgah_taste_ledger.py::gate`). `mine` reads omp's top-level session
|
|
475
|
+
files and Claude's top-level transcripts whose cwd is under `--root`
|
|
476
|
+
(`cmd_mine`, `bin/tezgah-taste::cmd_mine`). `measure` and `rate` use a fixed
|
|
477
|
+
labelling rule (`RULE`, `bin/tezgah-taste::RULE`).
|
|
427
478
|
|
|
428
479
|
## Tidying the lessons ledger
|
|
429
480
|
|
|
@@ -541,7 +592,11 @@ with `embed-m2v`. The fusion found it for 0.75 and 0.74, BM25 for 0.68.
|
|
|
541
592
|
|
|
542
593
|
Reversible and safe to repeat: every link and settings key it writes is
|
|
543
594
|
idempotent and re-created by `--install`. A write that changes a file first
|
|
544
|
-
copies it to `<file>.<timestamp>.tezgah-bak`.
|
|
595
|
+
copies it to `<file>.<timestamp>.tezgah-bak`. A host file's copy goes under
|
|
596
|
+
`~/.config/tezgah/backups/` (`bin/tezgah-setup::backup_base`). A host lists the
|
|
597
|
+
files in its own dirs. tezgah's own state dir
|
|
598
|
+
keeps its copies beside the file. `--install` moves the copies an older release
|
|
599
|
+
left in a host dir (`bin/tezgah-setup::sweep_backups`). The oldest of those copies is
|
|
545
600
|
always kept, beside the newest four (`backup()`, `BACKUP_CAP`). The oldest is
|
|
546
601
|
the file before tezgah's first timestamped write, which is tezgah's own content
|
|
547
602
|
for a file tezgah created. On an upgraded install the pre-tezgah copy is the
|
|
@@ -575,8 +630,8 @@ question instead of paying an agent to read a page: the analyze-app loop's snaps
|
|
|
575
630
|
triage (`bin/tezgah-triage`), `bin/tezgah-docs` for a query its keyword index
|
|
576
631
|
cannot place, and the prompt-path skill hint (`hooks/tezgah_skill_pick.py`), the
|
|
577
632
|
one caller no shell row sees, `bin/tezgah-route`, the tier router
|
|
578
|
-
([models](models.md)), and `bin/tezgah-taste`'s `measure` and `
|
|
579
|
-
([above](#opt-in-taste-capture)). All five go through the same stdlib-only seam,
|
|
633
|
+
([models](models.md)), and `bin/tezgah-taste`'s `measure`, `rate` and `learn`
|
|
634
|
+
([above](#opt-in-taste-capture-learn-apply)). All five go through the same stdlib-only seam,
|
|
580
635
|
which returns `None` rather than raising because a hook may import it
|
|
581
636
|
(`available()`, `hooks/tezgah_judge.py::available`); the request is
|
|
582
637
|
one batched call, and the credential resolves per call (`ask()`,
|
package/docs/orca.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Orca: what it owns, what tezgah owns, where they meet
|
|
2
|
+
|
|
3
|
+
Orca is an IDE that runs agent sessions in its own terminals and worktrees.
|
|
4
|
+
This page is for a maintainer or an agent working inside Orca. Read it when a
|
|
5
|
+
session asks how tezgah and Orca share a machine. It describes Orca 1.4.221 and
|
|
6
|
+
1.4.222 as observed on 2026-10-07. Each Orca claim names the command or file
|
|
7
|
+
behind it, so a reader can check it again after a later release.
|
|
8
|
+
|
|
9
|
+
## How a process knows it runs in Orca
|
|
10
|
+
|
|
11
|
+
Every terminal Orca starts exports its own markers. The table lists the ones
|
|
12
|
+
tezgah reads, as `env` printed them inside an Orca terminal.
|
|
13
|
+
|
|
14
|
+
| Variable | Value shape | Read by |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| `ORCA_WORKTREE_ID` | `<repoId>::<absolute path>` of the Orca worktree | `hooks/tezgah_orca.py::session` |
|
|
17
|
+
| `ORCA_TERMINAL_HANDLE` | `term_<uuid>`, the handle `orca terminal` commands take | `hooks/tezgah_orca.py::session` |
|
|
18
|
+
| `ORCA_PANE_KEY` | `<tab>:<pane>` | `hooks/tezgah_orca.py::session` |
|
|
19
|
+
| `TERM_PROGRAM` | `Orca` | `hooks/tezgah_orca.py::session` |
|
|
20
|
+
| `ORCA_APP_VERSION` | `1.4.221` | `hooks/tezgah_orca.py::session` |
|
|
21
|
+
| `ORCA_CLI_COMMAND` | the wrapper Orca exports for WSL sessions (orca-cli skill) | `hooks/tezgah_orca.py::cli` |
|
|
22
|
+
| `ORCA_CLI_BIN_DIR` | `/Applications/Orca.app/Contents/Resources/bin` | `hooks/tezgah_orca.py::cli` |
|
|
23
|
+
| `CODEX_HOME`, `ORCA_CODEX_HOME` | `~/Library/Application Support/orca/codex-accounts/<id>/home` | `hooks/tezgah_paths.py::HOST_DIRS` (CODEX_HOME only) |
|
|
24
|
+
|
|
25
|
+
Orca also exports `ORCA_AGENT_HOOK_*` for its hook endpoint, port and token.
|
|
26
|
+
It exports `ORCA_OMP_*` for its omp status extension. tezgah reads none of
|
|
27
|
+
them and writes no `ORCA_*` variable. The marker set in `MARKERS` is the whole
|
|
28
|
+
contract.
|
|
29
|
+
|
|
30
|
+
A shell started from an Orca terminal inherits the variables. A subagent whose
|
|
31
|
+
working directory is a raw worktree still reports the parent's
|
|
32
|
+
`ORCA_WORKTREE_ID`. So the marker says "inside Orca", not "inside this
|
|
33
|
+
checkout".
|
|
34
|
+
|
|
35
|
+
## What Orca owns
|
|
36
|
+
|
|
37
|
+
| Surface | What Orca keeps | Evidence |
|
|
38
|
+
|---|---|---|
|
|
39
|
+
| Worktrees | a git worktree plus a card (status, comment, lineage, links), by default under `~/orca/workspaces/<repo>/<name>` | `orca worktree create --help`; `orca repo show` lists paths under `~/orca/workspaces/tezgah/` |
|
|
40
|
+
| Terminals | live PTYs with handles, read/send/wait | `orca terminal list --json` |
|
|
41
|
+
| Agent sessions | agent type and state per pane, shown in `orca worktree ps` | `agents[].agentType`, `agents[].state` in `orca worktree ps --json` |
|
|
42
|
+
| Orchestration | runs, tasks, dispatch, supervised workers, decision gates | `orca --help`, section Orchestration |
|
|
43
|
+
| Embedded browser | tabs per worktree, snapshot and element refs | `orca --help`, section Browser Automation |
|
|
44
|
+
| Artifacts | public HTML or Markdown links, off until a human enables them in Settings | `orca skills get orca-cli`, section Artifacts |
|
|
45
|
+
| Codex accounts | one codex home per account, marked by `.orca-managed-home` | the account home's directory listing |
|
|
46
|
+
| Its hooks | `~/.orca/agent-hooks/`, plus entries tagged `orca-agent-hook-form=1` in each managed codex `hooks.json` | the account home's `hooks.json` |
|
|
47
|
+
| omp extensions | `orca-agent-status.ts`, `orca-prefill.ts` and `orca-titlebar-spinner.ts` in `~/.omp/agent/extensions/` | that directory's listing |
|
|
48
|
+
|
|
49
|
+
The `orca-cli` skill is a stub. The full guide comes from the binary itself:
|
|
50
|
+
`orca skills get orca-cli`. Run it before any Orca command a session has not
|
|
51
|
+
run before.
|
|
52
|
+
|
|
53
|
+
## What tezgah owns
|
|
54
|
+
|
|
55
|
+
tezgah owns the contract text, the gate, the evidence ledger and the status
|
|
56
|
+
line. It also owns its own hook entries in each host config
|
|
57
|
+
([architecture](architecture.md), [hosts](hosts.md)). It does not create
|
|
58
|
+
worktrees or terminals in code. A parallel slice is an instruction to the
|
|
59
|
+
session: the `Tier 1` paragraph of `hooks/tezgah_policy.py::ORCHESTRATE`,
|
|
60
|
+
mirrored in `skills/tezgah-contract/SKILL.md`, and the orchestrator agent body
|
|
61
|
+
`hooks/tezgah_agents.py::_orch_body`.
|
|
62
|
+
|
|
63
|
+
## Where they meet
|
|
64
|
+
|
|
65
|
+
| Point | Behaviour | Code |
|
|
66
|
+
|---|---|---|
|
|
67
|
+
| Session start inside Orca | one `Orca:` line names the worktree and tells the session to use `orca worktree create`, `orca terminal create` and `orca worktree ps`. It reads env only and starts no process. | `hooks/tezgah_orca.py::context_line`, called from `hooks/tezgah_context.py::context_for` |
|
|
68
|
+
| Slice checkouts | inside Orca, `orca worktree create --name <slug> --parent-worktree active --json`, else `git worktree add` | `hooks/tezgah_policy.py::ORCHESTRATE`, `hooks/tezgah_agents.py::_orch_body` |
|
|
69
|
+
| Tracking | `tezgah-status --orca` lists each checkout Orca tracks (card, state, live terminals, agent states) and each git worktree Orca does not list | `bin/tezgah-status::print_orca`, `hooks/tezgah_orca.py::checkouts` |
|
|
70
|
+
| Install report | `tezgah-setup --status` prints one `orca:` line where Orca is present. The line names the runtime, the terminal and an Orca-managed CODEX_HOME. | `hooks/tezgah_orca.py::summary`, `bin/tezgah-setup::main` |
|
|
71
|
+
| Codex hooks | tezgah drops only entries holding `tezgah` and appends its own groups, so Orca's entries stay | `bin/tezgah-setup::install_codex` |
|
|
72
|
+
| Cursor hooks | the same merge | `bin/tezgah-setup::install_cursor` |
|
|
73
|
+
| Claude status line | tezgah's wrapper runs Orca's `~/.orca/agent-hooks/claude-statusline.sh` first and appends its own segment | `statusline.py::ORCA`, `bin/tezgah-setup::wire_claude_statusline` |
|
|
74
|
+
| Codex rules file | Orca links the account home's `AGENTS.md` to `~/.codex/AGENTS.md`. tezgah writes the managed block through that link with a plain `open`, so the link stays. | `bin/tezgah-setup::set_managed_md` |
|
|
75
|
+
|
|
76
|
+
### The collision this fixes
|
|
77
|
+
|
|
78
|
+
On 2026-10-07 the tezgah repository had eleven git checkouts and Orca listed
|
|
79
|
+
one. `git worktree list` printed the main checkout and ten under
|
|
80
|
+
`~/Projects/tezgah-wt/`, and `orca worktree list --repo path:<main> --json`
|
|
81
|
+
returned `totalCount: 1`. The ten came from raw `git worktree add`, so Orca
|
|
82
|
+
showed no card, terminal or agent state for them. `tezgah-status --orca` now
|
|
83
|
+
prints that gap.
|
|
84
|
+
|
|
85
|
+
A smoke run on the live runtime closed the loop. It ran `orca worktree create
|
|
86
|
+
--name tezgah-orca-smoke --parent-worktree active --setup skip --json` from the
|
|
87
|
+
main checkout. `tezgah-status --orca --json` then listed the new checkout as
|
|
88
|
+
tracked, with one terminal. `orca worktree rm --force` removed it, its branch
|
|
89
|
+
included.
|
|
90
|
+
|
|
91
|
+
### CODEX_HOME inside Orca
|
|
92
|
+
|
|
93
|
+
Inside Orca, CODEX_HOME names an Orca-managed account home. A `tezgah-setup
|
|
94
|
+
--install` run from an Orca terminal arms that home, and a run from another
|
|
95
|
+
terminal arms `~/.codex` (`hooks/tezgah_paths.py::HOST_DIRS`). On this machine
|
|
96
|
+
both homes carry tezgah's seven hook events. The Orca home keeps
|
|
97
|
+
Orca's own entries beside them. Orca also keeps
|
|
98
|
+
`.orca-hook-trust-provenance.json` there, keyed by event and entry index.
|
|
99
|
+
tezgah appends after Orca's entries, so Orca's index does not move. Whether
|
|
100
|
+
Orca re-trusts a changed tezgah entry on its own is not verified.
|
|
101
|
+
|
|
102
|
+
## What the Orca CLI does not offer
|
|
103
|
+
|
|
104
|
+
- No command adopts an existing raw git worktree as an Orca worktree. The
|
|
105
|
+
`Worktrees` section of `orca --help` lists `list`, `show`, `current`,
|
|
106
|
+
`create`, `set`, `rm` and `ps` only. `orca repo set --repo <sel>
|
|
107
|
+
--external-worktree-visibility show` makes non-Orca worktrees visible in the
|
|
108
|
+
sidebar. It does not give them cards.
|
|
109
|
+
- An agent cannot turn on artifact publishing. A human enables it in Settings
|
|
110
|
+
(the `Artifacts` section of `orca skills get orca-cli`).
|
|
111
|
+
|
|
112
|
+
## Tests
|
|
113
|
+
|
|
114
|
+
`tests/test_orca.py` runs with no real Orca. A fake `orca` on disk answers
|
|
115
|
+
`worktree ps` and `status`, and every marker is a fixture. `tests/support.py`
|
|
116
|
+
strips the developer's own `ORCA_*` markers and `TERM_PROGRAM=Orca`, and pins
|
|
117
|
+
`TEZGAH_ORCA_BIN` to a missing path. Without that, a suite run inside Orca
|
|
118
|
+
would reach the live runtime.
|
|
119
|
+
|
|
120
|
+
## Source of truth
|
|
121
|
+
|
|
122
|
+
- `hooks/tezgah_orca.py`: detection, CLI resolution, the listing and both report lines
|
|
123
|
+
- `hooks/tezgah_context.py`: where the session line joins the session-start block
|
|
124
|
+
- `hooks/tezgah_policy.py`, `skills/tezgah-contract/SKILL.md`, `hooks/tezgah_agents.py`: the slice rule
|
|
125
|
+
- `bin/tezgah-status`, `bin/tezgah-setup`: the two report surfaces
|
|
126
|
+
- `tests/test_orca.py`, `tests/support.py`
|