Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,21 @@ below corresponds to one such version.
bounded to its 50-row sample on every engine through the new `Dialect.limited`, where it used to
be bounded only where the row-limit keyword was `LIMIT`. (ACE-114)

- **`reconcile.py` reads any input a person brings, and grades a supplied statement part by part.**
Three new verbs beside `parse`, `diff` and `band`, which are unchanged. `intake` reads the four
shapes the reconcile skill will accept, a list of questions, questions with the SQL the person
trusts, labels with numbers, and labels with numbers and the SQL behind each tile, into one row
shape with the question, the statement and the expected value, any of which may be missing. A CSV
whose third column is SQL now yields a statement instead of a label with SQL glued onto it; a
statement whose label matches a tile joins that tile's row. `ledger` grades every part of a
supplied statement from the files the skill wrote beside it: what happened when it ran, what
`sm prepare` and `sm receipt` said, what `sm join-probes` and `sm filter-values judge` reported, and
the probe CSVs the execution tier returned. Four grades, and only measurement earns `model_gap`; a
join that could not be graded leaves the fan-out check on its aggregate `unresolved`, said out
loud. `findings` writes a run's findings, the person's own defects listed apart, and every row's
ledger. Three shared references describe the row, the ledger and how a supplied statement is run
the way the AI's own SQL runs. Nothing here runs SQL or writes to the semantic model. (ACE-115)

### Fixed

- **A chain of joins is no longer reported as a chasm trap.** The aggregates section flagged two
Expand Down
966 changes: 966 additions & 0 deletions plugins/agami/scripts/reconcile.py

Large diffs are not rendered by default.

99 changes: 99 additions & 0 deletions plugins/agami/shared/evidence-row.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# The evidence row

Shared by `agami-reconcile` (which reads every input through it) and `agami-save-correction` (which
reads a pasted statement the same way). Read this before writing code or prose that consumes what a
person hands over: a screenshot, a spreadsheet, a query they trust, a list of questions, or a mix.

**The person's query is evidence, never the answer.** Every row here is something to check. Nothing in
a row is trusted until the checks in [`part-ledger.md`](part-ledger.md) have run.

## The four shapes, and the one row they all become

Any input reduces to rows of three optional fields plus where it came from:

```json
{"label": "Q3 Revenue", "question": null, "statement": "SELECT SUM(total) FROM orders WHERE q = 3",
"expected": 4200000.0, "raw_value": "$4.2M",
"provenance": {"shape": "d", "source": "the finance dashboard", "file": "tiles.csv", "line": 2,
"graded": null}}
```

| Shape | What the person handed over | `question` | `statement` | `expected` |
|---|---|---|---|---|
| **a** | a list of questions | given | none | none, until the person grades the AI's answer |
| **b** | questions with the SQL they trust | given, or derived from the statement and confirmed | given | produced by running the statement |
| **c** | a dashboard screenshot, or a label-and-number table | a label, turned into a question and confirmed | none | read by code, confirmed by the person |
| **d** | a screenshot or table with the SQL behind each tile | a label | given | read by code, and checked against the statement's own result |

`provenance.shape` is per row. The input's own `shape` is one letter when every row agrees and
`mixed` otherwise.

## How `reconcile.py intake` reads an input

Run it once per input, with every file the person gave:

```bash
python3 "$AGAMI_PLUGIN_ROOT/scripts/reconcile.py" intake --file <path> [--file <path> ...] \
[--source "<the person's own words for where this came from>"] > /tmp/agami-reconcile-rows-<ts>.json
```

Detection is by content, never by asking:

- **A cell that starts with `SELECT` or `WITH`** is a statement. A `.sql` file is statements split on
`;`, and every chunk gets the same test: one that is not a `SELECT` or a `WITH` is skipped with a
reason, never a statement row.
- **A `.txt` or `.md` file, or a file with no commas or tabs,** is one question per line; a line that
starts with `SELECT` or `WITH` is a statement.
- **A `.json` file** is a list of strings (questions) or objects with any of the keys `label`,
`question`, `value`, `sql`, or their common spellings (`metric`, `tile`, `expected`, `statement`,
`query`, `prompt`, and the like).
- **Anything else is CSV.** A header row is recognised by name (`label`, `metric`, `value`,
`expected`, `sql`, `statement`, `question`, and their common spellings), or by the shape
`parse_csv` always recognised: a non-numeric second cell over rows whose second cells are numbers.
Without a header: one cell is a question; two cells are a question and a statement when the second
is SQL, or a label and a number when it parses; three or more cells are a label, a number, and a
statement when one of the rest is SQL. A third cell that is not SQL is glued onto the label as
context, exactly as `parse` has always done.
- **Numbers are parsed by code**, through `parse_value`, never by the AI eyeballing them. The
vision branch of the skill writes what it read to a CSV first, so it goes through the same reader.

