Interrogate
Find what gtme knows about one person, where each value came from, and why it's current, with gtme show --provenance, and fix a wrong value at its source
What you'll learn
Someone asks why a record says what it says, such as a title that doesn't match the opener you sent, and you need the answer from the ledger.
- how to read every current value of one record with its source, confidence, and run
- how to trace a value to the pipeline and run that wrote it, and see every value the field has held
- what a person's answer records beside its value, and what it was about
- how to fix a wrong value where it came from, and check that gtme show wrote nothing
Goal: everything the ledger knows about one person, from one command. You'll also trace a value to where it came from, and fix it at that source when it's wrong. SPEC.md, the file that fixes what gtme does, states the promise this guide checks in its Interrogate story:
what the system knows about one record is always one command away.
Before you start
You need gtme (Install) and a record whose facts came from more than one place. On your own ledger, go straight to the steps with the person's identity key, usually their email. Otherwise, build the practice record first, and the outputs will match this page.
This guide needs no API keys, spends nothing, and sends nothing. Steps 1 through 5 only read the ledger. Step 6 writes one corrected fact.
Build a practice record
Make a folder with its own ledger, so the practice stays out of your real one, and fetch a three-person CSV:
mkdir -p interrogate && cd interrogate
export GTME_LEDGER=./ledger.db
curl -fsSLO https://raw.githubusercontent.com/gtme-run/gtme/main/examples/contacts.csv
Save this pipeline in the folder as outreach.yaml. The opener step writes each person a line from a template, and grade asks a person to grade each title:
Run it, grade Jane with gtme answer, and run it again to collect the grade (Participants covers the waiting and collecting):
gtme run outreach.yaml
gtme answer outreach grade jane.doe@acme.com --set outreach.grade=A \
--as grader --note "owns the budget"
gtme run outreach.yaml
The last run reports Jane graded and the other two still waiting:
grade: 3 in, 1 out — 2 awaiting human/review; `gtme answer outreach` records, the next `gtme run outreach` collects (or `gtme show --run 01M3K5QSF03CZ4DTX6XYGPFDF8 --pending grade` to read them)
Now a CRM export arrives with a different title for Jane. Save it, and a second pipeline that reads it as crm.yaml, then run that:
cat > crm-export.csv <<'EOF'
Full Name,Email,Title,Company Website
Jane Doe,jane.doe@acme.com,Marketing Manager,acme.com
EOF
name: crm
version: 1
source:
use: csv/source
with:
path: crm-export.csv
columns: { full_name: Full Name, email: Email, title: Title, company_domain: Company Website }
steps: []
gtme run crm.yaml
Jane's record now holds values from three adapters, and her title disagrees with the opener the pipeline wrote for her.
Steps
-
Ask the ledger what it knows about Jane:
gtme show jane.doe@acme.com --provenanceThe output is similar to the following, trimmed with
...:{ "entity_type": "person", "fields": { ... "outreach.grade": { "confidence": 1, "created_at": "2026-09-28T04:52:57.514Z", "note": "owns the budget", "referent": "01M3K5QSF2B2YSA760WXH41RKM", "run_id": "01M3K5QSF03CZ4DTX6XYGPFDF8", "source": "human/review @ grader#9bc1c6eed369", "value": "A" }, "outreach.opener": { "confidence": 1, "created_at": "2026-09-28T04:52:57.444Z", "run_id": "01M3K5QSF03CZ4DTX6XYGPFDF8", "source": "text/compose @ #de9117fbd54a", "value": "Hi Jane Doe, congrats on the VP Marketing role." }, "title": { "confidence": 1, "created_at": "2026-09-28T04:52:57.550Z", "run_id": "01M3K5QSJE484B2ZQ92KSKATGT", "source": "csv/source@1", "value": "Marketing Manager" } }, "identity_key": "jane.doe@acme.com", "identity_key_tier": "email" }Every field prints its current value with its provenance: who wrote it, how sure it was, when, and in which run.
sourceis the adapter and its version. For the grade and the opener, the hash after#identifies the step's definition, so an edited step gets a new hash.graderis who answered, andnoteis the reason they typed. Fields the file itself declared, likeoutreach.grade, carry the pipeline's name.The title is
Marketing Managerbecause every row here has confidence 1, so the newest one wins. Facts have provenance covers when a lower confidence changes that. -
Find the pipeline that wrote the title. Every fact carries its run id, and
gtme runspairs each id with its pipeline:gtme runsrun pipeline status started records in flight 01M3K5QSJE484B2ZQ92KSKATGT crm done 2026-09-28T04:52:57.550Z 1 - 01M3K5QSF03CZ4DTX6XYGPFDF8 outreach pending 2026-09-28T04:52:57.440Z 3 2The title's
run_idis thecrmrun. Theoutreachrun ispendingbecause Bob and Carol still wait on a grade. -
See every value the title has held, ranked the way the ledger ranks them. This query joins each title row to its run, so each row names its pipeline:
gtme query "SELECT f.value, f.source, r.pipeline, f.created_at, f.rank FROM field_value_ranks f JOIN runs r ON r.id = f.run_id WHERE f.field = 'title' AND f.identity_id = (SELECT id FROM identities WHERE identity_key = 'jane.doe@acme.com')"{"created_at":"2026-09-28T04:52:57.550Z","pipeline":"crm","rank":1,"source":"csv/source@1","value":"\"Marketing Manager\""} {"created_at":"2026-09-28T04:52:57.442Z","pipeline":"outreach","rank":2,"source":"csv/source@1","value":"\"VP Marketing\""} 2 rowsRank 1 is what
gtme showprinted, and the ledger kept the old row. Values print with their own quotes because the ledger stores each one as JSON. Swaptitlefor any field to trace it the same way. -
Check what the grade was about. A review records its referent, the id of the exact value it judged:
gtme query "SELECT f.value, r.pipeline FROM field_values f JOIN runs r ON r.id = f.run_id WHERE f.id = 'REFERENT'"Replace
REFERENTwith the grade'sreferentfrom step 1, here01M3K5QSF2B2YSA760WXH41RKM. It prints:{"pipeline":"outreach","value":"\"VP Marketing\""} 1 rowsThe A was for
VP Marketing, and so was the opener. A step that read Jane's title before the export saw the old value, and nothing reruns it by itself. -
Before you trust the rest of the export, list every title the run wrote:
gtme show --run RUN_ID --fields titleReplace
RUN_IDwith the title'srun_id, here01M3K5QSJE484B2ZQ92KSKATGT. It prints one line per record:{"entity_type":"person","fields":{"title":"Marketing Manager"},"identity_key":"jane.doe@acme.com","identity_key_tier":"email","state":"sourced"} 1 record(s) from run 01M3K5QSJE484B2ZQ92KSKATGT -
Fix the value where it came from. gtme has no command that edits a value in place, so a fix is always a new fact from a source you can name.
If the CRM is stale and Jane is still VP Marketing, correct her in the CRM and export again. Here, edit the export and run the pipeline that reads it:
sed -i.bak 's/Marketing Manager/VP Marketing/' crm-export.csv gtme run crm.yaml gtme show jane.doe@acme.com --provenance --fields title{ "entity_type": "person", "fields": { "title": { "confidence": 1, "created_at": "2026-09-28T04:53:08.796Z", "run_id": "01M3K5R4HVVYMHSN2HKDK3C2MH", "source": "csv/source@1", "value": "VP Marketing" } }, "identity_key": "jane.doe@acme.com", "identity_key_tier": "email" }If the CRM was right,
contacts.csvis the stale source, and the opener and the grade came from it. Correct Jane's title incontacts.csvtoo, or the nextoutreachrun writes the old title back as the newest value. Theoutreachrun still pending collects its answers before it reads the CSV again, so answer Bob and Carol first. The next run then prints:opener: 3 in, 1 out, 2 cached, 0 filtered, 0 failed grade: 3 in, 0 out, 2 cached, 0 filtered, 0 failed, 1 awaiting human/reviewOnly Jane's input changed, so only her opener is written again, now with
Marketing Manager, and only her grade is asked for again.
What you have now
Any record's current values, each traced to its adapter, run, and pipeline, and a fix made at the source. gtme show only reads. To check, compare the ledger file's checksum around it:
shasum -a 256 ledger.db
gtme show jane.doe@acme.com --provenance > /dev/null
shasum -a 256 ledger.db
The output is similar to the following:
7270198106267d33af7948617ee2809135e5278733397c3f0b1fd24789ed312d ledger.db
7270198106267d33af7948617ee2809135e5278733397c3f0b1fd24789ed312d ledger.db
The two lines match, so the file didn't change by a byte (SPEC §8, and the decision record ADR-006). On Linux, use sha256sum.
To have Claude Code answer these questions for you, paste this line:
Interrogate IDENTITY_KEY in gtme: run gtme show IDENTITY_KEY --provenance, then for FIELD name the pipeline and run that wrote the current value with gtme runs, list every value FIELD has held from field_value_ranks, and tell me which file or source to fix. Only read the ledger.
Replace the following:
IDENTITY_KEY: the person's email, LinkedIn URL, or any key the record has heldFIELD: the field someone asked about, such astitle
Next
gtme showlists every form of the command, including--runand--pending.gtme querylists every flag of the command the history checks use.- The
field_value_ranksview lists every column the history query reads.