@r1z4x/tezgah 1.6.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 +140 -1
- package/MANIFEST +7 -0
- package/README.md +6 -3
- package/bin/tezgah-decisions +6 -14
- package/bin/tezgah-design +21 -5
- package/bin/tezgah-docs +18 -16
- package/bin/tezgah-doctor +152 -0
- package/bin/tezgah-lessons +4 -1
- package/bin/tezgah-research +249 -17
- package/bin/tezgah-route +70 -8
- package/bin/tezgah-setup +26 -13
- package/bin/tezgah-task +877 -60
- package/bin/tezgah-taste +56 -15
- package/docs/architecture.md +28 -11
- package/docs/clarity-baseline.json +2 -2
- package/docs/contract.md +108 -24
- package/docs/environment.md +3 -1
- package/docs/evidence.md +209 -28
- package/docs/feature-audit.md +1 -1
- package/docs/gate.md +78 -23
- package/docs/glossary.md +4 -4
- package/docs/hosts.md +8 -8
- package/docs/index.json +11 -5
- package/docs/judge.md +9 -3
- package/docs/models.md +75 -14
- package/docs/operations.md +133 -3
- package/docs/research.md +349 -15
- package/docs/skills.md +1 -1
- package/docs/status-line.md +5 -5
- package/docs/testing.md +20 -13
- package/docs/vision.md +12 -4
- package/hooks/hooks.json +2 -2
- package/hooks/lang/en.json +323 -0
- package/hooks/lang/tr.json +344 -0
- package/hooks/projects-auto-init.py +29 -1
- package/hooks/projects-posttooluse.py +24 -5
- package/hooks/projects-pretooluse.py +9 -4
- package/hooks/projects-stop.py +28 -16
- package/hooks/tezgah_agents.py +53 -17
- package/hooks/tezgah_clarity.py +7 -3
- package/hooks/tezgah_context.py +489 -438
- package/hooks/tezgah_gate.py +996 -80
- package/hooks/tezgah_integrity.py +1547 -197
- package/hooks/tezgah_models.py +427 -45
- package/hooks/tezgah_paths.py +147 -16
- package/hooks/tezgah_policy.py +261 -129
- package/hooks/tezgah_rank.py +29 -27
- package/hooks/tezgah_replay.py +200 -30
- package/hooks/tezgah_research.py +1303 -14
- package/hooks/tezgah_stopspec.py +7 -0
- package/hooks/tezgah_store.py +74 -27
- package/hooks/tezgah_task.py +1201 -40
- package/hooks/tezgah_taste.py +8 -4
- package/hosts/codex/hook.py +32 -13
- package/hosts/cursor/hook.py +32 -14
- package/hosts/dsh/hooks.json +2 -2
- package/hosts/omp/hook.py +44 -23
- package/hosts/omp/tezgah-hook.ts.in +93 -7
- package/hosts/opencode/plugins/tezgah.js +273 -6
- package/output-styles/tezgah.md +76 -42
- package/package.json +1 -1
- package/skills/i-have-adhd/EVALS.md +22 -2
- package/skills/i-have-adhd/SKILL.md +12 -5
- package/skills/plan-add/SKILL.md +27 -11
- package/skills/plan-add/render_table.py +8 -3
- package/skills/plan-sync/SKILL.md +5 -3
- package/skills/ponytail/EVALS.md +21 -2
- package/skills/ponytail/SKILL.md +4 -4
- package/skills/research/SKILL.md +89 -1
- package/skills/tezgah-contract/EVALS.md +52 -7
- package/skills/tezgah-contract/SKILL.md +154 -59
package/hooks/tezgah_models.py
CHANGED
|
@@ -37,6 +37,7 @@ tier from the static table, and with no phase the middle tier is used.
|
|
|
37
37
|
|
|
38
38
|
Stdlib only; every network or subprocess failure is a fallback, never a raise.
|
|
39
39
|
"""
|
|
40
|
+
import contextlib
|
|
40
41
|
import datetime
|
|
41
42
|
import json
|
|
42
43
|
import os
|
|
@@ -232,18 +233,331 @@ def _omp_stored_providers():
|
|
|
232
233
|
return {row[0] for row in rows}
|
|
233
234
|
|
|
234
235
|
|
|
235
|
-
def funded_families():
|
|
236
|
+
def funded_families(live=True):
|
|
236
237
|
"""The `OMP_FAMILIES` with a credential this machine shows: an omp auth-store
|
|
237
|
-
entry, an env key omp reads, or (for `any`) `openrouter_ready
|
|
238
|
+
entry, an env key omp reads, or (for `any`) `openrouter_ready` - minus, with
|
|
239
|
+
`live`, the families the health record holds as down (`down_families`).
|
|
238
240
|
|
|
239
241
|
A credential is not a balance - a funded-looking key can still answer 402 -
|
|
240
|
-
which is why each agent gets a chain rather than one more pin
|
|
241
|
-
|
|
242
|
+
which is why each agent gets a chain rather than one more pin, and why a
|
|
243
|
+
family whose provider answered a limit leaves the chain until its reset. A
|
|
244
|
+
family whose credential cannot be detected is left out, never guessed in."""
|
|
242
245
|
stored = _omp_stored_providers()
|
|
246
|
+
down = down_families() if live else set()
|
|
243
247
|
return [family for family in OMP_FAMILIES
|
|
244
|
-
if
|
|
245
|
-
|
|
246
|
-
|
|
248
|
+
if family not in down
|
|
249
|
+
and (OMP_AUTH[family][0] in stored
|
|
250
|
+
or any(os.environ.get(env, "").strip() for env in OMP_AUTH[family][1])
|
|
251
|
+
or (family == "any" and openrouter_ready()))]
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
# ------------------------------------------------------------ provider health
|
|
255
|
+
#
|
|
256
|
+
# A family with a credential can still be spent: on 2026-10-11 every subagent
|
|
257
|
+
# chain read `anthropic, openrouter`, one Anthropic 429 moved the subagents onto
|
|
258
|
+
# an OpenRouter key past its limit (403), and they died; zai, funded in omp's
|
|
259
|
+
# store, answered `429 Weekly/Monthly Limit Exhausted. Your limit will reset at
|
|
260
|
+
# 2026-10-12 02:02:35`. The record below holds which family is down and until
|
|
261
|
+
# when; `funded_families` leaves those out, and `tezgah-route --health` - started
|
|
262
|
+
# in the background at session start when due - probes the non-mode families
|
|
263
|
+
# and re-applies omp's chain, so a family rejoins it once its reset has passed.
|
|
264
|
+
HEALTH_FILE = "model-health.json"
|
|
265
|
+
HEALTH_EVERY = 6 * 3600 # re-probe at most this often, unless a reset passed
|
|
266
|
+
HEALTH_DOWN = 6 * 3600 # how long a limit with no reset time keeps a family out
|
|
267
|
+
HEALTH_RATE = 600 # the same for a rate limit (429): it lifts in minutes
|
|
268
|
+
HEALTH_DOWN_MAX = 7 * 86400 # the longest a named reset is believed
|
|
269
|
+
OPENROUTER_KEY = "https://openrouter.ai/api/v1/key"
|
|
270
|
+
_RESET_AT = re.compile(r"reset[s]? (?:at|on) (\d{4}-\d{2}-\d{2})[ T](\d{2}:\d{2}:\d{2})")
|
|
271
|
+
_RETRY_MS = re.compile(r"retry-after-ms=(\d+)")
|
|
272
|
+
_LIMITED = re.compile(r"\b(?:429|402|403)\b|rate[ _-]?limit|quota|limit exhausted|"
|
|
273
|
+
r"key limit exceeded|usage limit|insufficient (?:credit|balance)",
|
|
274
|
+
re.I)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def _health_path():
|
|
278
|
+
return os.path.join(tp.cache_dir(), HEALTH_FILE)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def read_health():
|
|
282
|
+
"""{"checked": epoch or None, "down": {family: {"until": epoch, "reason": str}}};
|
|
283
|
+
an unreadable record reads as empty - nothing known down."""
|
|
284
|
+
try:
|
|
285
|
+
with open(_health_path(), encoding="utf-8") as fh:
|
|
286
|
+
data = json.load(fh)
|
|
287
|
+
except (OSError, ValueError):
|
|
288
|
+
data = {}
|
|
289
|
+
if not isinstance(data, dict):
|
|
290
|
+
data = {}
|
|
291
|
+
down = data.get("down") if isinstance(data.get("down"), dict) else {}
|
|
292
|
+
return {"checked": data.get("checked"), "down": down}
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def write_health(data):
|
|
296
|
+
"""Write the record atomically; False when the cache dir cannot be written."""
|
|
297
|
+
path = _health_path()
|
|
298
|
+
try:
|
|
299
|
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
300
|
+
tmp = "%s.%d.tmp" % (path, os.getpid())
|
|
301
|
+
with open(tmp, "w", encoding="utf-8") as fh:
|
|
302
|
+
json.dump(data, fh, indent=1, sort_keys=True)
|
|
303
|
+
os.replace(tmp, path)
|
|
304
|
+
return True
|
|
305
|
+
except OSError:
|
|
306
|
+
return False
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def down_families(now=None, data=None):
|
|
310
|
+
"""The families the record holds as down whose reset has not passed yet."""
|
|
311
|
+
import time
|
|
312
|
+
now = time.time() if now is None else now
|
|
313
|
+
data = read_health() if data is None else data
|
|
314
|
+
return {family for family, row in data["down"].items()
|
|
315
|
+
if isinstance(row, dict) and isinstance(row.get("until"), (int, float))
|
|
316
|
+
and row["until"] > now}
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def limit_until(text, now=None, default=HEALTH_DOWN):
|
|
320
|
+
"""When a provider's limit answer says it lifts, as an epoch, or None when the
|
|
321
|
+
text is no limit answer at all. A named reset (`reset at 2026-10-12 02:02:35`,
|
|
322
|
+
read as local time) or a `retry-after-ms` wins; else `default` from now. A
|
|
323
|
+
reset further than HEALTH_DOWN_MAX away is capped: a wrong year must not lock a
|
|
324
|
+
family out for good."""
|
|
325
|
+
import time
|
|
326
|
+
now = time.time() if now is None else now
|
|
327
|
+
text = str(text or "")
|
|
328
|
+
if not _LIMITED.search(text):
|
|
329
|
+
return None
|
|
330
|
+
until = None
|
|
331
|
+
hit = _RESET_AT.search(text)
|
|
332
|
+
if hit:
|
|
333
|
+
try:
|
|
334
|
+
until = time.mktime(time.strptime("%s %s" % hit.groups(), "%Y-%m-%d %H:%M:%S"))
|
|
335
|
+
except (ValueError, OverflowError):
|
|
336
|
+
until = None
|
|
337
|
+
if until is not None and until <= now:
|
|
338
|
+
# a named reset already past is a clock we misread (the provider's
|
|
339
|
+
# zone is not stated): a minute's hold would re-probe every prompt
|
|
340
|
+
until = now + default
|
|
341
|
+
if until is None:
|
|
342
|
+
ms = _RETRY_MS.search(text)
|
|
343
|
+
until = now + int(ms.group(1)) / 1000.0 if ms else now + default
|
|
344
|
+
return min(max(until, now + 60), now + HEALTH_DOWN_MAX)
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
@contextlib.contextmanager
|
|
348
|
+
def _health_locked():
|
|
349
|
+
"""An exclusive flock on the record's lock file around one re-read, change
|
|
350
|
+
and write, so two writers (a slice's stop, the probe) do not drop each
|
|
351
|
+
other's hold. Unlocked where fcntl is missing or the file cannot be opened."""
|
|
352
|
+
fh = None
|
|
353
|
+
try:
|
|
354
|
+
import fcntl
|
|
355
|
+
os.makedirs(os.path.dirname(_health_path()), exist_ok=True)
|
|
356
|
+
fh = open(_health_path() + ".lock", "a", encoding="utf-8")
|
|
357
|
+
fcntl.flock(fh, fcntl.LOCK_EX)
|
|
358
|
+
except (ImportError, OSError):
|
|
359
|
+
pass
|
|
360
|
+
try:
|
|
361
|
+
yield
|
|
362
|
+
finally:
|
|
363
|
+
if fh is not None:
|
|
364
|
+
fh.close()
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def _family(provider="", model="", default=""):
|
|
368
|
+
"""The OMP_FAMILIES entry a provider id (omp's auth-store id), else a model id
|
|
369
|
+
(`_vendor`), else `default` runs under; None when none is one."""
|
|
370
|
+
by_provider = {auth[0]: family for family, auth in OMP_AUTH.items()}
|
|
371
|
+
for family in (by_provider.get(str(provider or "").lower()),
|
|
372
|
+
_vendor(str(model)) if model else None, default):
|
|
373
|
+
if family in OMP_FAMILIES:
|
|
374
|
+
return family
|
|
375
|
+
return None
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def note_limit(text, provider="", model="", transient=False, default="", now=None):
|
|
379
|
+
"""Hold down the family a slice stopped on with a limit answer (`text`), and
|
|
380
|
+
re-apply omp's chain, so the next spawn of this session leaves that family
|
|
381
|
+
instead of the next session start's probe moving it (research
|
|
382
|
+
orchestrator-flow-audit S2, F3a). A rate limit (`transient`) with no reset
|
|
383
|
+
named holds HEALTH_RATE, a quota HEALTH_DOWN. Returns the family newly held,
|
|
384
|
+
else None: no family resolves (`_family`), `text` is no limit answer
|
|
385
|
+
(`limit_until`), the family is already held, or TEZGAH_HEALTH_CHECK=0 (the
|
|
386
|
+
suite's switch)."""
|
|
387
|
+
import time
|
|
388
|
+
family = _family(provider, model, default)
|
|
389
|
+
if not family or os.environ.get("TEZGAH_HEALTH_CHECK") == "0":
|
|
390
|
+
return None
|
|
391
|
+
now = time.time() if now is None else now
|
|
392
|
+
until = limit_until(text, now, HEALTH_RATE if transient else HEALTH_DOWN)
|
|
393
|
+
if until is None:
|
|
394
|
+
return None
|
|
395
|
+
with _health_locked():
|
|
396
|
+
data = read_health()
|
|
397
|
+
if family in down_families(now, data):
|
|
398
|
+
return None
|
|
399
|
+
data["down"][family] = {"until": until,
|
|
400
|
+
"reason": " ".join(str(text).split())[:200]}
|
|
401
|
+
if not write_health(data):
|
|
402
|
+
return None
|
|
403
|
+
apply_omp()
|
|
404
|
+
return family
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def other_family_up(text):
|
|
408
|
+
"""Where work a limit stopped on can move: the first family the routing lets
|
|
409
|
+
a chain fall back to (`_chain` under `tp.fallback_policy()`, every slot) that
|
|
410
|
+
is funded and not down (`funded_families`) and that `text` does not name
|
|
411
|
+
(`named_families`), else None. None with routing off or under
|
|
412
|
+
TEZGAH_HEALTH_CHECK=0, so the suite never reads this machine's credentials."""
|
|
413
|
+
if os.environ.get("TEZGAH_HEALTH_CHECK") == "0":
|
|
414
|
+
return None
|
|
415
|
+
mode = omp_mode()
|
|
416
|
+
if mode not in OMP_FAMILIES:
|
|
417
|
+
return None
|
|
418
|
+
funded = funded_families(live=True)
|
|
419
|
+
chained = {sel for slot in SLOTS for sel in _chain(mode, slot, funded)}
|
|
420
|
+
named = named_families(text)
|
|
421
|
+
return next((family for family in funded if family not in named and any(
|
|
422
|
+
sel.startswith(OMP_AUTH[family][0] + "/") for sel in chained)), None)
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def named_families(text):
|
|
426
|
+
"""The OMP_FAMILIES `text` names by an auth-store provider id (a whole word)
|
|
427
|
+
or a model stem (`VENDOR_STEMS`, a word start): `openrouter 403` names `any`,
|
|
428
|
+
`glm-5.3` names `zai`."""
|
|
429
|
+
low = str(text or "").lower()
|
|
430
|
+
return {family for family in OMP_FAMILIES
|
|
431
|
+
if re.search(r"\b%s\b" % re.escape(OMP_AUTH[family][0]), low)
|
|
432
|
+
or any(re.search(r"\b%s" % re.escape(s), low)
|
|
433
|
+
for s, v in VENDOR_STEMS if v == family)}
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def _openrouter_key():
|
|
437
|
+
env, home = tp.PROVIDER_KEYS["openrouter"]
|
|
438
|
+
key = os.environ.get(env, "").strip()
|
|
439
|
+
if key:
|
|
440
|
+
return key
|
|
441
|
+
try:
|
|
442
|
+
with open(os.path.join(tp.HOME, home), encoding="utf-8") as fh:
|
|
443
|
+
return fh.read().strip()
|
|
444
|
+
except OSError:
|
|
445
|
+
return ""
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def probe_family(family, timeout=60):
|
|
449
|
+
"""(up, reason) for one family: None for up when the probe could not tell.
|
|
450
|
+
|
|
451
|
+
`any` asks OpenRouter's key endpoint, which costs no tokens: a key whose
|
|
452
|
+
`limit_remaining` is 0 or less is down. A family omp runs is asked one short
|
|
453
|
+
prompt on that family's cheap row through `omp -p --no-session`; exit 0 is
|
|
454
|
+
up, a limit answer is down (its reset in `reason`), anything else is unknown."""
|
|
455
|
+
if family == "any":
|
|
456
|
+
key = _openrouter_key()
|
|
457
|
+
if not key:
|
|
458
|
+
return None, "no OpenRouter key"
|
|
459
|
+
req = urllib.request.Request(OPENROUTER_KEY,
|
|
460
|
+
headers={"Authorization": "Bearer " + key})
|
|
461
|
+
try:
|
|
462
|
+
with urllib.request.urlopen(req, timeout=15) as resp:
|
|
463
|
+
info = (json.loads(resp.read().decode("utf-8")) or {}).get("data") or {}
|
|
464
|
+
except Exception as exc: # noqa: BLE001 - any network failure is "cannot tell"
|
|
465
|
+
text = str(exc)
|
|
466
|
+
return (False, text) if _LIMITED.search(text) else (None, text)
|
|
467
|
+
left = info.get("limit_remaining")
|
|
468
|
+
if isinstance(left, (int, float)) and left <= 0:
|
|
469
|
+
return False, "OpenRouter key limit exhausted (limit %s, usage %s)" % (
|
|
470
|
+
info.get("limit"), info.get("usage"))
|
|
471
|
+
return True, "OpenRouter key has room"
|
|
472
|
+
exe = tp.omp_bin()
|
|
473
|
+
if not exe:
|
|
474
|
+
return None, "omp not found"
|
|
475
|
+
# TEZGAH_NESTED, as consult and the judge set it: a hook that does load in
|
|
476
|
+
# the child records nothing and refuses nothing
|
|
477
|
+
env = dict(os.environ, PI_CODING_AGENT_DIR=OMP_AGENT, TEZGAH_NESTED="1")
|
|
478
|
+
try:
|
|
479
|
+
# the consult argv: no tools, rules, skills, extensions or title call, so
|
|
480
|
+
# the probe is one short request and fires no nested tezgah hooks
|
|
481
|
+
argv = [exe] + tp.CONSULT_CLIS["omp"][0][1:]
|
|
482
|
+
proc = subprocess.run(argv + ["--model", _selector(family, "cheap"),
|
|
483
|
+
"--max-time=%d" % timeout, "Reply with exactly: OK"],
|
|
484
|
+
capture_output=True, text=True, timeout=timeout + 15,
|
|
485
|
+
env=env, cwd=tp.HOME)
|
|
486
|
+
except (OSError, subprocess.SubprocessError) as exc:
|
|
487
|
+
return None, str(exc)
|
|
488
|
+
text = (proc.stdout or "") + (proc.stderr or "")
|
|
489
|
+
if proc.returncode == 0:
|
|
490
|
+
return True, "answered"
|
|
491
|
+
tail = " ".join(text.split())[-200:]
|
|
492
|
+
return (False, tail) if _LIMITED.search(text) else (None, tail)
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
def refresh_health(mode=None, probe=probe_family, now=None):
|
|
496
|
+
"""Probe every family with a credential except the mode's own (it leads the
|
|
497
|
+
chain whatever its state, and the session is running on it), record the
|
|
498
|
+
downs with their resets, and return the record. A family the probe cannot
|
|
499
|
+
judge keeps the state it had. The probes run unlocked; their results land on
|
|
500
|
+
the record re-read under the lock (`_health_locked`), so a hold a slice's
|
|
501
|
+
stop wrote meanwhile stays."""
|
|
502
|
+
import time
|
|
503
|
+
now = time.time() if now is None else now
|
|
504
|
+
mode = mode or omp_mode()
|
|
505
|
+
seen = {family: probe(family) for family in funded_families(live=False)
|
|
506
|
+
if family != mode}
|
|
507
|
+
with _health_locked():
|
|
508
|
+
down = dict(read_health()["down"])
|
|
509
|
+
for family, (up, reason) in seen.items():
|
|
510
|
+
if up is True:
|
|
511
|
+
down.pop(family, None)
|
|
512
|
+
elif up is False:
|
|
513
|
+
down[family] = {"until": limit_until(reason, now) or now + HEALTH_DOWN,
|
|
514
|
+
"reason": reason[:200]}
|
|
515
|
+
data = {"checked": now, "down": {f: r for f, r in down.items()
|
|
516
|
+
if isinstance(r, dict) and r.get("until", 0) > now}}
|
|
517
|
+
write_health(data)
|
|
518
|
+
return data
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
def health_due(data=None, now=None):
|
|
522
|
+
"""True when the record was never checked, is older than HEALTH_EVERY, or
|
|
523
|
+
holds a down family whose reset has passed since it was checked. Never with
|
|
524
|
+
TEZGAH_HEALTH_CHECK=0 (the test suite's switch, like TEZGAH_UPDATE_CHECK)."""
|
|
525
|
+
import time
|
|
526
|
+
if os.environ.get("TEZGAH_HEALTH_CHECK") == "0":
|
|
527
|
+
return False
|
|
528
|
+
now = time.time() if now is None else now
|
|
529
|
+
data = read_health() if data is None else data
|
|
530
|
+
checked = data.get("checked")
|
|
531
|
+
if not isinstance(checked, (int, float)) or now - checked >= HEALTH_EVERY:
|
|
532
|
+
return True
|
|
533
|
+
return any(isinstance(row, dict) and checked < row.get("until", 0) <= now
|
|
534
|
+
for row in data["down"].values())
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
def start_health():
|
|
538
|
+
"""Stamp the record, then start one detached `tezgah-route --health`, so the
|
|
539
|
+
session starts that follow read a fresh stamp and start nothing. False when
|
|
540
|
+
nothing started."""
|
|
541
|
+
import sys
|
|
542
|
+
import time
|
|
543
|
+
with _health_locked():
|
|
544
|
+
data = read_health()
|
|
545
|
+
data["checked"] = time.time()
|
|
546
|
+
if not write_health(data):
|
|
547
|
+
return False
|
|
548
|
+
route = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))),
|
|
549
|
+
"bin", "tezgah-route")
|
|
550
|
+
kwargs = {"stdin": subprocess.DEVNULL, "stdout": subprocess.DEVNULL,
|
|
551
|
+
"stderr": subprocess.DEVNULL, "close_fds": True}
|
|
552
|
+
if os.name == "posix":
|
|
553
|
+
kwargs["start_new_session"] = True
|
|
554
|
+
else: # pragma: no cover - Windows: no console, outlives its parent
|
|
555
|
+
kwargs["creationflags"] = 0x00000008 | 0x00000200
|
|
556
|
+
try:
|
|
557
|
+
subprocess.Popen([sys.executable, route, "--health"], **kwargs)
|
|
558
|
+
return True
|
|
559
|
+
except OSError:
|
|
560
|
+
return False
|
|
247
561
|
|
|
248
562
|
|
|
249
563
|
def omp_mode(default_selector=None):
|
|
@@ -308,11 +622,14 @@ def _chain(mode, slot, funded, agent=""):
|
|
|
308
622
|
because a session on Opus that ran its subagents on GPT and GLM after one
|
|
309
623
|
Anthropic 429 is the failure the owner reported (2026-10-07). `none`: the
|
|
310
624
|
mode's selector alone. `any`: every other funded family, each on that
|
|
311
|
-
slot's own row
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
(
|
|
315
|
-
|
|
625
|
+
slot's own row. The families omp runs on an account (zai) come before
|
|
626
|
+
OpenRouter, which bills per token - the owner's ask (2026-10-11) was to use
|
|
627
|
+
zai when it is up - and among those the order is rotated by the agent's own
|
|
628
|
+
name (`agent`): identical chains for every agent meant one family's 429
|
|
629
|
+
moved all of them onto the same next family in the same moment, which is the
|
|
630
|
+
cascade the chain exists to break (independent review, 2026-10-01). The
|
|
631
|
+
rotation is deterministic - the same agent resolves the same order on every
|
|
632
|
+
machine - so nothing here is random."""
|
|
316
633
|
policy = tp.fallback_policy()
|
|
317
634
|
families = [mode] + [f for f in OMP_FAMILIES if f != mode and f in funded]
|
|
318
635
|
if policy == "none":
|
|
@@ -327,10 +644,11 @@ def _chain(mode, slot, funded, agent=""):
|
|
|
327
644
|
out.append("openrouter/" + OPENROUTER_ID[model]
|
|
328
645
|
+ (":" + effort if effort else ""))
|
|
329
646
|
return list(dict.fromkeys(out))
|
|
330
|
-
tail = families[1:]
|
|
647
|
+
tail = [f for f in families[1:] if f != "any"]
|
|
331
648
|
if agent and len(tail) > 1:
|
|
332
649
|
shift = sum(ord(c) for c in agent) % len(tail)
|
|
333
|
-
|
|
650
|
+
tail = tail[shift:] + tail[:shift]
|
|
651
|
+
families = families[:1] + tail + [f for f in families[1:] if f == "any"]
|
|
334
652
|
return [_selector(family, slot) for family in families]
|
|
335
653
|
|
|
336
654
|
|
|
@@ -552,7 +870,11 @@ def apply_omp(remove=False):
|
|
|
552
870
|
owns.update(owns_chains)
|
|
553
871
|
merged_chains = dict(chains)
|
|
554
872
|
for key in flat_chains:
|
|
555
|
-
role = key.
|
|
873
|
+
role = key.rsplit(".", 1)[1]
|
|
874
|
+
# an earlier release wrote the chain under `fallbackChains.<role>`, a key
|
|
875
|
+
# omp never reads (the `split(".", 1)` of the flat key); it is tezgah's
|
|
876
|
+
# own and goes
|
|
877
|
+
merged_chains.pop("fallbackChains." + role, None)
|
|
556
878
|
value = new_chains.get(key)
|
|
557
879
|
if value is None:
|
|
558
880
|
merged_chains.pop(role, None)
|
|
@@ -863,40 +1185,84 @@ def _in_order(routes, kids):
|
|
|
863
1185
|
return [stem for _ts, stem in kids]
|
|
864
1186
|
|
|
865
1187
|
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
1188
|
+
# How far apart, in seconds, two children of one task call start: an omp call
|
|
1189
|
+
# of 11 workers opened their ledgers over 2 seconds (ledger 01a12277). A second
|
|
1190
|
+
# task call within that gap would read as part of the first - ponytail: a model
|
|
1191
|
+
# turn between two calls takes longer than this.
|
|
1192
|
+
FAN_OUT_GAP = 2
|
|
1193
|
+
|
|
1194
|
+
# How long after its route a route's first worker may start: tezgah-route runs
|
|
1195
|
+
# right before the task call. The joined omp routes (ledgers 01a12277, 01a12bf1)
|
|
1196
|
+
# started theirs 39 to 1296 s later; 1296 s is a route made beside another and
|
|
1197
|
+
# run after it finished. Past this a same-type child proves nothing.
|
|
1198
|
+
ROUTE_GAP = 1800
|
|
1199
|
+
|
|
1200
|
+
|
|
1201
|
+
def _fan_out(kids):
|
|
1202
|
+
"""The children of the first task call in sorted [(ts, key)]: each started
|
|
1203
|
+
within FAN_OUT_GAP of the one before."""
|
|
1204
|
+
out = kids[:1]
|
|
1205
|
+
for kid in kids[1:]:
|
|
1206
|
+
if kid[0] - out[-1][0] > FAN_OUT_GAP:
|
|
1207
|
+
break
|
|
1208
|
+
out.append(kid)
|
|
1209
|
+
return out
|
|
1210
|
+
|
|
1211
|
+
|
|
1212
|
+
def _join(routes, kids):
|
|
1213
|
+
"""[[child key]] per route, [] where unjoined; `kids` is [(ts, key, type)],
|
|
1214
|
+
type None when no row typed the child.
|
|
1215
|
+
|
|
1216
|
+
With no child typed, order over all of them is the only evidence
|
|
1217
|
+
(`_in_order`). Otherwise each route agent's routes join that agent's
|
|
1218
|
+
children: a route owns the first fan-out of its type after it and before
|
|
1219
|
+
the next route of its type - later same-type spawns are ones no route sent.
|
|
1220
|
+
It owns nothing when the route before it of its type had no worker yet
|
|
1221
|
+
(both routes' workers may be in this one fan-out), when the fan-out started
|
|
1222
|
+
more than ROUTE_GAP after it, or when an untyped child started inside its
|
|
1223
|
+
span (that child could be its worker). A child of an
|
|
1224
|
+
unrouted type is left out. Whatever is not proven stays unjoined."""
|
|
1225
|
+
if kids and all(kind is None for _ts, _key, kind in kids):
|
|
1226
|
+
keys = _in_order(routes, [(ts, key) for ts, key, _k in kids])
|
|
1227
|
+
return [[key] for key in keys] if keys else [[] for _r in routes]
|
|
1228
|
+
out = [[] for _r in routes]
|
|
1229
|
+
untyped = [ts for ts, _key, kind in kids if kind is None]
|
|
878
1230
|
for agent in {fields.get("agent") for _ts, fields in routes}:
|
|
879
1231
|
mine = [i for i, (_ts, fields) in enumerate(routes) if fields.get("agent") == agent]
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
1232
|
+
theirs = sorted((ts, key) for ts, key, kind in kids if kind == agent)
|
|
1233
|
+
for n, i in enumerate(mine):
|
|
1234
|
+
start = routes[i][0]
|
|
1235
|
+
end = routes[mine[n + 1]][0] if n + 1 < len(mine) else float("inf")
|
|
1236
|
+
if n and not any(routes[mine[n - 1]][0] <= ts < start for ts, _key in theirs):
|
|
1237
|
+
continue
|
|
1238
|
+
batch = _fan_out([kid for kid in theirs if start <= kid[0] < end])
|
|
1239
|
+
if batch and batch[0][0] - start <= ROUTE_GAP \
|
|
1240
|
+
and not any(start <= ts <= batch[-1][0] for ts in untyped):
|
|
1241
|
+
out[i] = [key for _ts, key in batch]
|
|
884
1242
|
return out
|
|
885
1243
|
|
|
886
1244
|
|
|
1245
|
+
# A fan-out's outcome is its most telling first check: one failed worker fails
|
|
1246
|
+
# the route, so a fan-out never hides a failure behind a sibling's pass.
|
|
1247
|
+
OUTCOME_ORDER = ("fail", "pass", "ran", "none")
|
|
1248
|
+
|
|
1249
|
+
|
|
887
1250
|
def route_report(ledgers):
|
|
888
1251
|
"""({(via, tier): counts}, counts for Jev routes below the static tier) over
|
|
889
1252
|
{ledger stem: rows}.
|
|
890
1253
|
|
|
891
|
-
A routed worker runs in its own session, so its checks
|
|
892
|
-
whose `spawned` row names the parent session the
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
1254
|
+
A routed worker on omp and opencode runs in its own session, so its checks
|
|
1255
|
+
are in its own ledger, whose `spawned` row names the parent session the
|
|
1256
|
+
route row was written under. On Claude and Codex it runs under its parent's
|
|
1257
|
+
session id: its rows are in the parent's ledger with `agent` = its id, and
|
|
1258
|
+
the `subagent_start` row (SubagentStart's agent_id, agent_type in `detail`)
|
|
1259
|
+
types it. A route joins the children `_join` can prove are its workers, and
|
|
1260
|
+
the outcome is their first check (`pass`, `fail`, `ran`, `none`; over a
|
|
1261
|
+
fan-out, OUTCOME_ORDER's first). A route it cannot prove - an unrouted
|
|
1262
|
+
spawn, a tie, a host that writes no child row - is `unjoined`: the parent's
|
|
1263
|
+
own next check is never taken for the worker's, and neither is a guess. The
|
|
1264
|
+
second count is the V2-as-default question: Jev picking under the static
|
|
1265
|
+
table's tier, and how often that first attempt failed (docs/models.md)."""
|
|
900
1266
|
def counts():
|
|
901
1267
|
return {"routes": 0, "pass": 0, "fail": 0, "ran": 0, "none": 0, "unjoined": 0}
|
|
902
1268
|
def spawn_rows(rows):
|
|
@@ -906,7 +1272,11 @@ def route_report(ledgers):
|
|
|
906
1272
|
for stem, rows in ledgers.items():
|
|
907
1273
|
link = next((r for r in rows if r.get("kind") == "spawned" and r.get("parent")),
|
|
908
1274
|
None)
|
|
909
|
-
|
|
1275
|
+
# Cursor's row is written from the parent's subagentStart under the
|
|
1276
|
+
# child's `subagent_id`; that the child's own calls carry this id is not
|
|
1277
|
+
# documented, so the link counts only once a row of the child's landed
|
|
1278
|
+
if link and not (link.get("host") == "cursor"
|
|
1279
|
+
and all(r.get("kind") == "spawned" for r in rows)):
|
|
910
1280
|
children.setdefault(ti._slug(link["parent"]), []).append(
|
|
911
1281
|
(link.get("ts") or 0, stem, link.get("agent")))
|
|
912
1282
|
groups, below = {}, counts()
|
|
@@ -918,15 +1288,27 @@ def route_report(ledgers):
|
|
|
918
1288
|
# orchestrator shares the parent's TEZGAH_SESSION, so its worker names
|
|
919
1289
|
# that parent while the row typing the worker is in the orchestrator's
|
|
920
1290
|
# ledger. Another session's task names never type this one's children.
|
|
921
|
-
# A name two calls typed differently is untyped (None)
|
|
1291
|
+
# A name two calls typed differently is untyped (None).
|
|
922
1292
|
spawned_as = {}
|
|
923
1293
|
for r in spawn_rows(rows) + [r for _ts, kid, _a in children.get(stem, [])
|
|
924
1294
|
for r in spawn_rows(ledgers[kid])]:
|
|
925
1295
|
if spawned_as.setdefault(r["child"], r["agent"]) != r["agent"]:
|
|
926
1296
|
spawned_as[r["child"]] = None
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
1297
|
+
# a child key is (ledger stem, agent id): "" for a whole ledger; an
|
|
1298
|
+
# empty type (a Cursor subagentStart with no subagent_type) is no type
|
|
1299
|
+
kids = [(ts, (kid, ""), spawned_as.get(name, name) or None)
|
|
1300
|
+
for ts, kid, name in children.get(stem, [])]
|
|
1301
|
+
# a Claude plugin agent's type is plugin-scoped (`tezgah:tezgah-cheap`)
|
|
1302
|
+
kids += [(r.get("ts") or 0, (stem, r["agent"]),
|
|
1303
|
+
str(r.get("detail") or "").rsplit(":", 1)[-1] or None)
|
|
1304
|
+
for r in rows if r.get("kind") == "subagent_start"
|
|
1305
|
+
and isinstance(r.get("agent"), str) and r["agent"]]
|
|
1306
|
+
for (_ts, fields), keys in zip(routes, _join(routes, kids)):
|
|
1307
|
+
got = {_outcome(ledgers[kid] if not agent else
|
|
1308
|
+
[r for r in ledgers[kid] if r.get("agent") == agent
|
|
1309
|
+
and r.get("kind") != "subagent_start"])
|
|
1310
|
+
for kid, agent in keys}
|
|
1311
|
+
outcome = next((o for o in OUTCOME_ORDER if o in got), "unjoined")
|
|
930
1312
|
bins = [groups.setdefault((fields["via"], fields["tier"]), counts())]
|
|
931
1313
|
static = fields.get("static")
|
|
932
1314
|
if fields["via"] == "jev" and static in TIERS and \
|