**Mixed input merges by label.** A statement whose label matches a tile's label, under a case and
whitespace fold, joins that tile's row and the row's shape becomes `d`. An unmatched statement gets
its own row (shape `b`); an unmatched tile stays a number-only row (shape `c`).

**What is skipped, and what is refused.** A row whose second column is neither a number nor a
statement is skipped with a reason, in `skipped`. A file that does not exist exits `2`. An input with
no question, statement or number anywhere exits `4`. Nothing is guessed.

## The row record the skill keeps

Phase 2d of `agami-reconcile` writes one record per row to `rows.jsonl`. Every key that record has
always had stays, with the same meaning: `label`, `question`, `expected`, `actual`, `delta_pct`,
`match`, `status`, `report_path`, `sql`, `recorded`, `error`. These keys are appended after `error`:

| Key | Holds |
|---|---|
| `provenance` | the block above: `shape`, `source`, `file`, `line`, `graded` (`null`, `right`, `wrong` or `unsure` once the person has graded a shape-a row), and `merged_from` (the file and line of a statement that joined a tile's row by label) |
| `statement` | the person's SQL, verbatim; `null` when they gave none |
| `statement_recorded` | what the person's SQL returned: one cell as `{"columns": [...], "rows": [[v]]}`, or `{"columns": [...], "row_count": n}` for a table. Never the rows of a table |
| `statement_receipt_path` | the receipt of the person's SQL, in the row directory |
| `receipt_path` | the receipt of the AI's SQL |
| `ledger`, `ledger_verdict` | the graded parts and the weakest grade, from `reconcile.py ledger` |
| `comparison` | `{"scalar": <diff>}` or `{"result_set": <compare-results>}` |
| `claims` | the `sm claims` diff between the two statements, when both exist |
| `finding_keys` | the keys of the findings this row contributed to |
| `words` | what the person wrote beside a `wrong` grade when they gave no SQL; `null` otherwise |

## The status a row gets

`status` keeps its three old values and gains two:

| Status | When |
|---|---|
| `match` | the numbers agree within tolerance, and every graded part is `confirmed` (or there was no statement to grade) |
| `match_unverified` | the numbers agree, but a part of the person's statement is not `confirmed`. Never offered in Phase 3e: a match nobody could verify may be luck |
| `mismatch` | the numbers differ and the person's statement has no `query_defect`, so the AI is the likelier culprit |
| `expected_doubtful` | the numbers differ and the person's statement has a `query_defect`, so the expected value itself is in doubt. Kept out of the mismatch tally |
| `error` | the row could not run; `sql` and `recorded` are `null`, as they always were |

`match` (the boolean) stays `reconcile.diff`'s verdict on the numbers alone.
1 change: 1 addition & 0 deletions plugins/agami/shared/file-layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Everything in here is either a secret, an auth file derived from a secret, or pe
| `charts/<profile>/<ts>.html` | Per-query HTML reports |
| `exports/<profile>/<ts>.csv` | Per-query CSV exports |
| `{review,model,examples-validation,eval}/<profile>/<ts>.html` | Per-profile dashboards. The `eval` kind also holds a JSON run artifact per run — the answer key and the generated statement, side by side |
| `reconcile/<ts>/` | One reconcile run: `rows.jsonl`, `ledger.json`, `findings.json`, `query_defects.json`, and `rows/<n>/` holding each supplied statement, its run record, receipts, probe plans and probe CSVs. Question text and SQL, never result rows beyond one recorded cell. See [`part-ledger.md`](part-ledger.md) |
| `serve/`, `tunnels/` | The copied MCP server; SSH tunnel scripts |
| `.duckdb_init_*.sql` | Ephemeral, chmod-600 — federation init files, deleted after the query |

Expand Down
Loading
Loading