Skip to main content
Your coding agents’ shell commands land in a table. One query should tell you which of them would have destroyed something, and a data team should be able to build that column in dbt like any other. hunch.sql makes a spec a SQL function in DuckDB: it takes the columns the spec reads, and returns each question’s answer.

Register a spec

hunch hook install copies the command guard into hunch/, with 38 real agent commands a panel labelled. Query them:
The function is named after the judgment and takes the spec’s state columns, in order. It returns a struct with one field per question, each holding what judge() returns for one row: label, p (the probability of that label) and route (act, review, or empty when the question has no act). review means the guard’s bar for acting alone, 0.9, wasn’t met: five of the seven are for a person to look at, and two it is sure of. DuckDB hands the function up to 2,048 rows at a time, and each batch is one hunch run: requests go out together, and every answer is kept in the store like any other. The first query sent Jev 38 requests, one per distinct command, for $0.001 in about a second. Run it again and it takes 0.06 s and asks nothing. hunch run on the same rows afterwards found all 114 answers already there: SQL, batch and judge() share one cache.

It is still measured

The panel’s labels are a column, so checking the function is a query:
37 of 38, the 97.4% that hunch test hunch/command_guard.yml reports for destroys. The function answers with the spec, so everything hunch knows about the spec (its tests, its reviews, what a change would flip) is about this column too.

A budget, not a surprise

select … from big_table asks for every row. max_cost is the most the function may be charged in total, in US dollars, across every query on the connection and every judgment and escalation in it. Each request reserves its worst case before it is sent, so the charge stays under the cap; a query that reaches it fails with DuckDB’s error around hunch’s message, and the answers it got are saved, so the next query over those rows is free. Without max_cost, HUNCH_MAX_COST is the total; with neither, there is no cap. A distilled spec answers on your machine for $0, which is what makes a decision on every row of a large table cheap.

In dbt

With dbt-duckdb, hunch is a plugin. In profiles.yml:
profiles.yml
A model then builds the decisions as columns. Make it a table: a view would store the function call itself, and a connection without hunch (the DuckDB CLI, a BI tool) couldn’t read it.
models/command_risks.sql
dbt run built it in 0.13 s from the store: 38 commands, 7 that destroy something, 9 that send data out, 12 that need a look. From here it is an ordinary table: dbt tests it, documents it, and downstream models join it.

Details

  • Arguments. The columns the specs read, in the order hunch.sql.columns(hunch.load(path)) lists them: every judgment’s state, then any column a where reads.
  • Projects. Register a folder of specs and the function is named after the folder. It returns a struct of judgments, each a struct of questions, a union included. A judgment a row never reached, because its where said no, is NULL. A multi question gives one field per option, topics__refund and so on.
  • Inputs are text. Every argument is VARCHAR, as a CSV cell is. Read CSVs with all_varchar = true: DuckDB otherwise guesses types, and 1.50 comes back as 1.5, a different input with a different cache entry. A NULL argument is sent as an empty string, as an empty cell is by hunch run, so the answers and the cache are the same. A row whose arguments are all NULL gets NULL.
  • Rows repeat. Identical rows are asked once, and a row judged before, by any route, costs nothing.
  • Rows are told apart by position, never by the spec’s key, which can repeat in a table.
  • Inside async code (Jupyter, FastAPI) it works as anywhere else: hunch runs its requests on a worker thread.
  • Install. uv add "hunch-ai[sql]" brings DuckDB, PyArrow and numpy. dbt needs dbt-duckdb as well. Spec paths in the dbt plugin are relative to where dbt runs.