# Agent runs (/docs/agents)

Hand the gateway a goal and let it run the whole tool loop server-side — routing, built-in tools, sub-agents, memory.

`POST /v1/chat/completions` is a pure pass-through: codai never injects its own tools, you own the tool loop. An **agent run** flips that around. You submit a task, the gateway runs the loop — picks the model, executes [built-in tools](./tools.mdx), spawns typed sub-agents, recalls memory — and hands back the outcome.

Two shapes, same request body:

| | Sync — `POST /v1/agents/run` | Async — `POST /v1/agents/runs` |
| --- | --- | --- |
| Returns | The final answer in one `200`. | `202 { id, status: "queued" }` immediately. |
| Best for | Short tasks, simple integrations, SDK one-liners. | Long tasks, anything you want to poll, stream or cancel. |
| Survives a client disconnect | No — the HTTP request is the run. | Yes — the run is persisted; see [async runs](./async-runs.mdx). |
| Key type | Any `codai_` key. | A key that belongs to a codai account (not a legacy compat key). |

Both need a plan that includes sub-agents and tools; otherwise the gateway answers `403 forbidden` — *The agents API requires a tier with sub-agents and tools (Pro or above).* (gateway wording: "Pro" there means the Starter plan or higher.)

## Request body [#request-body]

<TypeTable
  type={{
    task: { type: 'string', description: 'The goal. 1 – 64 000 characters.', required: true },
    context: { type: 'string', description: 'Extra material appended to the task under a "## Context" heading. Up to 256 000 characters.' },
    system: { type: 'string', description: 'System prompt for the run. Up to 32 000 characters.' },
    model: { type: 'string', description: 'Any alias you can call. The run bills as that alias.', default: "'codai'" },
    max_steps: { type: 'integer', description: 'Accepted 1 – 16 for forward compatibility; the gateway currently applies its own step budget and does not enforce this value.' },
  }}
/>

An invalid body is `400 bad_request` with the field-level report in `error.details`.

## A synchronous run [#a-synchronous-run]

<Tabs items={['curl', 'TypeScript', 'Python']}>
<Tab value="curl">

```bash
curl https://ai.codai.ro/v1/agents/run \
  -H "Authorization: Bearer $CODAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "task": "What changed in the Node.js 24 release notes that affects ESM loaders? Cite the section.",
    "model": "codai"
  }'
```

</Tab>
<Tab value="TypeScript">

```ts
import { Codai } from 'codai-sdk';

const codai = new Codai({ apiKey: process.env.CODAI_API_KEY! });

const run = await codai.agents.run({
  task: 'What changed in the Node.js 24 release notes that affects ESM loaders? Cite the section.',
});

console.log(run.result);
console.log(run.model, run.usage, run.eventId);
```

</Tab>
<Tab value="Python">

```python
import os
from codai import Codai

client = Codai(api_key=os.environ["CODAI_API_KEY"])

run = client.agents_run(
    "What changed in the Node.js 24 release notes that affects ESM loaders? Cite the section."
)

print(run.result)
print(run.model, run.usage, run.event_id)
```

</Tab>
</Tabs>

Response:

```json
{
  "result": "Node.js 24 moved the ESM loader hooks off-thread by default …",
  "status": "completed",
  "usage": { "prompt_tokens": 18422, "completion_tokens": 611 },
  "model": "codai",
  "event_id": "8b3f6d1e-…"
}
```

`event_id` is the usage row for the whole run — pass it to `POST /v1/feedback` or look it up in the hub. The response also carries `x-codai-upstream-model` (which upstream model served the final turn) and `x-codai-event-id`.

<Callout type="info">
A synchronous run counts as **one task** for your plan's task cap, checked before the loop starts. If you are over the cap the request is `429 quota_exceeded` and nothing runs — see [tasks and spend](./tasks-and-spend.mdx).
</Callout>

## What the loop does [#what-the-loop-does]

Inside a run the model has the [built-in tools](./tools.mdx) — `http_fetch`, `json_query`, `exec_python`, `web_search`, memory, skills, any MCP servers attached to your key — plus `spawn_agent` for typed sub-agents that run in parallel with their own context. The gateway executes every tool call of a turn concurrently, feeds results back, and stops when the model produces a final answer. Sub-agent spend counts toward the run's cost ceiling, and children never spawn children.

The same loop is what `x-codai-server-tools: 1` turns on for `POST /v1/chat/completions` — agent runs are simply the goal-first packaging of it.

## When not to use it [#when-not-to-use-it]

- You already have a tool loop (an IDE, a framework, your own agent) — call `/v1/chat/completions` and keep the tools client-side.
- You need to stream tokens to a user in real time — chat completions with `stream: true`. Agent runs stream **steps**, not tokens.
- You need to drive a session across devices — that is [shared sessions](../sessions/index.mdx), a different surface.
