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
| Door | Needs | Spends | Ends with |
|---|---|---|---|
| 1. Show me | nothing | $0 | a receipt from fixtures, then the top-up receipt |
| 2. My CSV | one model key | cents, on the model | your rows judged and written, then the cache receipt |
| 3. My stack | vendor keys | vendor credits, gated | a dry-run receipt a human reads, then one armed run |
| 4. Add a vendor | nothing | $0 | a 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.
| Pattern | Shape |
|---|---|
qualify-group-send | a cheap qualifier ⇒ group; a gated sender from the group |
email-waterfall | finder A → finder B → verifier, falling through on the cache |
account-shape | companies judged, people gated by their company, a brief per account, bounded outreach |
events-cron | a CSV a receiver appends to, run on a schedule, replays absorbed |
posts-to-engagers | people → 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
- Never arm. A command without
--simulateor--dry-runon a pipeline whose deliver step reaches a live target (door 3) is run by the human, after reading the dry-run receipt. Door 2's target is a local CSV; its armed run spends on the model only, and the human has read the line above it that says so. - Never handle a key.
gtme secret set KEYprompts the human; do not paste a key on the command line, into a file, or into a chat. - Spend is announced before it happens. Every command above says
what it spends.
gtme planis always $0 and always the first move on a pipeline you edited. - Errors name their fix. Read the message, do the named thing, run
the same command again.
gtme help --agentis the reference for anything the message does not settle. - Stop at the door's "done when." Report the receipt to the human; the next door is theirs to open.
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.