Skip to main content
An AgentOS agent is any container that serves two endpoints on the port from agentos.yaml:
  • GET /health — answers any 2xx once the agent is ready.
  • POST /invoke — runs one capability and answers with the result.
That is the whole contract. Any language that can serve HTTP works; the SDKs implement it for you in Python and TypeScript, and the conformance kit checks an agent written in anything else.

GET /health

After a container starts, AgentOS polls GET /health every 0.5 seconds (2 seconds per request, only the status line is read) for up to 60 seconds. Any 2xx means healthy, and only then does a new version go live. If the container exits first, the deploy fails at once with its exit code and last output.

POST /invoke

One endpoint serves every capability; the body names which one:
string
The capability to run, one of those in agentos.yaml.
string
The run this invocation belongs to.
string | null
The run’s session, if it has one.
string | null
The end user the run is for (from its session; a child run keeps its parent’s), or null.
object
The run’s input, already checked against the capability’s input schema.
object
id, seq, attempt and kind. kind is start for the first invocation and resume after a person decided a hold. seq counts invocations of the run; attempt counts retries of one invocation.
object | null
On a resume invocation, the decision. null on start.
object
Everything the agent needs to use the platform during this invocation: token (the run token), api_url (the platform’s address as seen from inside your container; platform calls go to {api_url}/v0/...), config (models, prompts, params of this version), agent_id, version, llm (the model gateway), secrets (see Secrets), and eval (null except in experiment runs).
New fields may be added to the invoke body over time. Ignore what you don’t know.

Answering

Answer 200 with JSON in one of three shapes:
  • completed — output must match the capability’s output schema, otherwise the run fails with invalid_output (the output is kept on the run so you can see what was wrong).
  • failed — error.code is your own code. Use lowercase letters, digits and _, starting with a letter, at most 63 characters, and a message that isn’t empty; anything else is recorded as agent_failed.
  • suspended — after you asked a person with POST /v0/holds. hold_id must name the open hold this invocation created, otherwise the run fails with invalid_response.
You can also answer with a stream instead of JSON. See Streaming answers.

How runs are invoked

For each run, AgentOS calls POST /invoke once, on the version the run was started on (an older version’s stopped container is started again for it), and waits up to the capability’s timeout for the answer.
  • Before your agent gets the request (connection refused, container not ready yet), the call is retried for 30 seconds.
  • Once your agent got the request, nothing is retried. A timeout, a crash, or any non-2xx status fails the run with agent_interrupted. Start a new run to try again.
After a person decides a hold, the same run is invoked again with invocation.kind: "resume", the next seq and a new token. A run has at most 100 invocations (its start and one resume per decision); after that it fails with too_many_resumes.

Make side effects happen once

Code before an ask runs again on the resume, and invocation.id changes on every resume. So key anything that must happen once per run — a payment, an email, a child run — by run_id plus a stable step name, never by invocation.id:
Keep progress in run state before you suspend, so the resume knows what was already done.

The run token

context.token belongs to this invocation only. It works while your agent is answering, and stops working as soon as it has answered (or the run is cancelled): at once on the platform, within 5 seconds at the model gateway. Send it as the x-agentos-run-token header on every platform call:
Never store or log the run token, and never send it anywhere but api_url and the gateway. The SDKs enforce this for you.

Experiment runs

When an eval runs your agent on a dataset, each item is a normal invocation with two differences:
  • context.config is the version’s config with the experiment’s temporary override merged in (for example another model for models.reader).
  • context.eval is {"recorded_holds": [...]} instead of null: the decisions people made on the run the item was copied from, each {intent, actions, items, details, decision} (decision is null for a hold that expired).
Nobody reviews holds in an experiment. A run that asks anyway ends as failed with asked_for_human, and the item counts as “asked for a human”. Use the recorded decisions or skip the human step, and check context.eval to keep side effects (payments, emails) out of experiment runs. Memory in an experiment run is a temporary, empty one that is deleted when the item’s run ends.

Next steps

Calling models

Use the environment’s models through the gateway.

Human approvals

Ask a person and continue with their decision.

SDKs

Let the SDK speak the contract for you.

Conformance kit

Check an agent in any language against the contract.