Docs contents
Reporting runs (ingest)
Ingesting is for non-hosted agents: your code runs the agent on your own infrastructure, and you report what happened so AgentOS can show it as runs and events. AgentOS is the observer; it never executes your agent.
If instead you want AgentOS to run the agent and hand you the output, that's the opposite flow. See Invoking hosted agents.
The run lifecycle
Every reported run follows the same shape:
startRun() ──▶ run created (status: running)
│
├─ emit(type, payload, level) ×N (progress, tool calls, custom events)
│
└─ complete() | fail() | cancel() ──▶ status: completed | failed | cancelled
You open a run, emit events as work happens, and close it exactly once. The SDK wraps this; the raw endpoints are below.
import { AgentOS } from "@agentos-sdk/core";
const client = new AgentOS({ agentId: "<agentId>", apiKey: "aos_agent_…" });
const run = await client.startRun({ input: { invoiceId: "INV-001" } });
try {
run.emit("invoice.fetched", { rows: 42 });
const result = await processInvoice("INV-001");
await run.complete({ output: { total: result.total } });
} catch (err) {
await run.fail({ errorMessage: String(err) });
}
Or let withRun() close the run for you: it completes with your function's return value, or fails with the error it throws (and rethrows it).
const total = await client.withRun(async (run) => {
run.emit("invoice.fetched", { rows: 42 });
return { total: (await processInvoice("INV-001")).total };
}, { input: { invoiceId: "INV-001" } });
Authenticate ingest with an agent key (aos_agent_…, limited to its own agent) or a workspace key (aos_ws_…, any agent in the workspace). Personal keys are not accepted. See Authentication.
SDK ↔ HTTP map
Every SDK method is a thin wrapper over one ingest endpoint:
| SDK | HTTP endpoint | Purpose |
|---|---|---|
client.startRun() | POST /api/ingest/v1/run/start | Open a run, get a run_id |
run.emit() and helpers | POST /api/ingest/v1/events | Append one or more events (and spans) |
run.complete() | POST /api/ingest/v1/run/complete | Close as completed with output and token usage |
run.fail() | POST /api/ingest/v1/run/fail | Close as failed with an error |
run.cancel() | POST /api/ingest/v1/run/cancel | Close as cancelled, with an optional reason |
run.startTask() | POST /api/ingest/v1/tasks | Create a task; the returned task has complete(), fail(), skip() |
run.requestHumanInput() | POST /api/ingest/v1/human-input/request | Ask a person in the dashboard and wait for the answer (long-poll) |
| (OpenTelemetry exporter) | POST /api/ingest/v1/traces | Send OTLP/JSON traces. See Tracing & spans |
Python uses the same methods in snake_case (start_run, start_task, request_human_input). Full request and response shapes are in the HTTP API reference.
Request fields and limits
| Endpoint | Fields |
|---|---|
run/start | agent_id (required), input (object), external_run_id (up to 128 chars), trigger_type (manual, scheduled, webhook, or sdk; default sdk), parent_run_id |
events | run_id, events: 1 to 500 per request. Each has type (1 to 128 chars), level (default info), payload (object), optional occurred_at and span fields |
run/complete | run_id, output (object), optional input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens |
run/fail | run_id, error_message (up to 4000 chars), optional error_detail (object) |
run/cancel | run_id, optional reason (up to 1000 chars) |
In the SDK, pass token counts as run.complete({ output, usage: { inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens } }). They feed the cost and usage views.
Closing a run is idempotent: repeating the same close returns 200 again. Closing it with a different status returns 409 CONFLICT.
Events: ordering & batching
- Events get a server-assigned
sequence_number, so the run timeline renders in the order AgentOS received them. Emit in the order things happen. - The events endpoint takes an array, so batch several events into one call. The SDK batches and flushes for you. See Events.
- Set
occurred_at(ISO 8601) on an event to record when it happened if that differs from when you send it.
Correlating with your own IDs
Pass external_run_id on startRun to store your own identifier (a job id, a message id) on the run, so you can map an AgentOS run back to the work item that produced it.
{ "agent_id": "<agentId>", "input": { … }, "external_run_id": "job_8f2a" }
It also makes starting a run idempotent: starting again with the same external_run_id for the same agent returns the existing run instead of creating a second one. The SDK always sends one (a random UUID if you don't pass externalRunId), so retries after a network error are safe.
Errors
| Code | HTTP | Meaning |
|---|---|---|
UNAUTHENTICATED | 401 | Missing, invalid, or revoked key |
VALIDATION_ERROR | 400 | Body fails the limits above |
FORBIDDEN | 403 | Key scoped to a different agent, or the agent is paused or archived |
WORKSPACE_SUSPENDED | 403 | The workspace is suspended |
NOT_FOUND | 404 | Unknown agent, run, or parent_run_id |
CONFLICT | 409 | Run already closed with a different status |
NO_PLAN | 402 | The workspace has no active plan. Choose one in Backoffice → Billing |
RATE_LIMITED | 429 | Over your plan's per-minute limit. Wait for the Retry-After header |
Per-plan rate limits are listed in the HTTP API reference.
Tips
- Emit custom event types freely (
invoice.fetched,retry.scheduled). They're just strings and render in the timeline. See Events. - Use Tasks for sub-steps you want tracked with their own status.
- Want a notification when a reported run fails? Create an alert rule and send it to a webhook or your inbox.
See Hosted vs non-hosted for choosing between ingest and invoke, and the HTTP API reference for exact shapes.