judge() answers with the same spec and the same store as hunch run.
One row
The user changed their mind about a framework, and the agent’s answer is to delete the project:state. A row the store has never seen makes one engine call, and its answer is then stored for every later caller, batch or app: ask again and the same answers come back with 'cached': True, in a few milliseconds.
The cache works the other way too. Running hunch run over past rows warms it, so repeats of them in the app cost nothing.
Code that compares a label with a name, like == "lost_or_stolen_card", breaks if the option is renamed. List those names in the spec’s exposures and the rename fails lint instead: see apps depend on answer names.
Check route before acting on the label
A label says what the engine answered; route says whether it met the confidence threshold you chose. Check both before your code acts. Give the question an act threshold in the spec:
route:
Because
act lives in the spec rather than in your code, hunch test can check it. Its dial shows how many rows a threshold automates and how many of those are wrong. Changing act asks nothing new, since it is not part of the cache key.
In async code
Inside FastAPI or any event loop, useajudge. judge starts its own loop and cannot run inside one. Here the guard is a service an agent’s hook calls before each command. It returns allow only when every question says no and destroys clears its threshold. The other two questions in this spec have no act threshold, so their labels still need an explicit check.
Sending uncertain answers to a second engine
Areview route doesn’t have to mean a person. A question can hand its uncertain answers to another engine first:
act, the same question goes to the escalate model. Its answer replaces the first only if it clears act itself; otherwise the first stays and routes to review. Behind a distilled model, the escalated answer always replaces it. Both are kept in the store, and the batch table’s destroys_by column records which engine answered.
Capping the cost
A burst of new rows shouldn’t be able to run up a bill:judge raises SystemExit. A spec error raises SystemExit too, and a row missing a state column raises KeyError, so catch both in a long-running service.
judge loads each spec once per process: restart after editing one. To keep rows so that a spec written later can be tried on them, pass log=True; see Change a spec safely.
Measure a Pydantic AI decision
Record the agent’s exact questions, test them on past rows, and inspect changes.