Start here

For humans: this page installs gtme, a single binary, and walks one of four doors to a receipt — a table of what a campaign pipeline did, what it cost, and what it would have sent. Nothing is sent and nothing is spent until a door says so, in plain words, right before the command.

For agents: the paste line is "Follow gtme.run/start.md". This file is the whole instruction set; gtme help --agent is the machine-readable surface when a step needs more. Read the rules at the bottom before the first command. (Until the site is up, the same file is https://raw.githubusercontent.com/elegant-atomics/gtme/main/START.md.)

Install

macOS or Linux, arm64 or amd64. Pick one:

brew install elegant-atomics/tap/gtme     # a prebuilt, checksummed binary
# or: the release tarball — verify against checksums.txt, untar, put gtme on your PATH
# https://github.com/elegant-atomics/gtme/releases/latest
# or: from source (Go 1.24+); also installs the repo's example adapters
git clone https://github.com/elegant-atomics/gtme && cd gtme && ./install.sh

Then:

gtme version      # prints the version
gtme init         # creates ~/.gtme and the ledger; safe to repeat

Nothing here pipes a download into a shell, and nothing phones home.

The four doors

DoorNeedsSpendsEnds with
1. Show menothing$0a receipt from fixtures, then the top-up receipt
2. My CSVone model keycents, on the modelyour rows judged and written, then the cache receipt
3. My stackvendor keysvendor credits, gateda dry-run receipt a human reads, then one armed run
4. Add a vendornothing$0a new adapter that verifies and simulates

Each door is one pipeline file you fetch, and every command below is safe to re-run.

Door 1 — Show me (no keys)

What happens: a whole outbound pipeline — vendor search, AI filter, paid reveal, AI compose, CRM delivery — runs offline. The vendor adapters serve their recorded fixtures, the AI steps answer synthetically and say so in provenance, delivery is held with its merge variables resolved into the receipt. No network, no keys, no spend, nothing persisted.

mkdir -p gtme-start && cd gtme-start
curl -fsSLO https://raw.githubusercontent.com/elegant-atomics/gtme/main/examples/demo.yaml
gtme run demo.yaml --simulate

The first receipt is the door's proof: a step table with in, out, cached, cost and avoided columns, a SIMULATED banner, one estimated charge on the reveal step, the campaign check skipped and saying so, and the held record with its variables rendered. Run it again if you like: a simulated run executes against a throwaway copy of the ledger and persists nothing, so the receipt is identical and the command is safe to repeat forever.

The second receipt is the top-up — what a re-run saves — and it needs a ledger that persists, so it comes from a second file that runs armed with zero keys: three fictional people, the binary's own synthetic enrichment at a stated pretend price of $0.01 each (its values say synthetic in the note field, and every dollar it prints is labelled demo/enrich), a SQL filter, and a CSV delivery to a file beside it.

curl -fsSLO https://raw.githubusercontent.com/elegant-atomics/gtme/main/examples/cache.yaml
curl -fsSLO https://raw.githubusercontent.com/elegant-atomics/gtme/main/examples/contacts.csv
gtme run cache.yaml            # 3 scored at $0.01 each, 1 kept, out.csv written
gtme run cache.yaml            # again: 3 cached, $0.0300 avoided, 0 delivered

The second run's receipt reads cached 3 and avoided $0.0300 on the enrichment, 0 out on the delivery, and avoided via cache in the total line. Then look at what the ledger kept:

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

Done when: a SIMULATED receipt from demo.yaml, then two receipts from cache.yaml where the second shows cached 3 and a dollar amount in avoided, exit code 0 each time.

Door 2 — My CSV (one model key)

What happens: your CSV of people is read, an AI filter keeps the ones that fit a prompt you wrote, an AI compose writes two intro lines for each, and the result is written to a CSV beside the input. Nothing leaves the machine except the model calls. The second run re-judges nobody: the receipt shows what the cache saved and delivers nothing twice.

You need a CSV with a header row, and an Anthropic API key. The human enters the key; it is stored in ~/.gtme/secrets, never in a pipeline file, never in a shell history line.

gtme secret set ANTHROPIC_API_KEY     # prompts, no echo — the human types it
curl -fsSLO https://raw.githubusercontent.com/elegant-atomics/gtme/main/examples/my-csv.yaml

Edit my-csv.yaml: set path: to the CSV, and under columns: map the canonical names (full_name, email, title, company_domain) to your header names. Headers that already match auto-map; unmapped headers are kept as csv.<header>. Then rewrite the two prompts for your campaign.

gtme plan my-csv.yaml              # $0: checks the mapping and the contracts
gtme run  my-csv.yaml --simulate   # $0: the shape of the output, from fixtures
gtme run  my-csv.yaml              # spends on the model; writes out.csv
gtme run  my-csv.yaml              # again: cached, nothing delivered twice

plan names a header it cannot find and lists the ones it saw; fix columns: and plan again. Start with a slice — the first twenty rows in a second file — before the whole list; a run scoped small exercises the whole chain at minimal cost.

Done when: out.csv holds one row per kept record with first_line and ps_line, and the second receipt shows cached above zero on both AI steps, a dollar amount in avoided, and 0 out on the deliver step.

Door 3 — My stack (vendor keys)

What happens: the same pipeline as door 1, live — Apollo searches, the filter judges, Apollo reveals only past the filter, the compose writes, and an Instantly campaign receives. Every rung of the ladder before the last spends nothing on delivery; the last is armed by a human.

The Instantly campaign named in demo.yaml (with: { campaign: ... }) must exist; edit the name to one of yours. The dry run reads it and reports whether it is fit to send to — active, with a sequence that references every variable the step sends — before a single record moves.

gtme secret set APOLLO_API_KEY
gtme secret set ANTHROPIC_API_KEY
gtme secret set INSTANTLY_API_KEY
gtme plan demo.yaml                 # $0: contracts, credentials, cost estimate
gtme run  demo.yaml --dry-run       # spends on search, reveal and the model; delivers nothing

Stop here. The dry-run receipt lists every record that would be delivered, with its variables resolved. A human reads it. Only a human runs the next line, and only after saying so:

gtme run demo.yaml                  # armed: delivers; re-runs deliver nothing twice
gtme run demo.yaml                  # again: the cache receipt, zero re-delivery

examples/apollo-to-instantly.yaml is the same shape at campaign size, with a LinkedIn enrichment in the middle; its header says which four keys it wants.

Done when: a dry-run receipt was read by a human, one armed run delivered, and the run after it shows avoided on the paid steps and 0 out on delivery.

Door 4 — Add a vendor (no keys)

What happens: you write an adapter for an API gtme does not ship — as one YAML file, no code — verify it offline against a recorded response, and simulate a pipeline through it. Most vendor APIs are CRUD over HTTP, and for those this is the whole job.

gtme help --bindings > bindings.json     # the contract: schema, discovery path, a reference binding
mkdir -p ~/.gtme/adapters/<vendor>-<operation>

Write ~/.gtme/adapters/<vendor>-<operation>/binding.yaml against the schema, modelled on the reference — its id is <vendor>/<operation>. Record one real, sanitized response per request the binding makes into fixtures/conformance.json beside it. Then:

gtme adapters verify <vendor>/<operation>   # schema + fixtures, offline; prints the hosts and credentials it would use
gtme run my-pipeline.yaml --simulate        # a pipeline that says `use: <vendor>/<operation>`, served from the fixtures

The moment the integration needs conditionals, multi-call workflows, an OAuth dance or computation, it is not a binding: gtme help --agent documents the process-adapter protocol, and CONTRIBUTING.md in the repo has the checklist for sharing either kind.

Done when: verify passes and a simulated receipt shows records coming out of the new adapter.

Five patterns, frozen

Past the doors, the shapes campaigns actually take — each a bundle (gtme freeze --bundle output: the exact pipeline that ran, its bindings with their fixtures, a manifest of hashes) that simulates offline from a clean checkout with no keys and no spend. Each folder's README says what the receipt shows and which rung comes next.

PatternShape
qualify-group-senda cheap qualifier ⇒ group; a gated sender from the group
email-waterfallfinder A → finder B → verifier, falling through on the cache
account-shapecompanies judged, people gated by their company, a brief per account, bounded outreach
events-crona CSV a receiver appends to, run on a schedule, replays absorbed
posts-to-engagerspeople → their posts → who reacted, via two traverses
curl -fsSL https://github.com/elegant-atomics/gtme/archive/refs/heads/main.tar.gz \
  | tar xz --strip-components=2 gtme-main/bundles/email-waterfall
cd email-waterfall
gtme run . --simulate         # $0: hashes verified, served from the fixtures inside

Swap email-waterfall for any pattern above (qualify-group-send and account-shape are folders of bundles, run in order — their READMEs walk it). A bundle refuses to run if a frozen file is edited; the input CSV beside it is not frozen, so put your rows under the same name, or copy pipeline.yaml out and run the copy. bundles/README.md in the repo has the rest.

Rules for the agent

Then

gtme show <email> --provenance     # every fact, who wrote it, when
gtme runs last                     # the receipt, reconstructed
gtme query "SELECT field, value FROM current_fields WHERE ..."
gtme freeze last --bundle DIR      # the run as a self-contained, portable folder

The README is the tour; SPEC.md is the canon; ADAPTERS.md lists what ships. A campaign is a folder under version control — pipelines diff, prompts are commits, and a colleague's campaign is a git pull.


Rendered from START.md in the gtme repo. Raw markdown for agents: gtme.run/start.md.