@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/docs/operations.md
CHANGED
|
@@ -224,7 +224,7 @@ After a change to the contract text (`hooks/tezgah_policy.py`,
|
|
|
224
224
|
- `--refresh` does that without a reinstall, for every armed host's static
|
|
225
225
|
block and not only opencode's (`refresh_contract`). opencode has no
|
|
226
226
|
session-start hook, so its plugin runs it once per session
|
|
227
|
-
(`hosts/opencode/plugins/tezgah.js:
|
|
227
|
+
(`hosts/opencode/plugins/tezgah.js:2689-2693`). Until 2026-10-02 it
|
|
228
228
|
re-rendered opencode's files alone and reset the one shared hash, so a rule
|
|
229
229
|
edit left the other static files stale while `--report` went green (audit
|
|
230
230
|
M-5).
|
|
@@ -371,6 +371,7 @@ does not hold.
|
|
|
371
371
|
| `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`) |
|
|
372
372
|
| `tezgah-doctor --clean` | vacuums opencode's database, and only when opencode is not running (`bin/tezgah-doctor::vacuum_db`, `bin/tezgah-doctor::main`); deletes every evidence ledger whose newest row is older than `--retention-days` (default 30), rows, import offsets and old JSONL file together (`hooks/tezgah_store.py::sweep_evidence`), and the turn stamps and used-tool marks last written before then (`hooks/tezgah_store.py::sweep_cache`); and deletes the files not modified for that long - `classify.log`, `context-drops.log`, `debug.log` and the `turns/` and `sessions/` files a running older release still writes - in the cache and its sandbox fallback, keeping the current session's (`$TEZGAH_SESSION`) ledger, stamp and marks whatever their age (`sweep_state`) |
|
|
373
373
|
| `tezgah-doctor --import-evidence` | imports every old `evidence/*.jsonl` file into the cache's `tezgah.db` now, in the cache and its sandbox fallback (`bin/tezgah-doctor::import_evidence`); session start runs the same import detached at most once a day (`hooks/tezgah_store.py::import_later`) |
|
|
374
|
+
| `tezgah-doctor --repair-evidence [--apply]` | counts, per session, the evidence rows an old `evidence/*.jsonl` file put in again when a volume remount gave it another device id, in the cache and its sandbox fallback. Every `imported` row of one file is one import stream, which put the file's first `bytes` in. For a row text, the longest stream's count stays and every other stream's count is a copy: that many of the newest rows go, so a row written to the database directly beside them stays. A session holding a file line fewer times than its streams put it in is left whole and printed for manual review. A file gone from its name, holding another inode or shorter than its longest stream is printed and skipped, not guessed (`bin/tezgah-doctor::evidence_plan`). It writes nothing without `--apply`, which backs `tezgah.db` up to `tezgah.db.bak-<YYYYmmdd-HHMMSS>` through SQLite's backup API, deletes those rows and leaves one import offset per file in one transaction. Before that transaction commits it counts the rows against the plan, and it rolls back and exits 1 when a row it kept is gone or a copy it deleted is still there (`bin/tezgah-doctor::repair_evidence`) |
|
|
374
375
|
| `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
376
|
| `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`) |
|
|
376
377
|
| `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`) |
|
|
@@ -412,7 +413,7 @@ ledger row. A rollback therefore never reads it as a pre-state (`capture_after`,
|
|
|
412
413
|
`hooks/tezgah_snapshot.py::capture_after`). A file outside the repository gets no
|
|
413
414
|
after blob.
|
|
414
415
|
opencode reaches the same call through a process (`noteTaste`,
|
|
415
|
-
`hosts/opencode/plugins/tezgah.js:
|
|
416
|
+
`hosts/opencode/plugins/tezgah.js:2436-2446`).
|
|
416
417
|
|
|
417
418
|
```sh
|
|
418
419
|
touch ~/.config/tezgah/taste-on # arm; rm it to disarm
|
|
@@ -425,7 +426,8 @@ bin/tezgah-taste export # accepted learnings into AGENTS.m
|
|
|
425
426
|
bin/tezgah-taste label; bin/tezgah-taste label ID preference # the calibration sample
|
|
426
427
|
bin/tezgah-taste calibrate # the bound a rule needs
|
|
427
428
|
bin/tezgah-taste gate # the pooled before/after test
|
|
428
|
-
bin/tezgah-taste mine --host omp --stats # sessions, writes, prompts after a writing turn
|
|
429
|
+
bin/tezgah-taste mine --host omp --stats # this repository's sessions, writes, prompts after a writing turn
|
|
430
|
+
bin/tezgah-taste mine --all-projects # every session under ~/Projects, each repo pseudonymised
|
|
429
431
|
bin/tezgah-taste measure --samples S --labels L [--typed] # judge precision/recall
|
|
430
432
|
```
|
|
431
433
|
|
|
@@ -614,6 +616,132 @@ it costs nothing there. On held-out Turkish phrasings of the docs index,
|
|
|
614
616
|
meaning alone found the page in the top three for 0.91 with `embed-mrl` and 0.78
|
|
615
617
|
with `embed-m2v`. The fusion found it for 0.75 and 0.74, BM25 for 0.68.
|
|
616
618
|
|
|
619
|
+
## Plan records: the lock, `landed`, the close commit
|
|
620
|
+
|
|
621
|
+
Every `tezgah-task` writer of a plan record holds an exclusive lock on the
|
|
622
|
+
`<main checkout>/.tezgah/plans` directory across its read, change and write
|
|
623
|
+
(`hooks/tezgah_task.py::plans_lock`). The lock leaves no file behind for the
|
|
624
|
+
private repository to commit. `tick` finds its item's line under the lock
|
|
625
|
+
(`hooks/tezgah_task.py::set_box`). A writer writes a temp file beside the record
|
|
626
|
+
and renames it over the record, so a reader never sees a torn file
|
|
627
|
+
(`hooks/tezgah_task.py::_write`). Windows has no `fcntl`, so writers run unlocked
|
|
628
|
+
there, and the rename alone keeps reads whole. `close NNN done` records
|
|
629
|
+
`landed:`, the main checkout's HEAD, once the reviewed commit has landed there
|
|
630
|
+
(`bin/tezgah-task::landed_head`). Before the merge it prints a warning and
|
|
631
|
+
records nothing. A squash merge and a branch deletion can orphan
|
|
632
|
+
`reviewed_sha:`, but not that commit. When `.tezgah` is a git repository of its
|
|
633
|
+
own, `close` commits the move as `plan: close NNN <slug>`. The commit holds the
|
|
634
|
+
record's old and new paths and nothing else the owner staged
|
|
635
|
+
(`bin/tezgah-task::commit_close`). Every worktree commits into that one
|
|
636
|
+
repository, so another git can hold its `index.lock`. Each private-repository
|
|
637
|
+
git call waits about 1.5 s for the lock (`hooks/tezgah_paths.py::ws_git`). A
|
|
638
|
+
lock still held after that fails the close: exit 1, with the command that
|
|
639
|
+
commits the moved record.
|
|
640
|
+
|
|
641
|
+
`close` then removes each linked worktree whose branch landed and that holds
|
|
642
|
+
nothing else (`bin/tezgah-task::prune_worktrees`). It deletes that branch with
|
|
643
|
+
`git branch -D` (`bin/tezgah-task::drop_branch`). `git branch -d` refuses a
|
|
644
|
+
squash- or rebase-merged branch, and `_landed` has already shown that its work
|
|
645
|
+
is in a landing base (`bin/tezgah-task::_landed`). A branch that has not landed
|
|
646
|
+
stays with its worktree, and the `worktree kept:` line names it as not merged.
|
|
647
|
+
A delete git still refuses prints `branch kept:` with git's reason.
|
|
648
|
+
|
|
649
|
+
## Slice journal: `tezgah-task slice`
|
|
650
|
+
|
|
651
|
+
A delegated slice can die with its parent. A crash, an omp `/clear`, a quota or
|
|
652
|
+
429 answer, or a model or host switch ends the parent session. The child's
|
|
653
|
+
worktree and commits stay, but no one remembers them. The slice journal is that
|
|
654
|
+
memory, and it is host-neutral.
|
|
655
|
+
|
|
656
|
+
Each spawned slice gets one row in `<repo root>/.tezgah/slices/slices.db`, a SQLite
|
|
657
|
+
file opened like the taste store (`hooks/tezgah_task.py::SLICE_SCHEMA`).
|
|
658
|
+
An older release wrote one JSON file per record there. The first open imports each one and deletes it.
|
|
659
|
+
`.gitignore` excludes `.tezgah/`, a private directory, so the journal never reaches a commit.
|
|
660
|
+
The id is the host's own agent name: the omp task `name` or the Codex
|
|
661
|
+
`task_name`. A Claude `Agent`/`Task` input may carry a `name` too. Otherwise the
|
|
662
|
+
id is the first 12 hex digits of the brief's sha1. A record holds the host, the agent
|
|
663
|
+
type, the first 4000 characters of the brief, the parent session and the cwd.
|
|
664
|
+
It also holds the linked worktree the brief names, its branch, and the root's
|
|
665
|
+
HEAD at open. Its `status` is `open`, `done`, `stopped`, `resumed` or `stale`.
|
|
666
|
+
Its `failure` is one of `quota`, `rate_limit`, `aborted`, `timeout`, `cut_off`
|
|
667
|
+
or `error`. `attempts` counts each re-open of the same id
|
|
668
|
+
(`hooks/tezgah_task.py::open_slice`, `hooks/tezgah_task.py::close_slice`).
|
|
669
|
+
Another session's record of the same name is not re-opened under another brief.
|
|
670
|
+
The spawn gets this session's own record of that name, else `<name>-<sha1[:6]>` of its brief.
|
|
671
|
+
`name` keeps the spawned name, and the same brief from another session still re-opens the record
|
|
672
|
+
(`hooks/tezgah_task.py::_own_id`). The record stays in cwd's journal, the one its
|
|
673
|
+
owner's Stop rule, resume block and review guard read. When every absolute path its
|
|
674
|
+
brief names lies in one other repository, `repo` names that repository, and the
|
|
675
|
+
slice line says `(in <name>)` (`hooks/tezgah_task.py::_brief_repo`). A repository's
|
|
676
|
+
own `.tezgah` counts as that repository (`hooks/tezgah_task.py::main_repo`).
|
|
677
|
+
|
|
678
|
+
The slice's own task text (without a batch's shared `context`) decides its
|
|
679
|
+
worktree and its lease. The worktree is the one the task names last, else the
|
|
680
|
+
brief (`hooks/tezgah_task.py::_worktree_in`). `declared` holds the checkout a
|
|
681
|
+
`Worktree:`, `Checkout` or `Cwd:` line declares, which only the main-checkout
|
|
682
|
+
refusal reads (`hooks/tezgah_task.py::_declared_in`). Its `Owns:` lines name
|
|
683
|
+
repository-relative globs, and only that slice may write those paths while its
|
|
684
|
+
record runs (`hooks/tezgah_task.py::owned_globs`, `hooks/tezgah_task.py::owner_of`). A
|
|
685
|
+
Claude background child's record also keeps the launch's `agentId` as `child`,
|
|
686
|
+
which its own calls carry (`hooks/tezgah_task.py::slice_of`). The
|
|
687
|
+
gate holds siblings to the lease (`docs/gate.md`, the Lease rule), and the
|
|
688
|
+
spawn-time warning names two writing slices that share one worktree
|
|
689
|
+
(`hooks/tezgah_task.py::worktree_clash`).
|
|
690
|
+
|
|
691
|
+
The hooks write it. A spawn call - omp `task`, Claude, dsh and Cursor
|
|
692
|
+
`Agent`/`Task`, Codex `spawn_agent`, opencode `task` - opens one record per
|
|
693
|
+
slice (`hooks/tezgah_task.py::spawned`, `hooks/tezgah_task.py::note_spawn`).
|
|
694
|
+
Claude, dsh, Cursor and opencode answer the call once the child ended. So the
|
|
695
|
+
record closes at once (`hooks/tezgah_task.py::classify`). A failed call becomes
|
|
696
|
+
`stopped` with its failure class, and a good one `done`. The omp `task` and Codex
|
|
697
|
+
`spawn_agent` calls answer while the child runs, so the record stays `open`. So
|
|
698
|
+
does a Claude launch sent with `run_in_background` or one whose result names a
|
|
699
|
+
background child. That child's SubagentStop closes it as `done`
|
|
700
|
+
(`hooks/tezgah_task.py::close_child`). An omp
|
|
701
|
+
record settles from the newest child transcript in the parent's session dir.
|
|
702
|
+
omp names a same-name re-dispatch `<name>-2.jsonl`. The last 64 KB of that file
|
|
703
|
+
decide `stopped` or `done` (`hooks/tezgah_task.py::pending`). A record
|
|
704
|
+
nothing moved for six hours closes `stale`, and a background `job` after
|
|
705
|
+
omp's 3600 s deadline. Codex reports no child end tezgah can see, so its
|
|
706
|
+
records end `stale`. Every prompt lists up to three pending records, newest
|
|
707
|
+
first, each with the command that resumes it (`hooks/tezgah_task.py::slice_lines`).
|
|
708
|
+
They are the records this session spawned or nobody owns, such as one opened by hand. The resume block at
|
|
709
|
+
session start and after a compaction adds one line that counts the other
|
|
710
|
+
sessions' records. `tezgah-task slice reconcile` lists them all.
|
|
711
|
+
|
|
712
|
+
| Command | What it does |
|
|
713
|
+
|---|---|
|
|
714
|
+
| `tezgah-task slice open --id ID [--kind agent\|job] [--host H] [--agent A] [--brief TEXT\|--brief-file F] [--parent P] [--cwd PATH]` | records a slice by hand, for a host whose spawn the hooks do not see |
|
|
715
|
+
| `tezgah-task slice done ID` / `slice stop ID [--class C]` | closes a record as done, or as stopped with a failure class. An exact record id answers from any session. A spawned name answers too, and under `TEZGAH_SESSION` only for that session's records and unowned ones (`hooks/tezgah_task.py::find_slice`) |
|
|
716
|
+
| `tezgah-task slice reconcile [--prune]` | lists every pending record with a resume brief. `--prune` also removes landed worktrees and marks those records done. It deletes a landed local branch, a squash-merged one too (`git branch -D`), only when no worktree holds it, it tracks no upstream and its reflog shows a commit of its own; it never deletes main, master or the current branch |
|
|
717
|
+
| `tezgah-task slice resume ID` | prints the native path first: omp `write agent://<id>` while the parent lives, Claude `SendMessage` when the id is an agent name. Otherwise it prints a re-dispatch brief: the original brief plus the last commit, the uncommitted files and "do not redo committed steps". A quota or rate-limit stop is sent to another provider. It sets the status to `resumed` |
|
|
718
|
+
| `tezgah-task slice stats [--window DAYS] [--json]` | counts records per status, failure class and host. A `class+host` signature with 3 or more events is an RSI lever candidate, and so is one with 5% of a window that holds 20 or more slices. The line names the lever |
|
|
719
|
+
| `tezgah-task slice board [--parent P]` | lists the open slices one parent spawned - the session's own, or for a subagent its parent's - newest first: the `Owns:` globs, worktree, branch and last commit of each, bounded to 12 lines (`hooks/tezgah_task.py::board`) |
|
|
720
|
+
| `tezgah-task slice merge-plan [--base REF]` | forecasts every done slice branch that carries unmerged work against REF (HEAD) and against the others with `git merge-tree`, and leaves out a `merge=union` file such as CHANGELOG.md. A branch touching `MANIFEST` or the citation baseline comes last, the rest fewest conflicts first. A branch merge-tree cannot forecast is printed as `not judged` and the exit is 1 (`bin/tezgah-task::merge_plan`) |
|
|
721
|
+
|
|
722
|
+
`tezgah-task review NNN <reviewer> approve` refuses while an open, stopped or
|
|
723
|
+
stale record names that reviewer as its id, agent or spawned name. Under
|
|
724
|
+
`TEZGAH_SESSION` only that session's records and unowned ones count. The refusal
|
|
725
|
+
prints the exact id to resume or close. A record keeps the plan
|
|
726
|
+
that was active when it opened, and only that plan's records count. A record
|
|
727
|
+
with no plan counts when it changed after the plan's checkpoint commit. A
|
|
728
|
+
`resumed` record hands the guard to its re-dispatch's own record
|
|
729
|
+
(`bin/tezgah-task::unfinished_review`).
|
|
730
|
+
The lever map is a closed set. `rate_limit` and `quota` point at the
|
|
731
|
+
`hooks/tezgah_models.py` fallback chain. `cut_off` and `timeout` point at a
|
|
732
|
+
per-slice deadline, `aborted` at the `wip:` commit cadence, a duplicate job at
|
|
733
|
+
journalling background jobs. Plain `error` has no lever: read the transcript.
|
|
734
|
+
|
|
735
|
+
The orchestration text carries the session's rule
|
|
736
|
+
(`hooks/tezgah_policy.py::ORCHESTRATE`, `hooks/tezgah_agents.py::_orch_body`). A
|
|
737
|
+
slice commits `wip: <step>` after each step. The session resumes or re-dispatches
|
|
738
|
+
a stopped slice and never absorbs it inline. `slice board` shows the sibling
|
|
739
|
+
leases and `slice merge-plan` orders the landed branches. `slice reconcile
|
|
740
|
+
--prune` runs before the turn that merged slices ends.
|
|
741
|
+
The `~/.config/tezgah/slices-off` switch
|
|
742
|
+
stops the hooks from writing records (`hooks/tezgah_paths.py::UNCLASSIFIED_SWITCHES`).
|
|
743
|
+
A cwd outside a git repository writes nothing either.
|
|
744
|
+
|
|
617
745
|
## Troubleshooting
|
|
618
746
|
|
|
619
747
|
| Symptom | First check | Likely cause |
|
|
@@ -760,6 +888,8 @@ page exits 1 with the message it always printed (`available()`,
|
|
|
760
888
|
its pins, the fetch and conversion, the reader and `fuse`; the `embed-mrl` and
|
|
761
889
|
`embed-m2v` registry rows that `--enable`, `--disable` and `--features` read.
|
|
762
890
|
`tests/test_embed.py` builds tiny models in the test and pins the rest.
|
|
891
|
+
- `hooks/tezgah_task.py`, `bin/tezgah-task` — the slice journal: the record, the
|
|
892
|
+
spawn parser, reconcile, resume, prune and stats.
|
|
763
893
|
- `hooks/tezgah_paths.py`, `hooks/tezgah_context.py` — the paths and `off()`; the
|
|
764
894
|
marks and the kill-switch filtering.
|
|
765
895
|
- `RELEASING.md`, `README.md`, `AGENTS.md`, `.gitignore`, `tests/test_setup.py` —
|
package/docs/research.md
CHANGED
|
@@ -65,7 +65,9 @@ nothing recorded the ask and `close --limit` accepted any string.
|
|
|
65
65
|
`state.json` `rules` is the line's format version. `init` writes `RULES`, the
|
|
66
66
|
newest set (`hooks/tezgah_research.py::RULES`). Each set keeps its own gate. Rules 2
|
|
67
67
|
are the standards rules. Rules 3 add the ask contract. Rules 4 add the protocol
|
|
68
|
-
sections below. Rules 5 add the scope lock.
|
|
68
|
+
sections below. Rules 5 add the scope lock. Rules 6 add the selection warnings
|
|
69
|
+
("Held-out set, repeats and noise band" below). Rules 7 add the `exploratory`
|
|
70
|
+
method ("Rules 7: exploratory lines" below). A bump never weakens an older set.
|
|
69
71
|
|
|
70
72
|
A `rules` above `RULES` means a later tezgah wrote the line. `check` then reports
|
|
71
73
|
one error that names both numbers and reads nothing else. The writers refuse the
|
|
@@ -121,7 +123,8 @@ ideation moves had no fence. Rules 5 lock a line to its question:
|
|
|
121
123
|
|
|
122
124
|
- `init --in "<covered>" --out "<left alone>"` (repeatable) write `state.json`
|
|
123
125
|
`scope`. The in-scope items are `S1`, `S2`, ... in order. `--method
|
|
124
|
-
qualitative|measured` writes `method
|
|
126
|
+
qualitative|measured|exploratory` writes `method` (`exploratory` from rules 7,
|
|
127
|
+
see "Rules 7: exploratory lines"). `scope_problems`
|
|
125
128
|
(`hooks/tezgah_research.py::scope_problems`) names an empty `scope.in`, an
|
|
126
129
|
empty `scope.out` and a missing method. `check` refuses them past `bootstrap`
|
|
127
130
|
and warns at it.
|
|
@@ -149,11 +152,14 @@ gets the question, its scope and its method in its brief. The research skill's
|
|
|
149
152
|
|
|
150
153
|
| Path | Holds |
|
|
151
154
|
|---|---|
|
|
152
|
-
| `state.json` | the question, the phase (`bootstrap`/`inner`/`outer`/`concluded`), the direction, `method` and `scope` (rules 5, see "Scope lock"), the locked evaluation and its optional second gate (`capability_tolerance`, `counter_metric`), the session events (`PHASES`, `hooks/tezgah_research.py::PHASES`), `rules` (the rule set `init` opened it under, `RULES`), `deliverable` (`kind`, `path`, `ask`, `min_variants`), `supersedes` for a new version of an older line, and `closed` once `close` records a deliberate limit. The phase is the author's declaration; `derived_phase` (`hooks/tezgah_research.py::derived_phase`) reads the *other* authority beside it from the line's own artifacts, and the checker names the two disagreeing |
|
|
155
|
+
| `state.json` | the question, the phase (`bootstrap`/`inner`/`outer`/`concluded`), the direction, `method` and `scope` (rules 5, see "Scope lock"), the locked evaluation and its optional second gate (`capability_tolerance`, `counter_metric`) with its `noise_band`, the optional `held_out` set (rules 6), the `arena` of an exploratory line (`problem`, `budget`, and `evaluator`, `evaluator_sha256`, `metric` for a measurable one - rules 7, see "The arena lock"), the session events (`PHASES`, `hooks/tezgah_research.py::PHASES`), `rules` (the rule set `init` opened it under, `RULES`), `deliverable` (`kind`, `path`, `ask`, `min_variants`), `supersedes` for a new version of an older line, and `closed` once `close` records a deliberate limit. The phase is the author's declaration; `derived_phase` (`hooks/tezgah_research.py::derived_phase`) reads the *other* authority beside it from the line's own artifacts, and the checker names the two disagreeing |
|
|
153
156
|
| `log.md` | the decision log, newest last: one line per decision, experiment, dead end or pivot, with the evidence that drove it |
|
|
154
157
|
| `findings.md` | the four sections every line answers, named by `FINDINGS_SECTIONS` (`hooks/tezgah_research.py::FINDINGS_SECTIONS`) |
|
|
155
158
|
| `claims.jsonl` | one JSON object per row: `statement`, `falsification`, `proof`, `provenance`, `status`, `kind`, `trace` (the in-scope item, rules 5), `scope` (what the claim's numbers were measured on, which may not be wider than the rows it rests on), and `supersedes` when the row replaces an earlier claim |
|
|
156
159
|
| `predictions.jsonl` | one JSON object per row: `commit` (a 40-character sha the row is bound to), `claim` (an id the line holds, or empty when the prediction stands alone), `metric`, `value_before`, `value_after`, `falsifier`, `components` (the keys of the components the change touches, read from `hooks/tezgah_components.py`), and `granted_by` when a human granted what the frozen paths below forbid |
|
|
160
|
+
| `ideas.jsonl` | an exploratory line's ideas, one JSON object per row, appended by `idea` (rules 7, see "Three files") |
|
|
161
|
+
| `stages.jsonl` | an exploratory line's stage moves, one row per move, appended by `stage`; an idea's stage is its newest row |
|
|
162
|
+
| `tournament.jsonl` | an exploratory line's judged pairwise matches, appended by `match` |
|
|
157
163
|
| `experiments/<hypothesis>/protocol.md` | what the change is, what it predicts, what result would falsify it, why - committed before the run |
|
|
158
164
|
| `experiments/<hypothesis>/results.jsonl` | the rows the run produced, one JSON object per non-blank line, each carrying a non-empty `source` and, once declared, a `scope` of `real`, `fixture` or `derived`, and - when that scope is `fixture` - a `fixture` description of what was generated |
|
|
159
165
|
| `experiments/<hypothesis>/analysis.md` | what the rows mean, and which claim they move |
|
|
@@ -168,7 +174,9 @@ gets the question, its scope and its method in its brief. The research skill's
|
|
|
168
174
|
file that exists (`init`, `hooks/tezgah_research.py::init`); `predictions.jsonl` is not
|
|
169
175
|
one of them, because a file that exists without a row is a line that looks like
|
|
170
176
|
it predicted something - it appears with the first `predict` the line records,
|
|
171
|
-
and `check` reads an absent one as no rows rather than as a missing file.
|
|
177
|
+
and `check` reads an absent one as no rows rather than as a missing file. The
|
|
178
|
+
three campaign files work the same way. Each appears with its first `idea`,
|
|
179
|
+
`stage` or `match`. A slug
|
|
172
180
|
is lowercase letters, digits and dashes (`valid_slug`,
|
|
173
181
|
`hooks/tezgah_research.py::valid_slug`), because it is the directory name `check`, `status` and
|
|
174
182
|
`claim` look for.
|
|
@@ -244,6 +252,13 @@ through its seal.
|
|
|
244
252
|
| `tezgah-research seal <slug> [--history-lost] --ack "<owner decision>"` | seals a line under `done/` that was concluded before seals existed: plainly, as `conclude` would, when every experiment's order still checks; with `--history-lost`, with that verdict for every experiment whose order the lost history left undecidable. Refuses a line with a real order violation, a plain seal on any order finding, and a `history-lost` seal with nothing lost (`retro_seal`, `hooks/tezgah_research.py::retro_seal`) | 0, 1 refused, 2 misuse |
|
|
245
253
|
| `tezgah-research import <checkout> [<slug>] [--allow-open "<reason>"]` | brings a line - or every line this checkout lacks - from another checkout's `.tezgah` repository with its history; an open line arrives beside open ones only with `--allow-open` (`import_line`, `hooks/tezgah_research.py::import_line`) | 0, 1 refused, 2 misuse |
|
|
246
254
|
| `tezgah-research next --topic <t> [--flow f] [--json]` | prints every step of a product flow with its status from the topic's files under `.tezgah/analysis/<topic>/`, then the first missing or due step's skill and command; `--flow` picks `metric-dashboard` (the default) or `feature`, and `--json` prints the same steps as data (`next_steps`, `hooks/tezgah_product.py::next_steps`) | 0, 2 unknown flow or no `--topic` |
|
|
255
|
+
| `tezgah-research init <slug> --ask "<the user's words>" --method exploratory --in "<covered>" --out "<left alone>" --arena "<problem>" --budget "<budget>" [--evaluator <path> --metric "<metric>"]` | opens a rules-7 exploratory line and writes the arena into `state.json` (`arena_record`, `hooks/tezgah_research.py::arena_record`). `--ask`, `--in` and `--out` are required as on any rules-5 line. Without `--arena` or `--budget` it is misuse; `--evaluator` and `--metric` go together; the evaluator has to be a file in the repository; the four flags on any other method are misuse. Its next step says to commit the arena before the first idea | 0, 1 refused over open lines, 2 misuse |
|
|
256
|
+
| `tezgah-research idea <slug>` | reads one idea row from stdin and appends it to `ideas.jsonl` under the lock, or refuses it with one reason per problem (`append_idea`, `hooks/tezgah_research.py::append_idea`) | 0 written, 1 refused, 2 misuse |
|
|
257
|
+
| `tezgah-research stage <slug> <idea> <stage> [--evidence "<ref or JSON>"] [--reason "<why>"]` | appends one stage move to `stages.jsonl` under the lock, or refuses it. `--reason` is the kill's reason, and a `known` prior art needs it for the kill appended with it (`append_stage`, `hooks/tezgah_research.py::append_stage`) | 0 written, 1 refused, 2 misuse |
|
|
258
|
+
| `tezgah-research match <slug>` | reads one judged match from stdin and appends it to `tournament.jsonl` under the lock, or refuses it (`append_match`, `hooks/tezgah_research.py::append_match`) | 0 written, 1 refused, 2 misuse |
|
|
259
|
+
| `tezgah-research board <slug> [--json]` | the campaign as its files show it: arena, rows, torn tails, each idea's stage, score and next action, the ideas tested and the next step; writes nothing (`board_text`, `hooks/tezgah_research.py::board_text`) | 0, 2 misuse |
|
|
260
|
+
| `tezgah-research dedup <slug> [--threshold <(0, 1]>] [--json]` | clusters the `proposed` ideas by statement and prints the `stage ... duplicate` command for each member; names the backend and the threshold's source; writes nothing (`dedup_text`, `hooks/tezgah_research.py::dedup_text`). A `--threshold` outside `(0, 1]` is misuse | 0, 2 misuse |
|
|
261
|
+
| `tezgah-research yield [--json] [--all-lines]` | per operator and per generator family, how many ideas reached each stage, the kill reasons, the board-score-to-pilot correlation and the next campaign's operator weights; writes nothing (`yield_text`, `hooks/tezgah_research.py::yield_text`) | 0, 2 misuse |
|
|
247
262
|
|
|
248
263
|
Exit code 2 is always misuse, so a caller can tell it from a line that fails the
|
|
249
264
|
checks (`misuse`, `bin/tezgah-research::misuse`). `check` asks nothing at all - no
|
|
@@ -436,8 +451,9 @@ writes, for a line whose every experiment's order still checks. The ack goes to
|
|
|
436
451
|
|
|
437
452
|
### Moving a line between checkouts
|
|
438
453
|
|
|
439
|
-
|
|
440
|
-
|
|
454
|
+
A repository keeps one `.tezgah`, in its main checkout, and every worktree writes
|
|
455
|
+
there. `import` moves a line out of a worktree `.tezgah` written before plan 097,
|
|
456
|
+
or out of another clone.
|
|
441
457
|
A **copy** of a line, committed in another checkout, lands protocol and results
|
|
442
458
|
in one commit. The copy destroys the proof: `check` refuses it as "one commit
|
|
443
459
|
added both". `tezgah-research import <checkout> [<slug>]` moves the line with
|
|
@@ -487,18 +503,288 @@ in `hooks/tezgah_research.py`, a module the frozen set forbids a prediction's
|
|
|
487
503
|
commit to touch without a human `granted_by` (`FROZEN_PATHS`), so the gate that
|
|
488
504
|
decides which candidate survives is not machinery the candidate can rewrite.
|
|
489
505
|
|
|
490
|
-
What the pair cannot catch: **a tolerance its own author sets wide
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
506
|
+
What the pair cannot catch alone: **a tolerance its own author sets wide
|
|
507
|
+
enough**. A line that writes "capability metric within 5.0 of the baseline" has
|
|
508
|
+
adopted the form of the two-gate rule and none of its force. From rules 6 the band
|
|
509
|
+
is read against a measurement. `evaluation.noise_band` is
|
|
510
|
+
`{"experiment": "<the experiment dir that reran the unchanged baseline>",
|
|
511
|
+
"spread": <the spread that rerun measured>}`. `check` warns in three cases. The
|
|
512
|
+
line declares `capability_tolerance` and no `noise_band`. `noise_band.experiment`
|
|
513
|
+
names no experiment of the line. The tolerance sentence holds a number wider than
|
|
514
|
+
`spread`, so the tolerance passes what noise alone would. A digit inside a name
|
|
515
|
+
such as `p95` is not that number (`_check_selection`,
|
|
516
|
+
`hooks/tezgah_research.py::_check_selection`). Whether the rerun was a fair one
|
|
517
|
+
stays the review's call.
|
|
518
|
+
|
|
519
|
+
## Held-out set, repeats and noise band (rules 6)
|
|
520
|
+
|
|
521
|
+
The measured line `rsi-borrow-screen` found three gaps. Of 27 decisions, 25 select
|
|
522
|
+
a variant and report it on the same cells. Of 23 armed experiments, 13 run once per
|
|
523
|
+
arm. Three lines declare a tolerance, and none of them measured it. Rules 6 add one
|
|
524
|
+
warning for each (`_check_selection`, `hooks/tezgah_research.py::_check_selection`).
|
|
525
|
+
Each stays a warning under `--strict` too, never a refusal (ADR 019):
|
|
526
|
+
|
|
527
|
+
- **Held-out set.** A measured line may name its held-out set in
|
|
528
|
+
`evaluation.held_out` (a non-empty string). It may log each look at that set in
|
|
529
|
+
`held_out_log.jsonl` at the line root, one row
|
|
530
|
+
`{"set", "candidate", "date", "result"}` per look. `check` warns when a
|
|
531
|
+
`decisions/<id>/` holds comparison cells and the line names no `held_out`: the
|
|
532
|
+
variant was selected and reported on the same cells. It also warns when one
|
|
533
|
+
candidate looked at one set more than once: the held-out set became development
|
|
534
|
+
data for it.
|
|
535
|
+
- **Repeats.** An experiment is armed when its `results.jsonl` rows carry `arm` or
|
|
536
|
+
`variant` with two or more values. `check` warns when no row has `repeat`,
|
|
537
|
+
`repeats` or `k` above 1 and its `protocol.md` states no `k:`/`repeats:` above 1.
|
|
538
|
+
An armless experiment is not read.
|
|
539
|
+
- **Noise band.** `evaluation.noise_band`, read in "Two gates" above.
|
|
540
|
+
|
|
541
|
+
A line opened under rules 5 or earlier is not read for any of the three.
|
|
542
|
+
|
|
543
|
+
## Rules 7: exploratory lines
|
|
544
|
+
|
|
545
|
+
A `qualitative` or `measured` line answers a question. Some asks want new ideas,
|
|
546
|
+
methods or approaches instead of an answer. Rules 7 add the method `exploratory`
|
|
547
|
+
for those asks (`hooks/tezgah_research.py::EXPLORE_RULES`,
|
|
548
|
+
`hooks/tezgah_research.py::METHODS`). The line runs one campaign. Many ideas
|
|
549
|
+
enter it, a funnel kills most of them, and a run under a frozen evaluator judges
|
|
550
|
+
the few that remain. One campaign is one line, so "One open line at a time" still
|
|
551
|
+
holds. `scope_problems` (`hooks/tezgah_research.py::scope_problems`) accepts
|
|
552
|
+
`exploratory` only on a rules-7 line. On an older line it names `qualitative` and
|
|
553
|
+
`measured` as the two methods the line can take.
|
|
554
|
+
|
|
555
|
+
### The arena lock
|
|
556
|
+
|
|
557
|
+
`init <slug> --ask "<the user's words>" --method exploratory --in "<covered>"
|
|
558
|
+
--out "<left alone>" --arena "<problem>" --budget "<budget>"` opens
|
|
559
|
+
the line. The arena is the problem in the user's words. The budget says how many
|
|
560
|
+
ideas, pilots and full runs, and how much wall time, the campaign may spend. A
|
|
561
|
+
measurable arena adds `--evaluator <path> --metric "<metric>"`. The two flags go
|
|
562
|
+
together, and the evaluator has to be a file in the repository. A missing arena
|
|
563
|
+
or budget is misuse (exit 2).
|
|
564
|
+
|
|
565
|
+
`arena_record` (`hooks/tezgah_research.py::arena_record`) builds `state.json`
|
|
566
|
+
`arena`. It holds the problem and the budget. A measurable arena also holds the
|
|
567
|
+
evaluator path, the sha256 of the evaluator's bytes and the metric. `init` also
|
|
568
|
+
writes `deliverable: {"kind": "finding"}`. The campaign delivers its staged ideas
|
|
569
|
+
as findings, never as compared variants (`hooks/tezgah_research.py::init`).
|
|
570
|
+
|
|
571
|
+
The lock has three halves:
|
|
572
|
+
|
|
573
|
+
- `arena_problems` (`hooks/tezgah_research.py::arena_problems`) names an absent
|
|
574
|
+
arena, an empty problem or budget, and an evaluator with no sha256 or no metric.
|
|
575
|
+
`check` reads it through `scope_problems`. It refuses these past `bootstrap` and
|
|
576
|
+
warns at it.
|
|
577
|
+
- `_check_arena_order` (`hooks/tezgah_research.py::_check_arena_order`) finds the
|
|
578
|
+
lock commit. That commit is the first one along HEAD whose `state.json` arena
|
|
579
|
+
names a problem and a budget. `ideas.jsonl` and every experiment's
|
|
580
|
+
`results.jsonl` have to enter the history after it. An arena that differs from
|
|
581
|
+
the locked one is a refusal too. A new arena means a new line.
|
|
582
|
+
- `evaluator_problem` (`hooks/tezgah_research.py::evaluator_problem`) hashes the
|
|
583
|
+
evaluator again. While the hash differs from the locked one, the writer and
|
|
584
|
+
`check` refuse every stage row. The candidate a campaign judges must not move
|
|
585
|
+
its judge.
|
|
586
|
+
- `append_stage` (`hooks/tezgah_research.py::append_stage`) also compares the
|
|
587
|
+
working `state.json` arena with the arena at the lock commit
|
|
588
|
+
(`hooks/tezgah_research.py::_arena_lock`). While they differ, it refuses every
|
|
589
|
+
stage row. So a rewritten `evaluator_sha256` does not reopen the campaign.
|
|
590
|
+
|
|
591
|
+
`init` prints the next step: commit the arena with `tezgah-research commit` before
|
|
592
|
+
the first idea. The arena replaces the one locked metric of a measured line.
|
|
593
|
+
`_check_evaluation` (`hooks/tezgah_research.py::_check_evaluation`) and
|
|
594
|
+
`_check_evaluation_order` (`hooks/tezgah_research.py::_check_evaluation_order`)
|
|
595
|
+
skip an exploratory line for that reason.
|
|
596
|
+
|
|
597
|
+
### Three files
|
|
598
|
+
|
|
599
|
+
The campaign lives in three append-only files at the line root. One function
|
|
600
|
+
judges each row type, and the writer and `check` both call it. No file stores a
|
|
601
|
+
status. An idea's stage is its newest `stages.jsonl` row
|
|
602
|
+
(`hooks/tezgah_research.py::campaign_stages`).
|
|
603
|
+
|
|
604
|
+
| File | One row | Writer | Judge |
|
|
605
|
+
|---|---|---|---|
|
|
606
|
+
| `ideas.jsonl` (`IDEAS_FILE`) | `id` (`I<n>`, unused so far), `statement` (two sentences at most), `mechanism`, `trace` (an in-scope item), `operator` (one of `OPERATORS`), `generator` (`{family, model}`), `kill` (the result that would kill it), `date`, and `parents` for a `mutation` (one or more) or a `crossover` (two or more) | `idea <slug>` | `idea_problems` |
|
|
607
|
+
| `stages.jsonl` (`STAGES_FILE`) | `idea`, `stage`, `evidence`, `reason` for a kill, `date` | `stage <slug> <idea> <stage>` | `stage_problems` |
|
|
608
|
+
| `tournament.jsonl` (`TOURNAMENT_FILE`) | `a`, `b`, `judge` (`{family, model}`), `order` (`ab` or `ba`), `winner` (`a`, `b` or `tie`), `rationale` | `match <slug>` | `match_problems` |
|
|
609
|
+
|
|
610
|
+
The field list is `IDEA_FIELDS` (`hooks/tezgah_research.py::IDEA_FIELDS`). The
|
|
611
|
+
moves are `OPERATORS` (`hooks/tezgah_research.py::OPERATORS`). `user` marks an
|
|
612
|
+
idea the person asked for. `PARENT_OPERATORS`
|
|
613
|
+
(`hooks/tezgah_research.py::PARENT_OPERATORS`) sets how many parents a derived
|
|
614
|
+
idea names. An idea outside the arena's scope gets one line under Open questions
|
|
615
|
+
in `findings.md`, never a row.
|
|
616
|
+
|
|
617
|
+
Every append goes through `_append_campaign`
|
|
618
|
+
(`hooks/tezgah_research.py::_append_campaign`). It takes the file lock and reads
|
|
619
|
+
the rows from the locked descriptor. It judges the new row against them, cuts any
|
|
620
|
+
torn tail (`truncate_to_committed`) and writes. A refused append leaves no file
|
|
621
|
+
behind. `check` refuses a committed row that a later version changed or dropped.
|
|
622
|
+
|
|
623
|
+
### The stage funnel
|
|
624
|
+
|
|
625
|
+
`STAGES` (`hooks/tezgah_research.py::STAGES`) gives the order: `proposed`,
|
|
626
|
+
`duplicate`, `prior-art`, `screened`, `piloted`, `promoted`, `confirmed`, and
|
|
627
|
+
`killed`. `stage_problems` (`hooks/tezgah_research.py::stage_problems`) holds
|
|
628
|
+
the moves to it:
|
|
629
|
+
|
|
630
|
+
- The idea row is the `proposed` stage. A stage row that says `proposed` is a
|
|
631
|
+
refusal.
|
|
632
|
+
- A stage moves forward in that order only, or to `killed`. A kill needs
|
|
633
|
+
`--reason`.
|
|
634
|
+
- A stage also needs its prerequisite somewhere in the idea's history
|
|
635
|
+
(`hooks/tezgah_research.py::PREREQUISITES`). `screened` needs a `prior-art`
|
|
636
|
+
row. `piloted` needs `screened`, `promoted` needs `piloted`, and `confirmed`
|
|
637
|
+
needs `promoted`.
|
|
638
|
+
- `killed` is final. A revived idea is a new idea that names the old one as its
|
|
639
|
+
parent.
|
|
640
|
+
- After `duplicate`, the only allowed row is `killed`. The same holds after a
|
|
641
|
+
`known` prior art.
|
|
642
|
+
|
|
643
|
+
Each stage asks for its own evidence. `--evidence` takes a reference or a JSON
|
|
644
|
+
object.
|
|
645
|
+
|
|
646
|
+
| Stage | Evidence | What the judge asks |
|
|
647
|
+
|---|---|---|
|
|
648
|
+
| `duplicate` | `{"of": "<id>", "similarity": <0..1>}` | `of` names another idea of the line |
|
|
649
|
+
| `prior-art` | `{"verdict": "novel\|incremental\|known", "nearest": [...], "delta": "<one sentence>"}` | `nearest` names at least `NEAREST_MIN` (3) notes. `literature/INDEX.jsonl` indexes each one, and not all of them are `agent-report`. `delta` is one sentence (`_prior_art_problems`) |
|
|
650
|
+
| `screened` | none | `tournament.jsonl` holds at least one match that names the idea |
|
|
651
|
+
| `piloted`, `promoted` | `<h>` or `experiments/<h>` | `protocol.md` names the idea id and repeats its `kill` text. `results.jsonl` holds a row. When the check asks git, `protocol.md` is in a commit. If `results.jsonl` is committed too, `protocol.md` entered the history before it (`_run_problems`) |
|
|
652
|
+
| `confirmed` | `{"experiment": "<h>", "set": "<set>"}` | the arena has an evaluator. The experiment differs from every `promoted` row's experiment. The run passes the same `_run_problems` checks. The first `held_out_log.jsonl` row for that set names this idea. Each look of this idea at the set carries a `YYYY-MM-DD` date, and none is dated before the idea's promotion: an earlier look was part of the selection. A look logged after the confirmation changes nothing (`_confirm_problems`) |
|
|
653
|
+
| `killed` | `--reason "<why>"` | a reason |
|
|
654
|
+
|
|
655
|
+
A `known` prior art kills the idea in the same write. `append_stage`
|
|
656
|
+
(`hooks/tezgah_research.py::append_stage`) needs `--reason` for it. It appends
|
|
657
|
+
the prior-art row and its `killed` row under one lock, so no file state holds a
|
|
658
|
+
known idea alive. `check` refuses a `known` prior art with no kill after it.
|
|
659
|
+
|
|
660
|
+
### The tournament is a prior
|
|
661
|
+
|
|
662
|
+
A match is one judge's verdict on a pair. `bradley_terry`
|
|
663
|
+
(`hooks/tezgah_research.py::bradley_terry`) fits a strength per idea from the
|
|
664
|
+
matches. `board` ranks the ideas by it. The score moves no stage. `screened`
|
|
665
|
+
asks only that a match exists. An LLM ranking is a prior, and a run under the
|
|
666
|
+
frozen evaluator is the judge.
|
|
667
|
+
|
|
668
|
+
`_tournament_warnings` (`hooks/tezgah_research.py::_tournament_warnings`) warns
|
|
669
|
+
about thin judging. A pair judged only by the family that generated one of its
|
|
670
|
+
ideas warns, unless both generators' families judged it. A pair judged in one
|
|
671
|
+
presentation order warns. An idea with fewer than two judges warns.
|
|
672
|
+
|
|
673
|
+
### Reports: board, dedup, yield
|
|
674
|
+
|
|
675
|
+
The three reports write nothing and exit 0. None of them is a gate.
|
|
676
|
+
|
|
677
|
+
- `board <slug>` (`hooks/tezgah_research.py::board`) shows the arena and whether
|
|
678
|
+
the evaluator still hashes as locked. It shows each file's row count, each
|
|
679
|
+
idea's rank, stage, score, record, operator, generator family and next action,
|
|
680
|
+
and the campaign's next step. An idea whose prior art is `known` gets the
|
|
681
|
+
`stage ... killed --reason` command as its next action. That resumes a campaign
|
|
682
|
+
whose kill row was torn off.
|
|
683
|
+
- `dedup <slug>` (`hooks/tezgah_research.py::dedup`) clusters the `proposed`
|
|
684
|
+
ideas by statement, at one similarity threshold. The lowest id of a cluster
|
|
685
|
+
represents it. For every other member it prints the `stage ... duplicate`
|
|
686
|
+
command to run. `similarity_matrix` (`hooks/tezgah_research.py::similarity_matrix`)
|
|
687
|
+
uses the static embedding when the feature is on and pinned. Otherwise it reads
|
|
688
|
+
BM25 scores (`hooks/tezgah_rank.py::scores`) as a similarity. Every output names
|
|
689
|
+
the backend and where its threshold came from.
|
|
690
|
+
- `yield` (`hooks/tezgah_research.py::yield_report`) counts, per operator and per
|
|
691
|
+
generator family, how many ideas reached each stage. It lists the kill reasons.
|
|
692
|
+
It gives Spearman's rho between the board score and the pilot outcome
|
|
693
|
+
(`hooks/tezgah_research.py::spearman`). It proposes the next campaign's operator
|
|
694
|
+
weights (`hooks/tezgah_research.py::operator_weights`). `EXPLORE_SHARE` (0.2)
|
|
695
|
+
of each draw stays uniform, so a move with no payoff yet still gets tried. It
|
|
696
|
+
reads the open exploratory lines, or every one with `--all-lines`.
|
|
697
|
+
|
|
698
|
+
### Resume from the files
|
|
699
|
+
|
|
700
|
+
Nothing outside the three files holds campaign state. A fresh session runs
|
|
701
|
+
`board` and goes on from its `next:` line. A writer that dies mid-append can
|
|
702
|
+
leave a fragment past the last newline. `_campaign_rows`
|
|
703
|
+
(`hooks/tezgah_research.py::_campaign_rows`) reads up to the committed boundary
|
|
704
|
+
and reports the fragment's size. `board` prints it on a `resume:` line. The next
|
|
705
|
+
append drops the fragment, so the fix is to run the append that crashed again.
|
|
706
|
+
|
|
707
|
+
### How many ideas were tested
|
|
708
|
+
|
|
709
|
+
Fifty ideas tested at the 5% level yield about 2.5 winners by chance. A reader
|
|
710
|
+
cannot discount what nobody counted. `_check_tested`
|
|
711
|
+
(`hooks/tezgah_research.py::_check_tested`) warns when `to_human/report.md` has
|
|
712
|
+
no line that names `piloted` with the number of ideas that reached it. The same
|
|
713
|
+
holds for `promoted`. A number followed by a full stop counts, as in "reached
|
|
714
|
+
piloted: 2.". `board` prints both numbers on its `tested:` line.
|
|
715
|
+
|
|
716
|
+
### The dedup threshold (two label sets)
|
|
717
|
+
|
|
718
|
+
Si et al. called two seed ideas duplicates at cosine 0.8 on MiniLM. That number
|
|
719
|
+
does not carry to another similarity. Two label sets measured it here.
|
|
720
|
+
|
|
721
|
+
- The fixture: 40 idea-statement pairs, 20 of them duplicates. The author of the
|
|
722
|
+
code labelled them. The acceptance runner records the sweeps as fixture-scoped
|
|
723
|
+
experiments: `dedup-calibration` (BM25) and `dedup-calibration-embed`
|
|
724
|
+
(embed-mrl). Copies stay under `.tezgah/scratch/calibration/`.
|
|
725
|
+
- MRPC: 400 pairs of the Microsoft Research Paraphrase Corpus test set, 200
|
|
726
|
+
paraphrases and 200 not, sampled with seed 87. Human annotators labelled them.
|
|
727
|
+
The pairs are news sentences, and the negatives share many words. At BM25 0.30
|
|
728
|
+
the sample kept every paraphrase and 95% of the negatives. The script and its
|
|
729
|
+
output are `.tezgah/scratch/calib2/run.py` and `out.txt`.
|
|
730
|
+
|
|
731
|
+
| Backend | F1 at 0.8 (fixture / MRPC) | Default | F1 at the default (fixture / MRPC) |
|
|
732
|
+
|---|---|---|---|
|
|
733
|
+
| embed-mrl | 0.62 / 0.64 | 0.65 | 0.90 / 0.705 |
|
|
734
|
+
| BM25 | 0.09 / 0.41 | 0.40 | 0.70 / 0.69 |
|
|
735
|
+
|
|
736
|
+
The default is the threshold, in steps of 0.05, whose worse F1 over the two sets
|
|
737
|
+
is highest. Neither the author's labels nor the news domain picks it alone. On
|
|
738
|
+
MRPC the best F1 beats the always-duplicate baseline (0.667) by about 0.04. The
|
|
739
|
+
defaults are a coarse filter for a session to review, not a judge.
|
|
740
|
+
|
|
741
|
+
`DEDUP_THRESHOLDS` (`hooks/tezgah_research.py::DEDUP_THRESHOLDS`) holds the two
|
|
742
|
+
values. Any other backend keeps `DEDUP_THRESHOLD`
|
|
743
|
+
(`hooks/tezgah_research.py::DEDUP_THRESHOLD`), Si et al.'s 0.8, and the output
|
|
744
|
+
calls it uncalibrated. `--threshold` overrides all of them. Its range is
|
|
745
|
+
`(0, 1]`.
|
|
746
|
+
|
|
747
|
+
### The evidence behind the method
|
|
748
|
+
|
|
749
|
+
- Si et al., arXiv 2409.04109: about 5% of 4,000 generated seed ideas were not
|
|
750
|
+
duplicates (cosine 0.8). The best LLM judge (a Claude-3.5 pairwise ranker)
|
|
751
|
+
agreed with the human ranking 53.3% of the time. Split-half reviewer panels
|
|
752
|
+
agreed 56.1%.
|
|
753
|
+
- The ideation-execution gap, arXiv 2506.20803: after execution, AI ideas lost
|
|
754
|
+
1.98 points overall and human ideas lost 0.63.
|
|
755
|
+
- Gupta and Pruthi, arXiv 2502.16487: 24% of 50 AI research documents
|
|
756
|
+
paraphrased or substantially borrowed earlier work. Plagiarism detectors missed
|
|
757
|
+
them.
|
|
758
|
+
- HindSight, arXiv 2603.15164: novelty as an LLM judges it correlates negatively
|
|
759
|
+
with realized impact (rho = -0.29).
|
|
760
|
+
- Darwin Godel Machine, arXiv 2505.22954: an archive of stepping stones beats
|
|
761
|
+
hill-climbing. The loop also removed its own detection markers and faked test
|
|
762
|
+
logs.
|
|
763
|
+
- ShinkaEvolve, arXiv 2509.19349: parent sampling by fitness and novelty,
|
|
764
|
+
novelty rejection by embedding, and UCB1 over models.
|
|
765
|
+
- POPPER, arXiv 2502.09858: falsification experiments on measurable implications,
|
|
766
|
+
with Type-I error control.
|
|
767
|
+
|
|
768
|
+
The consequences:
|
|
769
|
+
|
|
770
|
+
- An LLM ranking is a prior, never a verdict.
|
|
771
|
+
- A run under the frozen evaluator is the judge.
|
|
772
|
+
- Each idea gets its own prior-art check. No idea reaches `screened` without a
|
|
773
|
+
`prior-art` row (`hooks/tezgah_research.py::PREREQUISITES`).
|
|
774
|
+
- `dedup` and the operator weights keep the ideas diverse.
|
|
775
|
+
- The report always states how many ideas the campaign tested.
|
|
776
|
+
|
|
777
|
+
Rules 1-6, `FROZEN_PATHS` and ADR 009 stay as they were. Predictions and
|
|
778
|
+
components take nothing new. `research-off` covers this method too.
|
|
495
779
|
|
|
496
780
|
## What `check` refuses, and what it only warns about
|
|
497
781
|
|
|
498
782
|
A **refusal** (exit 1) is a guarantee the checker can decide. A **warning** is a
|
|
499
783
|
guarantee it cannot: the artifact is not wrong, it is unverifiable. `--strict`
|
|
500
|
-
turns
|
|
501
|
-
what a session mid-flight must not have, so the
|
|
784
|
+
turns the warnings the listed rules write through `_soft` into a refusal, which is
|
|
785
|
+
what a release gate wants and what a session mid-flight must not have, so the
|
|
786
|
+
default stays `warn`. A warning written outside `_soft` stays one. The section
|
|
787
|
+
below names three groups of them.
|
|
502
788
|
|
|
503
789
|
Refused:
|
|
504
790
|
|
|
@@ -577,6 +863,40 @@ Refused:
|
|
|
577
863
|
nothing, or a finding whose `quote` does not occur verbatim in its `target`
|
|
578
864
|
(`_check_review`, `hooks/tezgah_research.py::_check_review`).
|
|
579
865
|
|
|
866
|
+
On an exploratory line (rules 7), `check` also refuses each of these
|
|
867
|
+
(`_check_exploratory`, `hooks/tezgah_research.py::_check_exploratory`, called
|
|
868
|
+
from `_check_standards`):
|
|
869
|
+
|
|
870
|
+
- an arena that `arena_problems` faults, past `bootstrap`. The faults are no
|
|
871
|
+
arena object, no problem, no budget, and an evaluator with no sha256 or no
|
|
872
|
+
metric (`hooks/tezgah_research.py::arena_problems`).
|
|
873
|
+
- an arena that differs from the one the lock commit holds. The same goes for an
|
|
874
|
+
`ideas.jsonl` or `results.jsonl` that entered the history before that commit
|
|
875
|
+
(`hooks/tezgah_research.py::_check_arena_order`).
|
|
876
|
+
- an idea row that `idea_problems` faults
|
|
877
|
+
(`hooks/tezgah_research.py::idea_problems`). That means a missing field, a
|
|
878
|
+
`generator` that is not `{family, model}`, or an id that is not `I<n>` or
|
|
879
|
+
repeats an earlier one. It also means an operator outside `OPERATORS`, a parent
|
|
880
|
+
no earlier idea carries, or too few parents for a `mutation` or `crossover`. The
|
|
881
|
+
last cases are a statement of more than two sentences and a date that is not
|
|
882
|
+
`YYYY-MM-DD`. A trace to no in-scope item and a missing arena fail it too.
|
|
883
|
+
- a stage row that `stage_problems` faults
|
|
884
|
+
(`hooks/tezgah_research.py::stage_problems`). The funnel above lists the
|
|
885
|
+
moves and the evidence each stage asks for. Every stage row fails while the
|
|
886
|
+
evaluator's hash differs from the locked one
|
|
887
|
+
(`hooks/tezgah_research.py::evaluator_problem`).
|
|
888
|
+
- a stage row that skips its `PREREQUISITES` stage, and any row but `killed`
|
|
889
|
+
after `duplicate` or after a `known` prior art.
|
|
890
|
+
- a `known` prior art with no `killed` row after it.
|
|
891
|
+
- a match row that `match_problems` faults
|
|
892
|
+
(`hooks/tezgah_research.py::match_problems`). That means a side that names no
|
|
893
|
+
idea, an idea matched against itself, or a `judge` that is not
|
|
894
|
+
`{family, model}`. It also means an `order` outside `MATCH_ORDERS`, a `winner`
|
|
895
|
+
outside `MATCH_WINNERS`, or no `rationale`.
|
|
896
|
+
- a campaign file line that does not parse or is not an object
|
|
897
|
+
(`hooks/tezgah_research.py::_campaign_rows`).
|
|
898
|
+
- a committed campaign row that a later version changed or dropped.
|
|
899
|
+
|
|
580
900
|
Warned, and refused under `--strict` - each is the honest state of an artifact
|
|
581
901
|
this layer cannot decide on, not a defect:
|
|
582
902
|
|
|
@@ -674,7 +994,18 @@ this layer cannot decide on, not a defect:
|
|
|
674
994
|
judgement, so no migration can write one for the lines that concluded before
|
|
675
995
|
the rule existed.
|
|
676
996
|
|
|
677
|
-
|
|
997
|
+
On an exploratory line, two more warnings take this class. One is an arena
|
|
998
|
+
fault at `bootstrap`. The other is a campaign file git cannot order against the
|
|
999
|
+
arena lock (`_check_arena_order`).
|
|
1000
|
+
|
|
1001
|
+
Three groups of warnings are **not** promoted by `--strict`. The rules-6
|
|
1002
|
+
three (held-out set, repeats, noise band) stay warnings by ADR 019. The rules-7
|
|
1003
|
+
four stay warnings for the same reason: a tournament is a prior and a count is a
|
|
1004
|
+
disclosure. They are a pair that only a generator family of one of its ideas
|
|
1005
|
+
judged, unless both generators' families judged it. They are a pair judged in one
|
|
1006
|
+
order, and an idea with fewer than two judges (`_tournament_warnings`). The fourth
|
|
1007
|
+
is a `report.md` that does not state how many ideas reached `piloted` and
|
|
1008
|
+
`promoted` (`_check_tested`). The last is a `phase`
|
|
678
1009
|
declared in `state.json` behind the phase the line's own artifacts show
|
|
679
1010
|
(`_check_derived_phase`, `hooks/tezgah_research.py::_check_derived_phase`). The field is the author's
|
|
680
1011
|
declared intent and `_check_report` and `_check_review` read it, so a derivation
|
|
@@ -872,12 +1203,15 @@ armed. It removes the rule, not just the mark - the diff is in `core_split`,
|
|
|
872
1203
|
frozen set they read (`FROZEN_PATHS`), the protocol order and its tracked pair,
|
|
873
1204
|
the protocol's two questions, the concluded report's limits, the `supersedes`
|
|
874
1205
|
relation, `component_report` with the state it reads, `migrate`, `source_run`,
|
|
875
|
-
`init` and `summary
|
|
1206
|
+
`init` and `summary`, and the rules-7 campaign: the arena lock, the three row
|
|
1207
|
+
judges and their writers, `board`, `dedup` and `yield_report`.
|
|
876
1208
|
- `hooks/tezgah_components.py` - the manifest: the one definition of an editable
|
|
877
1209
|
component, the keys a prediction row names and the rules a row's outcome is
|
|
878
1210
|
attributed to.
|
|
879
1211
|
- `bin/tezgah-research` - the CLI, its subcommands and its exit codes.
|
|
880
1212
|
- `hooks/tezgah_product.py` - the product-flow steps and the `next` status reader.
|
|
1213
|
+
- `hooks/tezgah_rank.py` - `scores`, the BM25 reading `dedup` falls back to when
|
|
1214
|
+
the static embedding is off.
|
|
881
1215
|
- `hooks/tezgah_policy.py` - the `RESEARCH` paragraph a session is armed with.
|
|
882
1216
|
- `hooks/tezgah_context.py` - the prompt hint, the missing-orx note, the broken
|
|
883
1217
|
line note and the `research` mark.
|