Skip to main content
The CLI wraps these functions; both use the same specs and store.

Projects

A project is one or more judgments in dependency order.

load

Returns a project. base: where relative source paths in a dict spec resolve. Ignored for paths.

run

hunch run: asks what the store lacks, materializes each table, prints the same lines, returns the project.

results

A judgment’s table from its last complete run, one dict per row. Default: the last judgment in dependency order. Columns: Store.

Judging one row

judge

Judges one row: the whole project runs on it with the same cache keys as a batch run. Per question it returns: The shape depends on the project:
  • One judgment, or node given: {question: answer}.
  • Several judgments: {judgment: {question: answer}}. A judgment whose where excluded this row is None. A union judgment is left out.
  • A type: multi question appears as one yes/no answer per option, named <question>__<option>.
judge uses asyncio.run; inside a running loop use ajudge. With shadow, the candidate runs in a non-daemon thread after judge returns, so a script waits for it at exit.

ajudge

Async judge: same arguments and return. A shadow candidate runs as a background task in the current loop; its failures go to stderr, never raised.

Costs and errors

Both ask the engine for answers the store lacks, except:
  • A cost cap. HUNCH_MAX_COST (read when hunch is imported), or hunch.core.MAX_COST = 0.0 at runtime. If one judgment’s missing answers would cost more, nothing is asked and SystemExit is raised with a message such as intent: would ask 1 answers in 1 requests (~$0.0001), above --max-cost $0.0; nothing asked. The cap holds on what is charged: while asking, a request is sent only if its worst case still fits, and one that doesn’t stops the call with SystemExit (… stopped at --max-cost …), the answers already asked saved.
  • A missing input column raises KeyError, for example "command_guard: state needs ['command']".
Spec errors also raise SystemExit; catch it around judge in long-running code.

Pydantic classes as specs

Needs the pydantic extra. Each field becomes one question:

spec_from_model

A spec dict whose questions are the class’s fields. cls may also be a bare output type such as Literal["spam", "ham"] or bool. That becomes one question named output, asked with description. judgment defaults to the class name in snake case. Any other spec key (tests, redact, where, …) can be passed through **spec.

spec_from_agent

A spec dict whose questions are the ones a Pydantic AI agent (2.50 or later, on a decision model) sends, recorded from Pydantic AI with no model call. The agent runs once on a recording model, with deps if its instructions need them, and stops at its first request. Question names are Pydantic AI’s with . written as __: a list field topics becomes one yes/no question per option, topics__refund, topics__login. act, gold and escalate still go in Field(json_schema_extra={"hunch": {...}}) and apply to every question of that field; they are not sent. judgment defaults to the output class name in snake case (agent for a bare output type). Raises ValueError when the agent asks a route question first (several output types, or tools), has a system_prompt, or has instructions that depend on the prompt (it is recorded twice, with different prompts, to tell), and TypeError when state is not a string. Works from async code: the recording runs on a thread of its own.

judge_model

Judges one row with a spec built by spec_from_model and returns an instance of cls. For a bare output type it returns the value itself.

to_model

Builds an instance of cls from answers shaped {question: {"label": ...}}, converting each label to the type the field declares: bool, the Literal or Enum value, a list for list[...], the IntEnum level, None for “none of these”.

Also exported

Everything else in hunch.core is internal and not a stable API.