Which token for which process
A call missing its scope is
403 permission_denied, and the message names the
scope it wanted — so you never have to guess which one you left out.
sessions:read + artifacts:read is the read-only shape, and it really is
read-only: nothing it holds can delete, cancel or write.
sessions:read + tools:respond is a worker: it can watch a session and answer
its function calls, and it cannot send a message, cancel a turn or delete
anything. That is the token to give a fleet of tool handlers. The two scopes are
exact in both directions — sessions:write alone does not answer a tool call,
and tools:respond alone does not drive a session.
A call missing its scope is 403 permission_denied, and the message names the
scope it wanted — so you never have to guess which one you left out.
Reading one turn
One turn, by id, when you have kept one and want its current state without a list around it:artifacts, which is
what step 8 waits on.
The session loop
Everything above is one call each. In practice you want the loop: subscribe, send, answer whatever the agent asks you, resume if the connection drops. That loop isrun-session.ts
— copy it into your project and use it:
pnpm e2e:v1:minimax runs
against production, so it cannot rot without a test going red.
The tool is declared in two halves, and both are required: agent.tools is
what the model is told about, and handlers is what your process answers
with. Declaring without a handler parks the turn on a call nobody answers;
handling without a declaration means the call never happens.
To work on a repository, add environment: { repo: "acme/site" } — your
organization needs a GitHub connection first, or the request is refused up
front.
Three things it does that are easy to get wrong by hand, and wrong silently:
- Subscribes before sending. The other order loses the opening events whenever the agent starts quickly — which is to say sometimes.
- Answers required actions. Left unanswered, a turn stays
workingand the session staysrequires_actionfor as long as the workspace lives — nothing times it out, and nothing else moves until you answer. - Never sends a thrown handler’s message to the model. A stack trace is an excellent way to put your database host into a model’s context, so a failing handler yields a fixed string instead.
Last-Event-ID rather than starting over or losing
the middle. See events.md.
Taking many files at once
When a turn produced more than one file, take them all at once:?turn_id=… to narrow it to one turn, and the entries
carry the workspace’s own paths. Without it you get the whole session, and each
entry is prefixed with the turn that published it — two turns writing
report.md are two files, and a flat archive would extract as one.
Artifacts
Anything the agent writes under/workspace/outputs is published as an
artifact — a durable copy that outlives the workspace.
Publishing happens after the turn settles, so that a storage problem can
never delay or fail your work. That means status: "completed" is not yet a
promise that GET /v1/sessions/{id}/artifacts will list anything. The turn
tells you which it is:
turn.completed webhook already waits for you: it is sent once
publication settles, so an unattended integration can fetch artifacts the
moment it is called. If publication is still running after 30 seconds the
notification is sent anyway, with the turn still reading pending — a late
answer being better than none.
What to reach for next
The pieces this page assumes, each on its own page:- input.md — the four things you can send a session, and what each refuses
- events.md — stream instead of polling, and resume where you dropped
- webhooks.md — be told rather than watch, and verify the delivery
- required-actions.md — let the agent call your code
- idempotency.md — a retry that does not act twice
- pagination.md — collections longer than one page
- limits.md — every ceiling, with its number
- troubleshooting.md — a symptom you can look up, and errors.md for the shape every refusal arrives in