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 have run.
Any input reduces to rows of three optional fields plus where it came from:
{"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.
Run it once per input, with every file the person gave:
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>.jsonDetection is by content, never by asking:
- A cell that starts with
SELECTorWITHis a statement. A.sqlfile is statements split on;, and every chunk gets the same test: one that is not aSELECTor aWITHis skipped with a reason, never a statement row. - A
.txtor.mdfile, or a file with no commas or tabs, is one question per line; a line that starts withSELECTorWITHis a statement. - A
.jsonfile is a list of strings (questions) or objects with any of the keyslabel,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 shapeparse_csvalways 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 asparsehas 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.
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 |
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. A doubtful question_fit (Phase 1.5g: the statement may not answer its question) is such a part, so a sound statement paired with the wrong question is never kept as an example |
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.