See it run

Run a whole outbound pipeline offline with no keys, then run a keyless pipeline twice and watch the second run skip what the first one paid for

What you'll learn

Your first run. You have gtme and no API keys, and you want to see a whole pipeline work before connecting anything.

  • run an outbound pipeline offline from recorded responses
  • read a receipt
  • why a second run costs less and delivers nobody twice

This needs gtme and nothing else. No API keys and no accounts, and it spends $0 and sends nothing. If you don't have the binary yet, install it first.

Make a folder, keep the ledger inside it so demo people stay out of your real one, install the two Apollo adapters the demo names, and run the demo offline:

mkdir -p gtme-start && cd gtme-start
export GTME_LEDGER=./ledger.db
curl -fsSLO https://raw.githubusercontent.com/gtme-run/gtme/main/examples/demo.yaml
gtme adapters add apollo/search apollo/enrich instantly/add-to-campaign
gtme run demo.yaml --simulate

Vendor adapters come from gtme's registry, not the binary. adapters add downloads each one, checks it against its recorded responses, and installs it; it needs no keys.

The output is similar to the following:

simulate: ignoring missing credentials (3 plan problems:
  - step "source": missing credential APOLLO_API_KEY (set it in the environment or run `gtme secret set APOLLO_API_KEY`)
  - step "reveal": missing credential APOLLO_API_KEY (set it in the environment or run `gtme secret set APOLLO_API_KEY`)
  - step "send": missing credential INSTANTLY_API_KEY (set it in the environment or run `gtme secret set INSTANTLY_API_KEY`))
simulate: recorded responses only — no network, no spend, nothing sends, nothing persists
run 01M3FBEHWBNKYCDXYHPW1WNMX1 (demo)
...
run 01M3FBEHWBNKYCDXYHPW1WNMX1 — done (SIMULATED — recorded responses only; nothing sent, nothing persisted)
step    adapter                    in  out  empty  cached  filtered  failed  cost     avoided
source  apollo/search              0   1    -      0       -         -       $0       -
fit     ai/filter                  1   1    -      0       -         -       $0       -
reveal  apollo/enrich              1   1    -      0       -         -       $0.0100  -
lines   ai/compose                 1   1    -      0       -         -       $0       -
send    instantly/add-to-campaign  1   0    -      0       -         -       $0       -
send: preflight skipped — the target is not read under --simulate; --dry-run checks it
send: resolved variables for 1 record(s) — review, then run again without --dry-run to arm:
  nh:ed48d7bfcbdb5e0ce36005a8cec0be0812e91c1f4a4f542bc86eed418173f417
    first_line: "Fixture first line for nh:ed48d7bfcbdb5e0ce36005a8cec0be0812e91c1f4a4f542bc86eed418173f417"
    first_name: "Jane"
total: $0.0100 (estimated) spent

That's a receipt: one row per step, with what went in, what came out, and what it cost. The three "missing credential" lines aren't an error; they're the keys a real run would need. Simulate never touches the network: vendor steps answer from recorded responses, and AI steps return canned text that says so. The $0.0100 on the reveal row is what a real run would have paid, and 1 held on the send row means the email variables were printed and nothing was sent. A simulated run writes nothing down, so running it again gives the same receipt.

The file you just ran

demo.yaml is a whole outbound pipeline in 30 lines. Here it is with the comments stripped:

Search Apollo, keep the people an AI judge says own outbound tooling, and pay for contact details only after the judge says yes. Then write a first line and add them to an Instantly campaign. Each use: names an adapter as vendor/verb. The next section is about two of these lines: cache: 30d on reveal and idempotency: email on send.

Run something twice

The second file runs armed, which means for real: it writes to the ledger file you set. It still spends $0 and sends nothing. hello.yaml reads three fictional people from a CSV, scores them with demo/enrich, keeps anyone scoring 70 or more, and writes the keepers to out.csv in this folder. demo/enrich is gtme's built-in scorer: no vendor, a pretend $0.01 per record, so the receipt has real arithmetic.

  1. Download the pipeline and its CSV:

    curl -fsSLO https://raw.githubusercontent.com/gtme-run/gtme/main/examples/hello.yaml
    curl -fsSLO https://raw.githubusercontent.com/gtme-run/gtme/main/examples/contacts.csv
    
  2. Run it. This is the armed run; the $0.03 on the receipt is the pretend price:

    gtme run hello.yaml
    
    run 01M3FBESW58AW0AZHNBDV97VFX (hello)
    ...
    step    adapter      in  out  empty  cached  filtered  failed  cost     avoided
    source  csv/source   0   3    -      0       -         -       $0       -
    score   demo/enrich  3   3    -      0       -         -       $0.0300  -
    keep    sql/filter   3   1    -      0       2         -       $0       -
    out     csv/deliver  1   1    -      0       -         -       $0       -
    total: $0.0300 (estimated) spent
    
  3. Run it again:

    gtme run hello.yaml
    
    run 01M3FBESX4GP5SGBR0BAEKSWF6 (hello)
    ...
    step    adapter      in  out  empty  cached  filtered  failed  cost  avoided
    source  csv/source   0   3    -      0       -         -       $0    -
    score   demo/enrich  3   0    -      3       -         -       $0    $0.0300
    keep    sql/filter   3   1    -      0       2         -       $0    -
    out     csv/deliver  1   0    -      0       -         -       $0    -
    out: 1 already delivered
    total: $0 spent, $0.0300 avoided via cache (3 records skipped)
    

Read the score row: cached 3, avoided $0.0300. Each person already had a score in the ledger, inside its 30-day window, so the scorer didn't run. Then read the line under the table, out: 1 already delivered. Jane went to out.csv last time under the same email, so she wasn't delivered again; that's idempotency: email. It holds per delivery target, so the same person can still be added to a different campaign on purpose.

The counter is real and the dollars are pretend. With your own rows and a real price behind a step, the avoided column is money you didn't spend.

  1. See what the ledger kept about Jane:

    gtme show jane.doe@acme.com
    
    {
      "deliveries": [
        {
          "created_at": "2026-09-26T17:15:56.685Z",
          "run_id": "01M3FBESW58AW0AZHNBDV97VFX",
          "scope": "out.csv",
          "status": "accepted",
          "target": "csv/deliver"
        }
      ],
      "entity_type": "person",
      "fields": {
        "company_domain": "acme.com",
        "demo.note": "synthetic — demo/enrich called no vendor",
        "demo.score": 100,
        "email": "jane.doe@acme.com",
        "full_name": "Jane Doe",
        "title": "VP Marketing"
      },
      "identity_key": "jane.doe@acme.com",
      "identity_key_tier": "email"
    }
    

    The deliveries entry is the row that stopped her second delivery, and demo.note is the scorer admitting it called no one. gtme show only reads.

When you're done, rm ledger.db out.csv and the demo is gone. Your real ledger at ~/.gtme/ledger.db was never touched.

Next

Your CSV runs this shape over your own contacts with one Anthropic API key. It costs a few cents on the model. If Claude Code is doing the typing, the agent page has the line to paste.