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

  1. Ask the ledger what it knows about Jane:

    gtme show jane.doe@acme.com --provenance
    

    The 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. source is 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. grader is who answered, and note is the reason they typed. Fields the file itself declared, like outreach.grade, carry the pipeline's name.

    The title is Marketing Manager because every row here has confidence 1, so the newest one wins. Facts have provenance covers when a lower confidence changes that.

  2. Find the pipeline that wrote the title. Every fact carries its run id, and gtme runs pairs each id with its pipeline:

    gtme runs
    
    run                         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        2
    

    The title's run_id is the crm run. The outreach run is pending because Bob and Carol still wait on a grade.

  3. 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 rows
    

    Rank 1 is what gtme show printed, and the ledger kept the old row. Values print with their own quotes because the ledger stores each one as JSON. Swap title for any field to trace it the same way.

  4. 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 REFERENT with the grade's referent from step 1, here 01M3K5QSF2B2YSA760WXH41RKM. It prints:

    {"pipeline":"outreach","value":"\"VP Marketing\""}
    1 rows
    

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

  5. Before you trust the rest of the export, list every title the run wrote:

    gtme show --run RUN_ID --fields title
    

    Replace RUN_ID with the title's run_id, here 01M3K5QSJE484B2ZQ92KSKATGT. 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
    
  6. 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.csv is the stale source, and the opener and the grade came from it. Correct Jane's title in contacts.csv too, or the next outreach run writes the old title back as the newest value. The outreach run 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/review
    

    Only 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 held
  • FIELD: the field someone asked about, such as title

Next