judge:
Projects
A project is one or more judgments in dependency order.load
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
results raises SystemExit rather than return another spec’s answers, and names the hunch run that writes the table for this one; with the answers cached, that run is free. A judgment that has never run raises too.
Judging one row
judge
Per question it returns:
The shape depends on the project:
- One judgment, or
nodegiven:{question: answer}. - Several judgments:
{judgment: {question: answer}}. A judgment whosewhereexcluded this row isNone. Aunionjudgment is left out. - A
type: multiquestion 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
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 whenhunchis imported), orhunch.settings.MAX_COST = 0.0at runtime. If one judgment’s missing answers would cost more, nothing is asked andSystemExitis raised with a message such asintent: 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 withSystemExit(… stopped at --max-cost …), the answers already asked saved. - A missing input column raises
KeyError, for example"command_guard: state needs ['command']".
SystemExit; catch it around judge in long-running code.
Pydantic classes as specs
Needs thepydantic extra. Each field becomes one question:
spec_from_model
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
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
spec_from_model and returns an instance of cls. For a bare output type it returns the value itself.
to_model
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”.
SQL
Needs thesql extra: uv add "hunch-ai[sql]". The guide walks through it.
hunch.sql.register
path to the DuckDB connection con as a function, and returns its name: the judgment’s, or the folder’s for a project, unless name is given. The function takes the columns the specs read (every judgment’s state, then the columns its where reads, less the answers upstream judgments add), as VARCHAR, in order; hunch.sql.columns(hunch.load(path)) lists them. It returns a struct of questions, each {label, p, route}, or for a project a struct of judgments, NULL where a row never reached one.
max_cost is the most the function may be charged in total on this connection, across every judgment and escalation, in USD; default HUNCH_MAX_COST, else no cap. A hunch.sql.Budget(usd) passed to several register calls is one budget they share. At the cap, the query fails (DuckDB raises InvalidInputException with hunch’s message) and the answers already asked are saved.
dbt
hunch.dbt is a dbt-duckdb plugin: module: hunch.dbt with config: {specs: [...], max_cost: USD} registers each spec on every connection dbt opens, all drawing from one budget.
Also exported
Everything else (
hunch.spec, hunch.execute, hunch.measure and the other modules) is internal and not a stable API. Run settings are hunch.settings.