Before you start
Three things, and the quickstart covers all of them:model in it is what goes in
agent.model below.
The whole thing
Run it against any PDF. Standard library only — no SDK, no dependencies, in either language. The TypeScript is ESM: save it as.mts, or put
"type": "module" in your package.json, or the top-level await will not
compile.
Getting your files in
This is the step most pipelines get stuck on, so it is worth being exact.
Base64 from a shell, if you are not in Python:
type: "url" and no file-store id to reference: Gobare does not
fetch addresses on your behalf. For inputs past the ceilings, put them in a
repository and set environment.repo — the session clones it.
To add a file to a session that is already running, the same shape goes to
POST /v1/sessions/{session_id}/files. See sessions.md.
Binary formats
Your documents are probably not text, and you do not need to convert them. The session is a computer, and the agent is a coding agent on it. Asked for a PDF it cannot read directly, it writes code and reads it anyway. Both of these are from real runs:
Nothing in the request asked for that fallback and nothing had to be installed.
The same applies to
.xlsx, .docx and images: it is a sandbox with Python in
it, so “can it read my format” is usually “yes, and it costs a few seconds”.
Two things follow. Budget for the slower path — a scanned page needing OCR
is minutes, not seconds. And say the format in your prompt when you know it;
“the PDF at invoices/x.pdf” saves the agent a file call.
Shaping the output
agent.text.format composes your JSON Schema into the model’s instructions.
What that is worth, measured on the same invoice:
assert in the
script above is not decoration — it is the only line in this pipeline that turns
“usually correct” into “correct or loud”.
Why two waits
A turn reachingcompleted does not mean its files are fetchable. Artifacts are
published after the turn settles, so for a moment the list is legitimately
empty — and an empty list is indistinguishable from “this turn produced
nothing”.
The turn’s artifacts field is the one to poll:
Wait for anything that is not
pending. Treating partial as “not done yet”
loops for ever; treating it as ready is how a dropped file becomes a file you
believe the agent never wrote.
Using webhooks instead? Then ignore all of this:
turn.completed is sent after publishing has finished.
A batch, not one file
One session per document, each with its own idempotency key derived from the input:429s — rate_limit_exceeded and
project_limit_exceeded — need telling apart, because only one is worth
retrying. An agent behind your API has the loop.
For many rows from one turn, take them in a single request:
When it goes wrong
You have it working when
- One session per document, each reaching
artifacts: ready - Every artifact parses as JSON and passes your own type check
- Re-running the batch with the same keys creates no new sessions
metadataon each session ties the row back to the document
Next
- Put an agent behind your own API — draining a queue without waiting
- sessions.md — repositories, files afterwards, environment profiles
- limits.md — every ceiling named here, with its number