Skip to main content

Agent Runs

An agent run executes a configured agent headlessly — no chat UI, no human at the keyboard. Runs are the programmatic core of NexSpace Headless AI: the same durable path backs API-initiated runs, automations (schedule/event triggers), and the nexspace agents run CLI command. Every run requires the agents:run scope on the API key.

Lifecycle

Runs are persisted durably (headless_agent_runs) — state survives server restarts and is readable across instances. Orphaned runs are reconciled and old runs are pruned on a schedule.

Start a run

The response is 202 Accepted — the run is enqueued, not finished. Poll or stream it (below) for the result.

Request body

Those two fields are the whole body — unknown keys are stripped, not rejected. A missing or empty message returns 400 VALIDATION_ERROR with the per-field validation errors in error.details.
seedMessage is a different field on a different endpoint. It belongs to triggers (POST /api/agent-builder/{id}/triggers), where it stores the instruction an unattended schedule/event run will be seeded with. The run API takes message.
A second run started while one is already executing for the same agent returns 409 AGENT_BUSY — the one-run-per-agent guard prevents overlapping runs.

Run identity

A run always executes as the user your credential is bound to. There is no runAs parameter on this endpoint and no way to run as somebody else over this API. That user’s RBAC permissions and suite entitlements are the ceiling: a run can never do more than the bound user could do in the app. Consequences worth designing around:
  • Org-only keys cannot use this API. An API key with no bound user (user_id IS NULL) is accepted by the authenticator but rejected by every /api/agents/{id}/runs… route with 401 API_KEY_REQUIRED. Use a PAT or a user-bound key/OAuth token.
  • Foreign agents return 404, never 403. If the agent definition belongs to another facility or another org, you get 404 NOT_FOUND — the same response as an id that does not exist. This is deliberate: it keeps agent ids unprobeable. Do not read 404 as “wrong id” without also checking tenancy.
  • The run executes under your facility. The facility comes from the API key’s facility lock, else the bound user’s facility — never from the agent record you looked up. A key locked to facility A cannot inherit facility B by naming an agent that lives there.
  • Runs are scoped to you on read, too. GET/DELETE on a run owned by a different user answer 404, not 403.
There is no headless endpoint that lists agent definitions — /api/agents only serves /{agentId}/runs…. Take the agent id from the Agent Builder dashboard or from the CLI (nexspace agents runs list <agentId> and friends all take the id as an argument).

Poll, list, and cancel

Stream a run

Stream run events as they happen over Server-Sent Events:
Each SSE frame carries a monotonic id. If the connection drops, reconnect with a Last-Event-ID header to replay the frames you missed — the same resumable transport described in MCP. The CLI does this for you: nexspace agents run 42 --output-format stream-json.

Approvals

When a run invokes a tool that requires approval (see autonomy), it transitions to pending_approval and exposes pendingApprovalIds instead of executing unattended. List the pending actions, then approve or reject — approving resumes the agent loop, it does not merely run the one tool.
reason is optional on both (max 2,000 characters). It is stored on the approval (approvalReason / rejectionReason) and written to the decision audit record.

Approving is a two-part authorization

The agents:approve scope is necessary but not sufficient. Resolving an approval over this API requires the caller to own the run that raised it — and owning the run means you are the requester, so approving it is self-approval. That needs a second, independent grant: the ai.approve_actions RBAC permission on the credential’s bound user. Rejection is deliberately ungated: withdrawing your own pending action can only reduce what runs, so it stays available as a fail-safe even when you cannot approve. Internal operators with cross-facility scope are the one exception — their decision is recorded as internal and skips the self-approval check.

Responses you should handle

Because approval is a separate grant, the practical pattern for unattended fleets is: let the agent run under a credential that cannot self-approve, subscribe to agent_run.pending_approval, and have a human (or a second credential whose user does hold ai.approve_actions) make the call.

Autonomy

An agent definition’s own autonomy setting (defaultAutonomy, which defaults to require_approval) decides what happens on a risky write:
  • require_approval — pause at pending_approval and wait for a human (or an approver workflow wired to the webhook below).
  • notify_after — perform the write, then notify.
  • auto_execute — perform the write unattended (use only when the writes are safe to run without review).
Do not confuse this with the headless autonomy dial, which is a separate operator control resolved per API key, per facility, or fleet-wide for MCP tool calls. They share vocabulary (auto_execute / notify_after / require_approval) but are unrelated mechanisms: this one is a property of the agent definition you are running, the other tightens what a machine credential may execute. See Governance.

Lifecycle webhooks

Subscribe to run lifecycle events instead of polling (see Webhooks):
  • agent_run.pending_approval
  • agent_run.completed
  • agent_run.failed

See also

  • Automations — fire runs on a schedule or business event.
  • MCP — the tool layer runs invoke.
  • CLI commands — nexspace agents run.