Skip to content

fix!: make replays strict, record every failure, and fix the names and docs that a third review found - #34

Merged
osolmaz merged 1 commit into
mainfrom
fix/third-review
Oct 28, 2026
Merged

osolmaz merged 1 commit into
mainfrom
fix/third-review

Conversation

@osolmaz

@osolmaz osolmaz commented Oct 28, 2026

Copy link
Copy Markdown
Owner

Summary

A third independent review of switch after #33 found 37 more places where the code did something other than what its docs say.
The worst were in replay: a replay could end early and still say that it matched, and the replay command exited with code 0 when the case did not repeat its trace.
This change makes replays fail whenever they do not repeat the trace, gives every case a result and a readable trace however it fails, and fixes the names and docs that disagreed with the code.
It also writes down that only the fields of the decision request and the decision result take the decision prefix.

What Changed

Replay now checks everything that it can check, and a run records every failure where a reader of the run folder looks for it.
The other changes are small corrections of names, types and docs.

  • Replay. A replay fails when the domain gives no choices where the recorded run had some, and when the trace is of another domain or case; the replay command then exits with code 1. A changed definition is noted in the replay's trace. Replay carries the trace's start entry, so run takes the recorded limits itself, and RunOptions.limits goes. A fork counts only the steps that have a pick and a state. run --state-from needs only the earlier steps to have ended.
  • Trace. The start entry has the definition's hash and the case id. An ended entry records why the steps ended before the answers, and the answers entry, with the answers or why they failed, ends the run; the end entry goes. A choices entry says how many choices the limit cut. A thrown entry records the error that stopped a case, in place of the note "the case failed".
  • Run folder. result.json gets thrown, and keeps the ending and the answers that a case still had when it threw after its steps, for example in outcome. A case that fails in caseId, load or its earlier trace still gets a result and a trace with its start, and the totals count it. A case that runs again loses its old result first, so the viewer follows it. A set's case must have the id of its folder. --state-from is kept as a full path. The totals sum cacheWrite1h and reasoning too, and call the count requests.
  • Loop. It refuses a choices limit under 2 and a question with the pick's id before it sends any request. Without a view function it sends a too-long request once, since the view cannot get smaller. A label must be one of the pick's own labels, not a name that every object has, such as toString.
  • Types. Image is pi-ai's image with a label, and decisionImages is optional, as pi-ai's images is. outcome gets only the run's result. A choice type's transition gets the picked choice's key, which typedKey makes; legal-judge builds each key once with it. The default definition moves to definitionFor, and limitsOf goes.
  • Viewer. It reads the new trace, shows the error of a case that failed before or after its steps, shows how many choices the limit cut, and keeps notes after the steps with the case.
  • Names. progress and progressLine replace log and the CLI's storyOf. ViewerError and replyTo replace ViewError and the server's answer. In chess, gameAfter, EngineReply and query replace replay, Answer and ask, and the tests drop world. The word test also scans switch's and the viewer's tests.
  • Docs. The README, AGENTS.md, docs/MATH.md and the package READMEs follow. The definition is now described as what a domain lets a run vary; the texts in a domain's code come with its version.

Testing

I ran every check of switch, the viewer and both examples, and played and replayed a chess game with the fake decider.

  • pnpm check: format, lint, typecheck, coverage, DRY and Slophammer with no findings. 68 tests in switch, 35 in the viewer, 74 in chess and 45 in legal-judge.
  • New tests cover each fix: no choices in a replay, a fork, and a replay that asks again; another domain or case; a noted definition; the trace's limits; fork counts after a failed pick and after a failed transition; the limit of choices; the pick id before any request; toString as a label; one request without a view function; the cut count; the start entry; the totals' optional counts; results of cases that throw before, during and after their steps; a missing earlier trace; a set's id; a resolved --state-from; no old result while a case runs again; the replay command's exit code; the viewer's failure lines and cut line; and a transition's key.
  • A chess game played with the fake decider and replayed with --out gave the same 30 steps and outcome, with no requests. The chess harness's replay exits with 1 on a trace of another case, and with 0 on its own.

I made no model calls, and I did not look at the page in a browser this time; the viewer's own tests cover the new lines.

Risks

Traces, results and run folders change form again, so switch reads no run made before this change.

  • A replay that used to pass while the domain gave fewer choices now fails, which is the point, but such a trace has to be run again.
  • A judge file with choices: 1 is now refused; the default judge has 30.
  • The model reads the same requests as before, except that a request without images no longer has an empty images list, which pi-ai treated as no images already.

…d docs that a third review found

- A replay fails when the domain gives no choices where the recorded run had some, when the trace is of another
  domain or case, and the replay command then exits with an error. It notes a changed definition, takes the trace's
  limits from the trace, and a fork counts only steps with a pick and a state.
- The trace starts with the definition's hash and the case id, records why the steps ended before the answers, ends
  with the answers or their failure, keeps how many choices the limit cut, and records the error that stopped a case.
- result.json records that error as `thrown`, and the steps and answers that the case still had. A case that throws
  before it starts, or whose earlier trace is missing, also gets a result and a trace. A case that runs again loses
  its old result first. A set's case must have its folder's id. `--state-from` is kept as a full path.
- The loop refuses a choices limit under 2 and a question with the pick's id before any request, sends a request once
  when there is no view function, and does not take an object's built-in names as labels.
- Images are pi-ai's, with a label, and `decisionImages` is optional. `outcome` gets only the run's result. A choice
  type's transition gets the choice's key, from `typedKey`, which legal-judge now uses. The totals sum every usage
  count and name the requests `requests`.
- Names: `progress`, `progressLine`, `ViewerError`, `replyTo`, chess's `gameAfter`, `EngineReply` and `query`.
- Docs: the README, AGENTS.md (the scope of the `decision` prefix), docs/MATH.md and the package READMEs.
@osolmaz
osolmaz merged commit e62132f into main Oct 28, 2026
1 check passed
@osolmaz
osolmaz deleted the fix/third-review branch October 28, 2026 14:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant