Agent-operable by design
gtme describes its whole surface in one JSON document, keeps data on stdout and prose on stderr, and exits with one code per kind of failure
What you'll learn
You're handing gtme to an agent or a script and want to know what it reads, what it can parse, and how it tells a bad file from a missing key.
- what
gtme help --agentcontains and where it comes from - which output is data for a program and which is text for you
- what each exit code means and what an agent does next
- why plan is the check an agent runs after every edit
Ask the binary to describe itself. jq, a small command-line JSON reader (brew install jq), lists the top-level keys of what it prints:
gtme help --agent | jq keys
It prints:
[
"adapters",
"bindings",
"examples",
"exit_codes",
"ledger",
"participants",
"sql_steps",
"verbs"
]
That's one JSON document, printed by the binary you have installed. An agent reads it when it needs to know what gtme can do.
What you just saw
Each key is one part of gtme an agent can use.
| Key | What an agent gets |
|---|---|
verbs | Every command and flag, with a line on what each does |
adapters | What every installed adapter needs and provides, read from its manifest, the file that declares both |
examples | Whole pipeline files to start from |
ledger | The tables and views of the ledger, for writing SQL |
sql_steps | How the sql/* steps, the ones that run SQL against the ledger, read it |
participants | The four commands for answering an agent/* step, a step whose judgment the agent itself supplies |
exit_codes | What each exit code means |
bindings | A pointer to gtme help --bindings, the contract for writing a new adapter |
Here's what the document says demo/enrich, the pretend adapter from See it run, provides:
gtme help --agent | jq '.adapters[] | select(.id == "demo/enrich") | .provides'
It prints:
{
"type": "object",
"additionalProperties": false,
"properties": {
"demo.score": {
"type": "integer",
"minimum": 0,
"maximum": 100
},
"demo.note": {
"type": "string"
}
}
}
That's how an agent knows demo.score exists, and that it's a whole number from 0 to 100, before it writes a step that reads it. The binary builds the document from the adapters it finds each time you call it, so an adapter you install shows up in the next call.
Data on stdout, prose on stderr
Every command writes what you read to stderr and what a program reads to stdout. stdout and stderr are a program's two output streams. A terminal shows both, so you never see the difference. A pipe or a file gets stdout only.
After a run of hello.yaml from See it run, ask for the run's records and discard stderr with 2> /dev/null:
gtme show --run last 2> /dev/null
It prints one JSON object per line. Trimmed to Jane with ...:
{"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","state":"out"}
...
The line that went to stderr was 3 record(s) from run 01M3FMC2CE1H6B17NEJSYG4VJ3, a count for you. What's left, an agent can pipe into jq or save to a file as is. gtme freeze, which prints the exact pipeline a run used, puts its YAML on stdout the same way. The pending listing a participant reads before it answers puts one JSON line per record there.
The receipt is for you, and every number on it is a row an agent can query. The receipt goes to stderr as a table. In See it run, its score row says $0.0300 and its total says (estimated). The same dollars come back from the ledger as JSON:
gtme query "SELECT step_id, SUM(amount_usd) AS usd, basis
FROM costs GROUP BY step_id, basis"
It prints:
{"basis":"estimated","step_id":"score","usd":0.03}
1 rows
The JSON line is on stdout and 1 rows is on stderr. basis says whether the dollars were measured from a vendor's response or estimated from a rate.
One exit code per kind of failure
When a command fails, the exit code says what kind of failure it was, and the message says the fix. An exit code is the number a command ends with, which a script or an agent reads before any text. Every error naming its fix is one of gtme's design principles. In hello.yaml, the out step ends like this:
variables:
score: demo.score
note: demo.note
Change demo.score to demo.scor on the score: line, then plan the file and print the exit code:
gtme plan hello.yaml; echo $?
It prints:
gtme: step "out": needs demo.scor, which no earlier step provides (available: company_domain, demo.note, demo.score, email, full_name, title)
2
The message names the step, the field, and what's available, and nothing went to stdout. Here are the codes, from the same document:
gtme help --agent | jq -c '.exit_codes[]'
It prints:
{"code":0,"means":"ok"}
{"code":1,"means":"other error"}
{"code":2,"means":"validation or contract error: the plan, the file, or a value was refused"}
{"code":3,"means":"auth or credential error"}
{"code":4,"means":"rate-limited by a vendor"}
{"code":5,"means":"network error"}
So what?
An agent picks its next move from the number:
| Code | What the agent does next |
|---|---|
| 2 | Edits the file and plans again. |
| 3 | Asks you for the key the message asks for. You enter it at the gtme secret set prompt, never the agent, per For agents. |
| 4 or 5 | Runs the same command again later. That's safe: finished work is cached, and a delivery with the same key isn't made twice, for the reasons on the receipt page. |
| 1 | Reads the message. Code 1 carries no other hint. |
Exit 0 means the command ran, not that it did anything useful. A run that spends money and sends nothing still exits 0, with the spend on its receipt, so an agent checks the numbers with gtme query before it reports. A run that stops at an agent/* step isn't finished either; Participants covers what the agent does next.
Plan is the check an agent can afford after every edit. Plan resolves every field and credential with no network and no spend. Exit code 0 means every field and credential resolved, and plan's last line says nothing has been spent.
That's it. An agent reads one document, parses stdout, branches on the exit code, and plans before anything spends. If your agent needs telling, this is the block to give it:
Run `gtme help --agent` before writing a pipeline; it is the whole surface.
Read stdout as data and stderr as notes for a person.
Branch on the exit code: 2 fix the file, 3 ask me for the key, 4 or 5 retry later.
Run `gtme plan` after every edit, and show me the cost before any run that spends.
Why it's this way
Most of the document is adapter manifests, so it grows with every adapter you install. With the 16 built-in adapters it's about 44 kilobytes. We'd have an agent pull the one key it needs with jq, as this page does.
An agent works from whatever document is in front of it, so gtme generates that document. A page kept by hand would drift from the binary. The decision record ADR-007 made gtme help --agent print the whole surface from the binary itself. SPEC.md, the file that fixes what gtme does, sets the test: an agent given only this document must be able to write a pipeline that passes gtme plan.
Writing a binding takes a second document. The binding contract is large, and most agents never need it, so ADR-041 moved it to gtme help --bindings. Add a vendor uses it.
The streams and the codes are a contract, so a script can rely on them. SPEC §8 fixes both. The code says whether a command broke. Whether it did anything useful is a question for the ledger.
Where it shows up
- For agents is the agent's way in, with the rules it follows before anything spends or sends.
gtme helpand the CLI reference list every form and the shared exit codes.gtme queryis the lookup page for reading the ledger as JSON.