Agent operations

Agent Operations V1 is the authenticated surface agents use to submit typed provider work without receiving the provider credential. Every discrete operation goes through it, whether it arrives from the CLI, from an MCP tool call, or from a direct HTTP request. Git traffic is the exception, because Git speaks its own protocol on its own listener.

Contract ownership

The shared contract owns identity, idempotency, lifecycle, presentation, and safe errors. Providers own target validation, argument validation, execution, and result validation. That split is why gh-broker can generate hundreds of typed operations from pinned GitHub definitions without the shared lifecycle code learning anything about GitHub.

Handles

Five identifiers appear in the lifecycle, and mixing them up is the most common source of confusion:

HandleMeaning
request_idExact admission and retry identity, chosen by the caller or generated.
operation_idDurable operation lifecycle identity.
approval_idInternal approval linkage. Not exposed by MCP.
transfer_idBounded stream correlation handle.
credential_slotEncrypted destination for generated credentials.

The internal Agent V1 API calls the first one idempotency_key. MCP exposes the same value as request_id, and the mapping is one to one. Supplying the same request ID with the same immutable request returns the existing operation; supplying it with a conflicting request returns a bounded request_id_conflict rather than creating a second execution.

Submission

Submission returns immediately with the transcript-safe operation, including its ID, request ID, state, revision, timestamps, and safe presentation. It never waits for approval. That property is what makes the surface usable from an LLM tool call, where a blocking call would burn the context window on a wait.

gh-broker operation submit repo.contents.read \
  --target-json '{"kind":"repo","owner":"osolmaz","name":"unyolo"}' \
  --arguments-json '{"path":"README.md","ref":"main"}' \
  --wait

The --wait flag is a client convenience that polls after submitting. The submission itself still returned right away.

Authorization inside the lifecycle

Authorization mode does not select a dispatch path. Every ordinary operation tool submits one durable Agent V1 operation, and static allow, active window grant, approval request, and deny are all outcomes inside that single lifecycle.

An allowed operation executes immediately and creates no approval state. A requestable operation without an active grant creates one approval linked to that operation. A matching active window grant authorizes the stored operation and reserves one use atomically. A denied operation makes no provider request at all.

This matters for a practical reason. An allowed read of a private repository executes immediately using the broker’s credential; it does not ask for a grant and it does not expose the token. Explicit grant tools are for callers deliberately managing access that a native protocol will use later, and reaching for them before an ordinary operation creates approval requests nobody needed.

Recovery

Operations are durable, so a lost connection or a restarted process does not lose the work:

gh-broker operation get <id>
gh-broker operation wait <id>
gh-broker operation cancel <id>

Over MCP the same three exist as <prefix>operation_get, <prefix>operation_wait, and <prefix>operation_list. Waits default to 25 seconds, never exceed 25 seconds, and return the latest known state on timeout. List defaults to 20 results, caps at 50, uses validated opaque cursors, and supports exact request-ID recovery. Listing is scoped to the authenticated client.

Cancellation belongs to the requester. An operator cannot cancel someone’s pending request; an operator denies it, which is a different terminal state and reads differently in the audit log.

Routes

The agent listener exposes the lifecycle plus its supporting transfer surfaces:

GET  /.well-known/unyolo-agent
POST /api/agent/v1/operations
GET  /api/agent/v1/operations/{id}
GET  /api/agent/v1/operations/{id}/events
POST /api/agent/v1/operations/{id}/cancel
POST /api/agent/v1/sealed-payloads
POST /api/agent/v1/streams
GET  /api/agent/v1/streams/{id}

Sealed payloads and streams are how bounded content moves without going through the JSON body. Both are authenticated and bounded, and both stay inside the client’s own scope.

The discovery document at /.well-known/unyolo-agent exposes the exact generated contract digest and build identity, so a client can detect protocol drift without logging response bodies. A version label alone is never treated as compatibility evidence.

Ambiguous outcomes

When a provider execution times out or completes ambiguously, the broker does not automatically retry the mutation. Committed plans recover after a crash, and unprovable outcomes fail explicitly rather than being retried into a possible duplicate. Authority is retained for reconciliation.

The rule this implies for callers is short: reuse a stable request ID when retrying, and never retry an ambiguous execution under a new ID.

Result safety

Provider-native request and result schemas stay provider-owned, but the projection into public output is bounded. Providers project public names that collide with supported transcript redactors, and real secrets remain sealed or credential-slot-backed.

Provider errors get sanitized before a client sees them. gh-broker parses GitHub’s documented error message fields, removes control characters, redacts credential-like values, and limits the text. Raw response bodies, GraphQL paths, and extensions are not retained. Sealed operations omit provider messages entirely, because a provider may echo request values back. A durable operation can therefore report something useful like a disabled merge method while machine behaviour keeps using validated codes, HTTP status, and request IDs.

Admission

Submission passes through an admission controller before any durable operation or approval notification is created. Authentication, request validation, and idempotent replay all happen before quota charging, so a replayed request does not consume a slot.

A refusal returns a stable 429 with Retry-After, creates no operation, and does not consume another slot. Cancellation, operator decisions, liveness, and readiness bypass submission admission and stay available during overload. See admission control for the limits and how to configure them.

Esc

Type to search across every documentation page.