Run control API
Resolve waits, steer sessions, and apply idempotent safe-point or abort controls to exact Runs.
Every route below begins /v1/namespaces/:namespace/agents/:agent/sessions/:session. Controls are durable authenticated events. Supply the exact Run id and caller-stable eventId; do not infer current state from analytics before mutating it.
| Method | Suffix | Required body or purpose |
|---|---|---|
POST | /waits/:promiseId/resolve | { eventId, value }; value must satisfy the pinned wait schema |
POST | /steers | { eventId, text, data? }; append fresh authenticated guidance |
GET | /events/:eventId | Requires ledger:read; the Session's decision on an event: the Run it started, its place while it waits, or its refusal with that status |
POST | /runs/:run/pause | { eventId } or idempotency header; stop at a safe point |
POST | /runs/:run/resume | { eventId }; resume and drive dispatchable work |
POST | /runs/:run/cancel | { eventId? }; terminally stop future work |
POST | /runs/:run/interrupt | { eventId, payload, mode? }; mode is safe-point or abort |
POST | /runs/:run/policy | { eventId, policyHash }, { eventId, maxTurns }, or { eventId, maxMicroUsd } |
POST | /runs/:run/rebind | { eventId, addBindings } to add exact pins, { eventId, bindings } to replace the fixed map, or { eventId } to refresh scoped assignments |
POST | /runs/:run/truncate | { eventId, to, text, data? }; move the same history head and steer |
POST | /runs/:run/branch | { eventId, at, text, data? }; create and drive a forked Run |
Abort interruption additionally requires run:abort. A fixed rebind supplies the complete map of exact Resource CRN/hash pins; it cannot change the Agent's logical model identity or leave a Tool need unsatisfied. Selected revisions must be available, but do not have to be the latest Resource head. A scoped refresh resolves current assignments for the Run's already accepted principal/customer authority.
Add a Resource during a Run
Install the Resource first, then pass its returned CRN and hash to addBindings. For example, an MCP Gateway installed as tickets can be attached without replacing the Run's model, GitHub connection, or other bindings:
{
"eventId": "attach-tickets",
"addBindings": {
"tickets": {
"crn": "crn:constal:production:acme:default:mcp/tickets",
"hash": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}
}
}Use the actual installation hash, not the example value. The same request is available to Agents as the run.rebind operation through the Constal API Resource, with this body in input.request and the exact namespace, Agent, Session, and Run in input.
addBindings preserves existing pins and resolves scoped credentials only for the additions. Repeating an existing CRN/hash binding leaves it unchanged; changing that binding requires an explicit replacement. Do not supply both bindings and addBindings.
The response's targetResourcesHash identifies the requested snapshot. An accepted receipt does not mean the current Agent frame already sees it: the change applies at a safe point. Compare Run detail's resourcesHash with the receipt, or observe the applied bindings through the Agent context after that safe point. Rebinding a manually paused Run does not resume it.
StaleResourceSnapshot means another change won before this request could be accepted. Read the current state and retry the intended change. RunControlPending means an accepted control is still awaiting its safe point; the new request has not replaced it.
Run queues
A concurrent Run limit with overLimit: "queue" keeps an ordered queue of Run requests waiting for a slot. A queue is addressed by its Agent, its limit id as :queue, and its scope. These routes begin /v1/namespaces/:namespace/agents/:agent/run-queues/:queue and are not Session routes:
| Method | Suffix | Action | Body | Response |
|---|---|---|---|---|
GET | ?scope=:scope | run:read | none; optional limit (default 100) and cursor | { data: RunQueueItem[], next } |
POST | /items/:item/move?scope=:scope | run:reorder | exactly one of { before: item }, { after: item }, or { position } | { data: RunQueueItem } |
POST | /items/:item/drop?scope=:scope | run:cancel | { reason? } | { data: { id, state: "dropped" } } |
scope is the limit's scope: customer, subject, session, tenant, or resource. Add &session=:session for a session queue. The scope value comes from the caller, never from the request: the caller's Customer for customer, its principal for subject, its own Customer's Session named by the session query value for session, its tenant for tenant, and the Agent for resource. A caller with no Customer that addresses a customer queue gets 400. Policies see the action on the run Resource path :agent/run-queues/:queue.
A caller sees and controls only the items of its own Customer; a parent-tenant caller sees parent-tenant items only. position counts from 1 among those items, and a move reorders only them, so other Customers' items keep their positions. Pass the previous page's next as cursor.
{
"id": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"position": 1,
"session": "session-42",
"eventId": "evt-7",
"runId": null,
"receivedAt": 1790000000000,
"enqueuedAt": 1790000000150,
"summary": {
"kind": "message.received",
"title": "Summarize the open incidents",
"actor": { "kind": "user", "id": "user-9" },
"source": { "kind": "channel", "channel": "crn:constal:production:acme:default:channel/support" }
}
}The summary never includes the request's input. source is { kind: "agent", agent, session } for an Agent send, { kind: "channel", channel } for a Channel event, or null.
before and after name another waiting item in the same view. position is an integer from 1 and is clamped to the queue length. A drop refuses the waiting request with 409 RunQueueItemDropped and the dropping actor: an Agent send's Handle is cancelled and a Channel receives channel.failed. Dropping the same item again returns the same response. reason is 1 to 4096 characters.
| Status | Error | Meaning |
|---|---|---|
| 400 | invalid run queue request | Not exactly one move target, or an invalid position, reason, scope, or query |
| 400 | customer-scoped run queues are addressed by a Customer caller | A caller with no Customer addressed a customer queue |
| 403 | authorization_error | The caller lacks run:read, run:reorder, or run:cancel |
| 404 | agent not found | The Agent does not exist |
| 404 | RunQueueItemNotFound | The item or anchor is not in the caller's view |
| 409 | RunQueueItemNotWaiting | The item or anchor was already granted a slot, dropped, or withdrawn; the body carries its state |
Agents reach the same controls through the Constal API Resource. apply accepts run-queue.move (requires run:reorder) with input { namespace, agent, queue, scope, session?, item, before? | after? | position? } and run-queue.drop (requires run:cancel) with { namespace, agent, queue, scope, session?, item, reason? }. Both support dry run and an idempotency key, and apply to one item per call. A successful call is succeeded.
A run.start that must wait has no Run yet. Its result is { outcome: "queued", eventId, admission: { state: "waiting", queue, scope, position } }, or carries activeRunId when it waits behind the Session's live Run, and its receipt stays pending with that result. Each observe re-reads the event's decision at /events/:eventId, which updates position. Once the Session admits the event, the result carries the Run's runId and the receipt follows that Run to its end, as it does for a start admitted at once. A dropped start fails with RunQueueItemDropped. A start refused by a limit with overLimit: "reject", at once or when its turn comes, fails with AdmissionLimitExceeded.
Query kind run-queue-item lists waiting items. query requires exact equality filters on agent, queue, and scope, and on session for a session queue. Each result's id is agent/queue/scope/session/item, with - for no Session, and its fields carry position, eventId, runId, receivedAt, enqueuedAt, and summary. get accepts the same id. An Agent calling through its api binding acts with its Run's authority, including the Customer, so it sees only that Customer's items.
After every operation, persist the receipt and refresh Run detail. An uncertain external effect is reconciled according to the pinned Resource operation recovery contract; a control does not make blind repetition safe.