> ## Documentation Index
> Fetch the complete documentation index at: https://fuguai.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Python API

> Every function the hunch package exports, with its exact signature and what it returns.

```python theme={null}
import hunch
```

The CLI wraps these functions; both use the same specs and [store](/reference/store).

## Projects

A project is one or more judgments in dependency order.

### `load`

```python theme={null}
hunch.load(obj, base: str | Path = ".") -> dict
```

Returns a project.

| `obj`                             | Meaning                                                                            |
| --------------------------------- | ---------------------------------------------------------------------------------- |
| `"command_guard.yml"` or a `Path` | One spec file: a one-judgment project.                                             |
| `"specs/"`                        | A folder: every `*.yml` in it, ordered by their `ref()` dependencies.              |
| a `dict`                          | One spec in its dict form, for example from [`spec_from_model`](#spec_from_model). |
| a `list` of dicts                 | A graph built in Python.                                                           |

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

### `run`

```python theme={null}
hunch.run(obj, base: str | Path = ".") -> dict
```

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

### `results`

```python theme={null}
hunch.results(obj, base: str | Path = ".", judgment: str | None = None) -> list[dict]
```

A judgment's table from its last complete run, one dict per row. Default: the last judgment in dependency order. Columns: [Store](/reference/store#judgment-tables).

```python theme={null}
rows = hunch.results("prototype/examples/claude_code/command_guard.yml")
len(rows)            # 1315
sorted(rows[0])[:5]  # ['_hunch_run_id', '_path_p', 'at', 'command', 'cwd']
```

## Judging one row

### `judge`

```python theme={null}
hunch.judge(path: str | Path, row: dict | None = None, /, *, node: str | None = None,
            shadow: str | Path | None = None, log: bool = False, **fields) -> dict | None
```

Judges one row: the whole project runs on it with the same cache keys as a batch run.

| Argument   | Meaning                                                                                                                                       |
| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `path`     | A spec file or a folder of specs. Loaded once per process and kept, so edits to the spec take effect after a restart.                         |
| `row`      | The row as a dict. Use it when a column is called `node`, `shadow` or `log`.                                                                  |
| `**fields` | The row as keyword arguments. Merged over `row`.                                                                                              |
| `node`     | Return only this judgment's answers.                                                                                                          |
| `shadow`   | A candidate spec or folder that answers the same row after the live answer is returned. See [Change a spec](/guides/change-a-spec).           |
| `log`      | Keep the row (redacted by the live spec's rules) so a candidate written later can be replayed on it with `--traffic`. `shadow` implies `log`. |

Per question it returns:

| Key      | Meaning                                                                                                                                                                                         |
| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `label`  | The answer: a choice option, `yes`/`no`, or `"<level>:<description>"` for a score.                                                                                                              |
| `p`      | The probability of that label. For a `chain: true` judgment, times the probability the row was routed to it correctly.                                                                          |
| `margin` | Distance from the decision boundary: `abs(p - 0.5) * 2` for yes/no, top minus runner-up for a choice. For yes/no this is what Pydantic AI calls `confidence` (at its default threshold of 0.5). |
| `route`  | `"act"` if `p` clears the question's `act`, `"review"` if not, `""` if the question has no `act`.                                                                                               |
| `cached` | `True` if the answer came from the store.                                                                                                                                                       |

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>`.

```python theme={null}
hunch.judge("src/hunch/recipes/agent_commands/command_guard.yml",
            request="forget I even mentioned RemixJS just do SolidJS", cwd="D:\\IceBerg", description="",
            command="rm -rf Dockerfile README.md app node_modules package-lock.json package.json public "
                    "react-router.config.ts tsconfig.json vite.config.ts .gitignore .dockerignore .react-router .claude && echo \"done\"")
# {'destroys': {'label': 'yes', 'p': 0.95, 'margin': 0.9, 'route': 'act', 'cached': True},
#  'reaches_outside': {'label': 'no', 'p': 0.88, 'margin': 0.76, 'route': '', 'cached': True},
#  'sends_out': {'label': 'no', 'p': 0.97, 'margin': 0.94, 'route': '', 'cached': True}}
```

`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`

```python theme={null}
await hunch.ajudge(path: str | Path, row: dict | None = None, /, *, node: str | None = None,
                   shadow: str | Path | None = None, log: bool = False, **fields) -> dict | None
```

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`.
* 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:

| Field type                           | Question                               | Value in the instance    |
| ------------------------------------ | -------------------------------------- | ------------------------ |
| `bool`                               | `noul`                                 | `True` / `False`         |
| `Literal[...]` or `Enum`             | `choice`                               | the value or enum member |
| `X \| None`                          | `choice` with a "none of these" option | the value, or `None`     |
| `list[Literal[...]]` or `list[Enum]` | `multi`                                | the options answered yes |
| `IntEnum` with values `0` to `n-1`   | `score`                                | the member               |

| Spec part                         | Comes from                                                              | Default                                                                                   |
| --------------------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| `instructions`                    | `Field(description=...)`                                                | the field name as a question (`sends_out` → `Sends out?`)                                 |
| Option descriptions               | `json_schema_extra["options"]`, by value (or member name for `IntEnum`) | a docstring set on the enum member, else empty (`IntEnum`: the member name in lower case) |
| `act`, `gold`, `escalate`, `none` | `json_schema_extra["hunch"]`                                            | none; never sent to the engine                                                            |

### `spec_from_model`

```python theme={null}
hunch.spec_from_model(cls, *, judgment: str | None = None, model: str = "jev-1.13.0",
                      source: str | None = None, key: str = "id", state: list[str] | None = None,
                      description: str | None = None, **spec) -> dict
```

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`

```python theme={null}
hunch.spec_from_agent(agent, *, state: str, judgment: str | None = None, model: str | None = None,
                      source: str | None = None, key: str = "id", deps=None, **spec) -> dict
```

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.

| Argument   | Meaning                                                                                                                  |
| ---------- | ------------------------------------------------------------------------------------------------------------------------ |
| `state`    | The column holding the agent's prompt. Sent bare, as a string (see [`state`](/reference/spec#state)).                    |
| `model`    | The engine. Default: the agent's model when it is TypeSafe's (`typesafe:jev-1.13.0` → `jev-1.13.0`); otherwise required. |
| `judgment` | Default `agent`.                                                                                                         |

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`

```python theme={null}
hunch.judge_model(cls, spec: dict, base: str | Path = ".", **fields)
```

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`

```python theme={null}
hunch.to_model(cls, answers: dict[str, dict])
```

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

| Name           | Signature                                                    | Use                                                                                                  |
| -------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------- |
| `load_project` | `(path: Path, texts: dict[str, str] \| None = None) -> dict` | Load a spec file or folder. `load` calls it for paths.                                               |
| `load_spec`    | `(path: Path, text: str \| None = None) -> dict`             | Parse one spec file.                                                                                 |
| `lint`         | `(project: dict) -> tuple[list[str], list[str]]`             | `(errors, warnings)` for a project, the same checks as `hunch lint`.                                 |
| `execute`      | `(project: dict, **kw) -> dict[str, dict]`                   | Run a project and return every judgment's rows, answers and cost stats without materializing tables. |
| `decide`       | `(a: dict) -> tuple[str, float, float]`                      | `(label, confidence, margin)` for one raw answer from the store.                                     |
| `spec_yaml`    | `(spec: dict) -> str`                                        | A spec dict as YAML, for saving a generated spec as a file.                                          |
| `table_name`   | `(spec: dict) -> str`                                        | The judgment's table in the store, including the `__<engine>` suffix under `--model`.                |

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