Skip to content

refactor!: follow pi-ai's forms for decision requests, and fix replay, recording and names - #33

Merged
osolmaz merged 1 commit into
mainfrom
refactor/decision-request
Oct 28, 2026
Merged

osolmaz merged 1 commit into
mainfrom
refactor/decision-request

Conversation

@osolmaz

@osolmaz osolmaz commented Oct 28, 2026

Copy link
Copy Markdown
Owner

Summary

switch reshaped what it sends to the decision model and what comes back, and two reviews found 39 more discrepancies, several of which made replays and run --state-from do something other than what the docs say.
This change makes everything to and from the model follow pi-ai's classifier interface, moves replay into the loop so that a replay repeats the recorded run exactly, records every failure and why every case ended, and fixes the names and docs that disagreed with the code.
The plan's sections "The decision request" and "Other discrepancies" in docs/2026-10-28-domains-as-examples.md list each item, and "How the build did them" says where the build chose a way.

What Changed

The view function now gives exactly what the model reads, and nothing changes the request on its way.
Replay no longer pretends to be a model, and a run now records what happened to every case.

  • Decision request. View is { decisionState, decisionImages }, pi-ai's state and images. switch adds decisionQuestions, in pi-ai's form: bool, choice and score questions with criteria, by id. omitted goes. An answer stage gives its own view function in place of extra and json. The view function gets the decider's characters per token.
  • Decision result. The pi-ai decider passes pi-ai's result through: decisionAnswers, decisionUsage with the cost, api, provider, model, stopReason, errorMessage and timestamp, with switch's own tooLong, which replaces the guessed failure, and milliseconds. A choice answer always has its confidence. The API key comes from --api-key-env, or pi-ai finds the provider's own.
  • Replay. The loop takes each recorded pick by its key, with its recorded probabilities, marked repeated, under the trace's limits; it ends where the recorded steps ended, also at a failed pick, and repeats the recorded answers or their failure. run --state-from repeats every step this way, under the earlier run's limits, and asks only the questions. replay --out writes a run folder of one case. A pick question's id is pick, which a domain question cannot use.
  • Recording. result.json records ended. The trace ends with an end entry and keeps no usage, id or parent: requests.jsonl keeps every request and its usage once, with a schema. run.json's totals sum the usage, cost included, and count failed picks and cases that throw. A case that throws gets a result with its error, and a run that goes on runs it again. A run goes on only with the same definition hash.
  • Choices. offerChoices refuses a choice with the stop choice's text. An answer label that names no choice is a failed pick.
  • Names. DomainSetup, setupOf and runnerNamed; RecordedRequest; the viewer's draw hook, .drawing area and "Probability of each pick" chart; chess's piece type and SoundEvent; legal-judge's file format, also in case.json, and SIZES. runnerOf builds the default definition, so chess drops its own.
  • Viewer. It shows a case's answers and errors whether or not the domain gives an outcome, so legal-judge's verdicts show, and it keeps notes after the steps apart. Chess's leftover CSS variables leave the viewer's styles.
  • Examples. Chess's outcome takes ended: "step limit" or "pick failed" for an unfinished game. legal-judge's version drops the judge's hash, and its compare prints the cost. Both versions are 0.5.0.
  • Docs. The README, AGENTS.md, docs/MATH.md, the package READMEs and the plan follow, and AGENTS.md gets the rule that model data follows pi-ai's forms. switch asks for Node 22.18.

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. 53 tests in switch, 34 in the viewer, 74 in chess and 45 in legal-judge.
  • New tests cover each fix: replay by key with a new stop text, a replayed failed pick and failed answers, askAgain asking no picks, a fork past the trace, a run that goes on past the recorded steps, an unknown label, the pick id, the definition check, failures in the totals, a thrown case that runs again, replay --out, the stage view, the viewer's answers without an outcome, and chess's endings.
  • A chess game played with the fake decider, replayed with --out: the same 30 steps and outcome, and no model calls. Its page looks as before in a headless browser, with the chart's new name.

I made no model calls.

Risks

Every saved run, trace and requests file is in the old format, and switch reads none of them: cases are run again from scratch, as before.

  • legal-judge's case.json now names each file's format, so case folders are ingested again.
  • The model reads the same views as before, except that legal-judge's file list says format in place of kind and the pick question's id is pick.
  • A domain written for the old API must change its questions, view function and answer stages to the new forms, as the README shows.

…, recording and names

The view function now gives exactly what the decision model reads, in the
form of pi-ai's classifier interface: decisionState and decisionImages.
switch adds decisionQuestions, in pi-ai's question form with criteria and
ids as keys, and the decider passes the request to pi-ai and its result
back as they are: decisionAnswers, decisionUsage with the cost, the
provider, the stop reason and the error, with switch's own tooLong and
milliseconds. The requests file keeps every decision request and result,
with a schema, and run.json's totals sum the usage. omitted, extra and the
stage's fixed JSON go; an answer stage gives its own view function.

A replay is now part of the loop: it takes each recorded pick by its key,
under the trace's limits, ends where the recorded steps ended, and repeats
the recorded answers or their failure. run --state-from repeats every step
this way and asks the model only the questions. replay --out writes a run
folder of one case.

A run records why its steps ended, in result.json and in the trace's new end
entry, counts failed picks and cases that throw as failed, and compares the
definition's hash when it goes on. The trace keeps no usage, ids or parents.

Names: DomainSetup, setupOf, runnerNamed, RecordedRequest, the pick's id
and purpose "pick", the viewer's draw hook and drawing area, chess's piece
type and sound events, legal-judge's file format and SIZES. runnerOf builds
a domain's definition from its texts and limits by default. Docs follow,
and switch asks for Node 22.18.
@osolmaz
osolmaz merged commit bc54a1e into main Oct 28, 2026
1 check passed
@osolmaz
osolmaz deleted the refactor/decision-request branch October 28, 2026 11:19
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