> ## Documentation Index
> Fetch the complete documentation index at: https://docs.oao.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Create an agent

> Create, publish, run, and debug an immutable managed agent from the OAO console or REST API.

An OAO agent is a stable definition with an immutable sequence of versions. A
session pins one version, and every initial or follow-up message creates a
durable run on that same session and thread.

<Note>
  Creating an agent publishes version 1 in the same transaction. The current MVP
  does not have a separate unpublished-draft workflow.
</Note>

## Before you begin

Start the local stack and open the console:

```bash theme={null}
pnpm dev:local
```

The default URLs are:

* Console: `http://127.0.0.1:8080`
* Direct API: `http://127.0.0.1:3000/v1`

Runtime execution requires a configured project model preset. A Daytona
connection is required only when the published version enables a sandbox.
There is no selectable fake model or sandbox profile.

For direct API calls, use an OAO API key (organization-wide; the project in the
URL path selects where it acts) and the project ID from `GET /v1/context`:

```bash theme={null}
export OAO_API_URL=http://127.0.0.1:3000/v1
export OAO_API_KEY='oao_...'

curl --fail-with-body \
  --header "Authorization: Bearer $OAO_API_KEY" \
  "$OAO_API_URL/context"
```

The context response includes `project.id` and the publication-time model
allowlist in `activeModelPresets`. That list contains every provider-backed
preset approved for this project that is currently available. A preset is
omitted when it lacks a provider connection or platform
credential decryption is unavailable:

```json theme={null}
{
  "principal": {
    "id": "00000000-0000-4000-8000-000000000003",
    "organizationId": "00000000-0000-4000-8000-000000000001",
    "projectId": "00000000-0000-4000-8000-000000000002",
    "kind": "api_key",
    "subject": "api-key:...",
    "scopes": ["*"]
  },
  "organization": {
    "id": "00000000-0000-4000-8000-000000000001",
    "slug": "development",
    "name": "Development organization",
    "createdAt": "2026-08-20T10:00:00.000Z"
  },
  "project": {
    "id": "00000000-0000-4000-8000-000000000002",
    "organizationId": "00000000-0000-4000-8000-000000000001",
    "slug": "default",
    "name": "Default project",
    "createdAt": "2026-08-20T10:00:00.000Z"
  },
  "organizations": [
    {
      "id": "00000000-0000-4000-8000-000000000001",
      "slug": "development",
      "name": "Development organization",
      "createdAt": "2026-08-20T10:00:00.000Z"
    }
  ],
  "projects": [
    {
      "id": "00000000-0000-4000-8000-000000000002",
      "organizationId": "00000000-0000-4000-8000-000000000001",
      "slug": "default",
      "name": "Default project",
      "createdAt": "2026-08-20T10:00:00.000Z"
    }
  ],
  "activeModelPresets": ["project-model-v1"],
  "authProvider": "development"
}
```

```bash theme={null}
export OAO_PROJECT_ID=00000000-0000-4000-8000-000000000002
```

Human browser sessions can also call the API with OAO's HttpOnly session
cookie. Cookie-authenticated `POST`, `PUT`, `PATCH`, and `DELETE` requests must
include an `Origin` exactly allowed by `APP_ORIGIN`. Project API-key requests
authenticate with `Authorization: Bearer oao_...` and do not rely on browser
cookies.

## Create through the console

<Steps>
  <Step title="Create the definition">
    Open **Agents**, select **Create agent**, enter a name and optional
    description, and choose an approved model preset. You can also enable the
    sandbox, choose a configured organization Daytona connection, select its network
    policy, optionally select an active Daytona snapshot, select exact tool
    capabilities, and attach active Skill versions before submitting the form.

    The console publishes version 1 with this initial configuration:

    ```json theme={null}
    {
      "systemPrompt": "You are <agent name>, a helpful managed agent. Complete the user's request carefully and do not expose secrets or internal reasoning.",
      "modelPreset": "<selected preset key>",
      "tools": [],
      "harnessOperations": [],
      "skillVersionIds": [
        "44444444-4444-4444-8444-444444444445"
      ],
      "mcpBindings": [],
      "delegates": [],
      "sandbox": {
        "enabled": false,
        "provider": "not-configured",
        "network": "none",
        "capabilities": ["filesystem_read", "filesystem_write", "shell"]
      },
      "limits": {
        "maxTurns": 32,
        "timeoutMs": 60000
      }
    }
    ```

    The form selects the first available provider-backed project preset. The
    sandbox starts disabled and does not require a Daytona connection until you
    enable it; when enabled, the form selects the first available Daytona
    connection. The picker searches every preset returned by
    `GET /projects/{projectId}/model-presets`, showing the preset key and
    whether it is a deployment or project preset, and leaves an unavailable
    preset visible but unselectable. The generated agent key is the normalized
    name plus a random eight-character suffix. Use the API if you need to
    choose the key explicitly.
  </Step>

  <Step title="Review and publish a new version">
    On the agent detail page, edit the latest version's system instructions,
    approved model preset, Harness Operations, bound Skill versions, exact delegate roster, sandbox switch, network policy, or timeout. The
    searchable model list is the project's approved preset catalog, and the
    editor shows the resolved model plus routing policy or OpenAI generation settings for the selected preset
    with a link to **Manage models**. Select **Publish new version** to append an immutable
    version.

    Linking a different model, Skill version, or MCP toolset/policy version is
    always a publication. Configure remote tools under **MCP connections**, then
    select the immutable toolset and exact-origin policy on the agent editor.
    Publishing a newer Skill does not update an existing agent version.
    Existing sessions stay attached to the agent and Skill versions they were
    created with. See [Versioned Skills](/concepts/skills).

    Harness Operations are authored under their own editor section. Each one
    becomes a focused model-callable tool that inherits this Agent version's
    model, rendered instructions, tools, complete Skill catalog, and live
    sandbox. It receives `{ task: string }` and returns a schema-validated
    object. It is not a separate Agent, and there is no per-operation Skill
    selection. See [Harness Operations](/concepts/harness-operations).

    Delegate selection works the same way: the editor records the selected
    agent's exact latest version. The **Delegates** section lists only the
    agents linked to this version; **Add delegate** opens a dialog where you
    can search every other agent in the project by name, key, or description
    and pick one or more published, sandbox-compatible agents. Unavailable
    candidates stay visible with the reason. A coordinator can delegate only
    to that published roster, and each child keeps its own persistent session
    while sharing the coordinator workspace. See
    [Multi-agent orchestration](/concepts/multi-agent-orchestration).

    Console validation requires:

    * At least 20 characters in the system instructions.
    * A preset returned by `GET /projects/{projectId}/model-presets`.
    * A timeout from `1,000` through `3,600,000` milliseconds.
    * Exactly `32` maximum turns.

    Selecting an older version is read-only. Return to the latest version to
    prepare another publication.
  </Step>

  <Step title="Create a session">
    Open **Sessions**, select **Create session**, choose a published agent,
    enter a title and first message, optionally choose supported text, image,
    Office, PDF, or email files, and submit.

    The session pins the agent's latest version at that moment and automatically
    copies its exact Skill bindings. Do not send Skills in the session request.
    Creation returns while the first run is `queued`; the runtime finishes it
    asynchronously.
  </Step>

  <Step title="Continue after settlement">
    Open the session. When its latest run is `completed`, `failed`, `cancelled`,
    or `timed_out`, the **Continue session** form appears. Submitting a message
    creates a new durable run on the existing session and thread. You can attach
    another supported set of files from the same composer.
  </Step>
</Steps>

<Warning>
  The current API-key, archive, rename, resume, and branch-replay controls are
  not implemented in the console.
</Warning>

## Connect MCP tools to an agent

An agent version does not point directly at an MCP URL or secret. It pins an
immutable restricted toolset version and an exact-origin credential-policy
version. First complete [Add an MCP server](/integrations/mcp), then bind those
versions using either path below.

### Through the console

<Steps>
  <Step title="Open the latest agent version">
    Open **Agents**, select the agent, and select its latest version. Older
    versions are read-only.
  </Step>

  <Step title="Select the MCP toolset">
    Find **MCP toolsets** and check the restricted toolset you want to expose. If
    the list is empty, return to **MCP connections**, complete discovery, and
    publish a toolset first.
  </Step>

  <Step title="Choose the credential policy">
    Under the selected toolset, choose the exact-origin credential policy that
    authorizes its server endpoint. The editor derives a collision-safe namespace
    from the toolset key; the model will see names such as
    `mcp__example_read_tools__list_items`.
  </Step>

  <Step title="Publish and test">
    Select **Publish new version**, then create a new session for the agent.
    Existing sessions remain pinned to the earlier agent version and will not
    acquire the MCP tools.
  </Step>
</Steps>

You can also attach the agent on the last screen of the **Add MCP server**
wizard. That path performs the same immutable agent-version publication and
lets you confirm the namespace before finishing.

### Through the API

Read the latest complete agent config, append one `mcpBindings` entry, and
publish the complete next config. This example expects the
`toolset_version_id` and `policy_version_id` produced by the API setup in
[Remote MCP servers](/integrations/mcp#add-an-mcp-server-through-the-api).

```bash theme={null}
current_config="$(
  curl --fail-with-body --silent \
    --header "Authorization: Bearer $OAO_API_KEY" \
    "$OAO_API_URL/projects/$OAO_PROJECT_ID/agents/$OAO_AGENT_ID" \
  | jq '.versions[0].config'
)"

next_config="$(
  jq \
    --arg toolsetVersionId "$toolset_version_id" \
    --arg credentialPolicyVersionId "$policy_version_id" \
    '.mcpBindings = ((.mcpBindings // []) + [{
      toolsetVersionId: $toolsetVersionId,
      credentialPolicyVersionId: $credentialPolicyVersionId,
      namespace: "example_read_tools"
    }])' \
    <<<"$current_config"
)"

curl --fail-with-body \
  --request POST \
  --header "Authorization: Bearer $OAO_API_KEY" \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: bind-example-mcp-tools-1' \
  --data "$(jq -n --argjson config "$next_config" '{config: $config}')" \
  "$OAO_API_URL/projects/$OAO_PROJECT_ID/agents/$OAO_AGENT_ID/versions"
```

The namespace must be unique within the agent version and contain 1–64
characters. It must start with a lowercase letter and use lowercase alphanumeric
segments separated by single underscores, so consecutive or trailing underscores
are invalid. Publication requires `agent:write` and `mcp:bind`; reading the agent
and MCP resources requires `agent:read` and `mcp:read`.

## Create through the API

Every mutation requires:

* `Content-Type: application/json` when it has a JSON body.
* A non-empty `Idempotency-Key` of at most 200 characters.
* A principal scoped to the path's project with the required authorization
  scope.

The routes used in this guide enforce these project scopes:

| Operation | Required scope |
| - | - |
| List or read agents and versions | `agent:read` |
| Create an agent or publish a version | `agent:write` |
| List or read Skills | `skill:read` |
| Create a Skill or publish a Skill version | `skill:write` |
| Bind a Skill while publishing an agent | `skill:bind` |
| Deprecate or revoke a Skill version | `skill:revoke` |
| Disable, enable, or remove a Skill | `skill:revoke` |
| Create a session and its first run | `session:write` and `run:create` |
| Read a session | `session:read` |
| Create a follow-up or resume run | `run:create` |
| Read runs, pending work, or project events | `run:read` |
| Request cancellation | `run:cancel` |
| Claim, renew, or release caller work | `tool_call:claim` |
| Submit a caller tool result | `tool_call:submit` |
| Approve or deny an approval | `approval:resolve` |
| Read a delegation | `delegation:read` |
| Send a child-agent follow-up | `delegation:message`, `run:create` |
| Cancel a delegation | `delegation:cancel`, `run:cancel` |

`*` satisfies every project action. Human principals must also have a current
membership in the project. The project ID in the URL must match the principal's
project; otherwise the API returns `403 forbidden` before resource access.

Use a new idempotency key for each logical mutation. Retrying the same route,
key, principal, project, and JSON body within 24 hours returns the stored public
response and sets `Idempotency-Replayed: true`. Reusing the key for a different
body returns `409 idempotency_conflict`.

### 1. Create the agent and version 1

This example uses explicit organization model and Daytona provider keys:

```bash theme={null}
curl --fail-with-body \
  --request POST \
  --header "Authorization: Bearer $OAO_API_KEY" \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: create-support-agent-v1' \
  --data '{
    "key": "support-agent",
    "name": "Support agent",
    "description": "Answers product-support questions.",
    "config": {
      "systemPrompt": "Answer support questions concisely. Never expose secrets or internal reasoning.",
      "modelPreset": "project-model-v1",
      "tools": [],
      "sandbox": {
        "enabled": false,
        "provider": "daytona-primary",
        "network": "none",
        "capabilities": ["filesystem_read", "filesystem_write", "shell"]
      },
      "limits": {
        "maxTurns": 32,
        "timeoutMs": 60000
      }
    }
  }' \
  "$OAO_API_URL/projects/$OAO_PROJECT_ID/agents"
```

The route returns `201 Created`. Its response has this exact field shape; UUIDs,
hash-dependent IDs, and timestamps below are illustrative values:

```json theme={null}
{
  "id": "7e1eb994-328a-4a17-a69f-998998d43984",
  "organizationId": "00000000-0000-4000-8000-000000000001",
  "projectId": "00000000-0000-4000-8000-000000000002",
  "key": "support-agent",
  "name": "Support agent",
  "description": "Answers product-support questions.",
  "latestVersionId": "a9976868-296b-4a1d-907f-a85388db1877",
  "version": 1,
  "model": "project-model-v1",
  "status": "published",
  "createdAt": "2026-08-20T10:01:00.000Z",
  "updatedAt": "2026-08-20T10:01:00.000Z"
}
```

Save both IDs:

```bash theme={null}
export OAO_AGENT_ID=7e1eb994-328a-4a17-a69f-998998d43984
export OAO_AGENT_VERSION_ID=a9976868-296b-4a1d-907f-a85388db1877
```

You may send `initialConfig` instead of `config`. One of them is required; OAO
does not publish a fake-backed default version. An empty description is stored
as no description and is returned as `"description": ""`.

### 2. Publish a tool-enabled version

Publication always sends the complete next configuration. It does not patch the
previous version.

```bash theme={null}
curl --fail-with-body \
  --request POST \
  --header "Authorization: Bearer $OAO_API_KEY" \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: publish-support-agent-v2' \
  --data '{
    "config": {
      "systemPrompt": "Answer support questions concisely. Use lookup_customer when account context is required.",
      "modelPreset": "project-model-v1",
      "tools": [
        {
          "schemaVersion": 1,
          "name": "lookup_customer",
          "description": "Look up safe customer support context.",
          "owner": "caller",
          "approval": "always",
          "inputSchema": {
            "type": "object",
            "properties": {
              "customerRef": {
                "type": "string",
                "description": "Customer id, account number, or exact customer name.",
                "minLength": 1,
                "maxLength": 200
              },
              "options": {
                "type": ["object", "null"],
                "description": "Optional provider-specific lookup options. Unknown keys are preserved."
              }
            },
            "required": ["customerRef"],
            "additionalProperties": false
          },
          "outputSchema": {
            "type": "object",
            "properties": {
              "found": {
                "type": "boolean"
              },
              "tier": {
                "enum": ["standard", "priority"]
              }
            },
            "required": ["found"],
            "additionalProperties": false
          }
        }
      ],
      "sandbox": {
        "enabled": false,
        "provider": "daytona-primary",
        "network": "none",
        "capabilities": ["filesystem_read", "filesystem_write", "shell"]
      },
      "limits": {
        "maxTurns": 32,
        "timeoutMs": 60000
      }
    }
  }' \
  "$OAO_API_URL/projects/$OAO_PROJECT_ID/agents/$OAO_AGENT_ID/versions"
```

The route returns `201 Created` with the newly inserted immutable version:

```json theme={null}
{
  "organizationId": "00000000-0000-4000-8000-000000000001",
  "projectId": "00000000-0000-4000-8000-000000000002",
  "id": "9f7e7819-6075-4b82-a7c0-47ca7bc45f76",
  "agentDefinitionId": "7e1eb994-328a-4a17-a69f-998998d43984",
  "version": 2,
  "config": {
    "systemPrompt": "Answer support questions concisely. Use lookup_customer when account context is required.",
    "modelPreset": "project-model-v1",
    "tools": [
      {
        "schemaVersion": 1,
        "name": "lookup_customer",
        "description": "Look up safe customer support context.",
        "owner": "caller",
        "approval": "always",
        "inputSchema": {
          "type": "object",
          "properties": {
            "customerRef": {
              "type": "string"
            }
          },
          "required": ["customerRef"],
          "additionalProperties": false
        },
        "outputSchema": {
          "type": "object",
          "properties": {
            "found": {
              "type": "boolean"
            },
            "tier": {
              "enum": ["standard", "priority"]
            }
          },
          "required": ["found"],
          "additionalProperties": false
        }
      }
    ],
    "sandbox": {
      "enabled": false,
      "provider": "daytona-primary",
      "network": "none",
      "capabilities": ["filesystem_read", "filesystem_write", "shell"]
    },
    "limits": {
      "maxTurns": 32,
      "timeoutMs": 60000
    }
  },
  "contentHash": "d4e2f52a12218960102f4f72552a6f80d02a5f19754902790986aaaf6274f799",
  "createdByPrincipalId": "00000000-0000-4000-8000-000000000003",
  "createdAt": "2026-08-20T10:02:00.000Z"
}
```

The example `contentHash` is illustrative; the API returns the actual lowercase
64-character SHA-256 hash of the canonicalized configuration. Publishing the
same configuration twice conflicts because content hashes are unique per agent.

### Tool contract

Each published tool has these fields:

| Field | Supported values |
| - | - |
| `schemaVersion` | `1`; it defaults to `1` when omitted |
| `name` | Non-empty string, at most 200 characters |
| `description` | Non-empty string, at most 2,000 characters |
| `owner` | `caller` or `platform` |
| `approval` | `never` or `always` |
| `inputSchema` | Supported object schema |
| `outputSchema` | Supported object schema |

Both root schemas must have `type: "object"`. `properties`, `required`, and
`additionalProperties` are optional; their defaults are `{}`, `[]`, and `true`.
The same canonical schema is compiled to Flue for model guidance and local
validation. The model receives the tool description plus property titles,
descriptions, examples, types, required fields, nesting, enums, and supported
constraints. OAO remains authoritative and validates arguments before a tool
can create an approval or execute.

Schema version 1 supports:

* String, number, integer, boolean, null, array, and object types.
* Nullable forms containing one type and null, such as
  `"type": ["string", "null"]`.
* Nested objects and homogeneous arrays with `items`.
* Primitive `enum` values and primitive `const`.
* `title`, `description`, and up to eight JSON `examples` on every schema node.
* `minLength`, `maxLength`, and the `date`, `date-time`, `email`, `time`, `uri`,
  and `uuid` string formats.
* `minimum`, `maximum`, `exclusiveMinimum`, `exclusiveMaximum`, and
  `multipleOf` numeric constraints.
* `minItems` and `maxItems` array bounds.
* Open objects, closed objects with `additionalProperties: false`, and typed
  maps whose `additionalProperties` is another supported schema.

The early contract intentionally rejects `pattern`, defaults, coercion,
`oneOf`, `anyOf`, `allOf`, discriminated unions, `$defs`, `$ref`, external or
recursive references, tuple schemas, and every unknown keyword. Rejection is
fail-closed and includes the exact schema path. Every name in `required` must
be unique and present in `properties`; `__proto__`, `constructor`, and
`prototype` property names are prohibited. A schema is limited to 64 KiB, 12
levels, 512 nodes, and 256 properties.
`enum` and `const` nodes may include a type and annotations, but not additional
constraints; this prevents a compiled provider schema from losing redundant or
conflicting guidance.

OAO does not silently downgrade a published schema for a model provider. The
MVP subset is chosen to work through the pinned Flue conversion and configured
OpenAI/OpenRouter adapters; if a downstream provider rejects that schema, the
model invocation fails visibly and no tool executes. Local argument and result
validation remains authoritative even when a provider uses the schema only as
generation guidance.

In the console's agent-version editor only the **Agent definition** section
(model preset and system instructions) starts open. Every other section —
**Tools**, **Harness Operations**, **Skills**, **MCP toolsets**, **Delegates**,
**Sandbox policy**, and **Recent sessions** — starts collapsed and summarizes
what the version binds in its heading (counts, or the sandbox provider,
network policy, capabilities, and timeout); select the heading to open it.
The model preset picker shows the selected preset's name, and one line below
it lists the model identifier, origin, and routing settings with a link to
**Models**. The editor lists tools as collapsible rows inside the
**Tools** panel. Each row opens to a syntax-highlighted
preview of the input and output schema. **Add tool** and **Edit definition**
open a modal editor for one tool definition as JSON, and **Edit JSON** opens
the complete tool list for authoring or pasting in bulk; both validate the JSON
before it can be applied to the draft. Publishing still creates a new immutable
version; existing versions are never rewritten. See
[Add tools to an agent](/agents/add-tools) for the complete console and API
workflow across caller, platform, sandbox, and MCP tools.

<Warning>
  Tool schemas are public agent configuration and are sent to the selected model
  provider. Never place credentials, authorization headers, personal secrets, or
  secret-bearing examples in names, descriptions, titles, enums, constants, or
  examples.
</Warning>

`caller` tools create durable work for an external operator or integration.
`platform` tools require a matching handler registered in the runtime worker.
`approval: "always"` creates a durable approval before either owner can
execute.

Retryable tool failures are returned to the model automatically. The model may
retry twice after the initial failure, for three total attempts per tool within
one run. OAO appends retry instructions to the managed system prompt and
enforces the attempt ceiling before another external call is published.
`tool_expired`, `tool_failed`, `platform_tool_failed`,
`invalid_tool_arguments`, and `invalid_tool_result` are retryable. Approval
denial or expiry, cancellation, and `tool_retry_exhausted` are not. Every retry
is a new fenced tool call with its own ID; successful completion resets the
consecutive-failure count.

<Warning>
  The default runtime does not register application-specific platform tool
  handlers. A published `platform` tool is valid, but an invocation fails with
  the safe `platform_tool_failed` outcome until the operator supplies a handler.
</Warning>

### Model presets

`config.modelPreset` must name a durable project preset linked to an
OpenRouter, OpenAI, Anthropic, or xAI provider in PostgreSQL. Runnable deployments do not expose a
built-in deterministic preset.

The check runs inside the same tenant transaction as the write, so an unknown
key is rejected with `400` before an agent or version row is created. Hosted
publication requires a provider connection in the same organization and platform
credential encryption. Provider API keys are write-only and stored as
tenant-scoped ciphertext, never in the agent config.

Hosted model identifiers start with `openrouter/`, `openai/`, `anthropic/`, or `xai/`
and must exist in the matching provider catalog. OpenRouter saved presets are exposed as
`openrouter/@preset/<slug>`. See [Adding models](/models/adding-models) for the
approval workflow and [HTTP API](/reference/http-api) for the preset contract.

### Sandbox policy

Set `sandbox.enabled` to `true` to attach the runtime's configured sandbox
adapter to the agent:

```json theme={null}
{
  "sandbox": {
    "enabled": true,
    "provider": "daytona-primary",
    "snapshotId": "77777777-7777-4777-8777-777777777777",
    "network": "restricted",
    "capabilities": ["filesystem_read", "filesystem_write", "shell", "browser"]
  }
}
```

`provider` is the key of an organization Daytona connection, such as
`daytona-primary`.
`snapshotId` is required when the sandbox is enabled and must be an active
snapshot UUID returned by that provider's snapshot discovery route. The
selected UUID is immutable version data. OAO does not provide an image fallback.
`network: "none"` blocks external egress. `restricted` uses that connection's
domain/CIDR allowlist and is rejected at publication when the allowlist is
empty. Capabilities map to tools as follows: `filesystem_read` adds `read`;
`filesystem_write` adds `write` and `edit`; `shell` adds `bash`, `grep`, and
`glob`; and `browser` adds `browser_navigate`, `browser_snapshot`, and
`browser_interact`.

The selected provider, snapshot, network policy, and capabilities are immutable
version data. The API key, target, and allowlist remain mutable project
connection configuration. See [Daytona sandboxes](/sandboxes/daytona).

When a coordinator version has delegates, its sandbox enabled state, provider,
snapshot, and network policy must match every selected child version because
their threads share one workspace sandbox. After changing the coordinator
snapshot, publish compatible child versions first or remove the incompatible
delegate bindings before publishing.

The publication contract does not expose arbitrary CPU, memory, disk, image, or
target selection. Those settings come from the selected Daytona snapshot.

## Create and continue a session through the API

### 1. Create the session and first run

Send `agentId` to pin its latest published version. You can alternatively send
`agentVersionId` directly. If you send both, the version must be that agent's
latest published version.

```bash theme={null}
curl --fail-with-body \
  --request POST \
  --header "Authorization: Bearer $OAO_API_KEY" \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: support-session-4831' \
  --data "{
    \"agentId\": \"$OAO_AGENT_ID\",
    \"title\": \"Northwind support request 4831\",
    \"initialMessage\": \"Summarize the customer's renewal options.\"
  }" \
  "$OAO_API_URL/projects/$OAO_PROJECT_ID/sessions"
```

The route atomically creates a thread, session, initial user message, queued
run, product events, audit entry, and durable runtime wake. It returns `201
Created` without waiting for the model:

```json theme={null}
{
  "organizationId": "00000000-0000-4000-8000-000000000001",
  "projectId": "00000000-0000-4000-8000-000000000002",
  "id": "7fd65b84-30ea-4ca9-aa40-ac219698e718",
  "threadId": "7b102c43-8dee-42ed-8736-a8c69afe5b98",
  "agentVersionId": "9f7e7819-6075-4b82-a7c0-47ca7bc45f76",
  "status": "queued",
  "createdAt": "2026-08-20T10:03:00.000Z",
  "lastActivityAt": "2026-08-20T10:03:00.000Z",
  "run": {
    "organizationId": "00000000-0000-4000-8000-000000000001",
    "projectId": "00000000-0000-4000-8000-000000000002",
    "id": "3b1d696c-70c8-4bfd-90df-79772205ee3c",
    "threadId": "7b102c43-8dee-42ed-8736-a8c69afe5b98",
    "sessionId": "7fd65b84-30ea-4ca9-aa40-ac219698e718",
    "agentVersionId": "9f7e7819-6075-4b82-a7c0-47ca7bc45f76",
    "createdByPrincipalId": "00000000-0000-4000-8000-000000000003",
    "state": "queued",
    "inputPublic": {
      "message": "Summarize the customer's renewal options."
    },
    "idempotencyKey": "support-session-4831",
    "cancellationRequestedAt": null,
    "admittedAt": null,
    "settledAt": null,
    "createdAt": "2026-08-20T10:03:00.000Z",
    "updatedAt": "2026-08-20T10:03:00.000Z"
  },
  "latestRunId": "3b1d696c-70c8-4bfd-90df-79772205ee3c"
}
```

Save the session and run IDs from the real response:

```bash theme={null}
export OAO_SESSION_ID=7fd65b84-30ea-4ca9-aa40-ac219698e718
export OAO_RUN_ID=3b1d696c-70c8-4bfd-90df-79772205ee3c
```

Both this route and later-turn routes accept an optional `files` array. Each
file contains `name`, `contentType`, and canonical `dataBase64`; bytes are never
returned in the response, transcript, audit log, or SSE. See
[Send files to an agent](/integrations/files) for supported types, limits, and
complete SDK/API examples. The agent version must enable a sandbox with read
tooling, and binary files require `shell`. OAO copies the original bytes into
that sandbox without preprocessing. A session or turn may omit its text field
only when at least one file is present.

### 2. Wait for the run to settle

Read the session until top-level `status` is `completed`, `failed`, `cancelled`,
or `timed_out`:

```bash theme={null}
curl --fail-with-body \
  --header "Authorization: Bearer $OAO_API_KEY" \
  "$OAO_API_URL/projects/$OAO_PROJECT_ID/sessions/$OAO_SESSION_ID"
```

The detailed response contains the session summary plus:

* `runs`: every run in chronological order.
* `transcript`: public, redacted messages across the session.
* `timeline`: safe runtime entries.
* `pendingWork`: unresolved tool and approval work.
* `debug.productEvents`, `debug.modelInvocations`, `debug.toolCalls`,
  `debug.approvals`, `debug.sandboxes`, and `debug.workspaceBackups`.

The top-level session usage includes `inputTokens`, `outputTokens`,
`cacheReadTokens`, and `cacheWriteTokens`. Cache reads are prompt tokens reused
from the provider cache; cache writes are prompt tokens newly added to it. The
same fields are available for each entry in `debug.modelInvocations`.

The run state progression can include `queued`, `running`,
`waiting_for_tool`, `waiting_for_approval`, and `retry_scheduled` before a
terminal state.

### 3. Send a follow-up message

The latest run must be settled before the next message can be accepted:

```bash theme={null}
curl --fail-with-body \
  --request POST \
  --header "Authorization: Bearer $OAO_API_KEY" \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: support-session-4831-follow-up-1' \
  --data '{
    "message": "Now list only the renewal exceptions."
  }' \
  "$OAO_API_URL/projects/$OAO_PROJECT_ID/sessions/$OAO_SESSION_ID/runs"
```

`redactedInput` is accepted as an alias for `message`. The route returns `202
Accepted` with the new run object:

```json theme={null}
{
  "organizationId": "00000000-0000-4000-8000-000000000001",
  "projectId": "00000000-0000-4000-8000-000000000002",
  "id": "f933bbdb-36b0-4afc-8bf5-62814f6154fb",
  "threadId": "7b102c43-8dee-42ed-8736-a8c69afe5b98",
  "sessionId": "7fd65b84-30ea-4ca9-aa40-ac219698e718",
  "agentVersionId": "9f7e7819-6075-4b82-a7c0-47ca7bc45f76",
  "createdByPrincipalId": "00000000-0000-4000-8000-000000000003",
  "state": "queued",
  "inputPublic": {
    "message": "Now list only the renewal exceptions."
  },
  "idempotencyKey": "support-session-4831-follow-up-1",
  "cancellationRequestedAt": null,
  "admittedAt": null,
  "settledAt": null,
  "createdAt": "2026-08-20T10:04:00.000Z",
  "updatedAt": "2026-08-20T10:04:00.000Z"
}
```

Follow-up runs retain the session's `agentVersionId`. Publishing a newer agent
version does not upgrade an existing session; create a new session to use the
new version.

<Note>
  `POST /runs/:runId/resume` also accepts `message` or `redactedInput`, but only
  for a settled run. It creates another run on that run's existing session. The
  console currently uses the session follow-up route and does not expose the
  resume action.
</Note>

## Validate and debug

### In the console

Open a session and use:

* **Transcript** for user, assistant, reasoning, and tool-facing records in
  chronological order. User and assistant messages render CommonMark plus
  GitHub Flavored Markdown, including lists, emphasis, links, code, task lists,
  and tables. Raw HTML is not rendered. A persistent sandbox in `running` state
  is ready for the session and is not shown as pending work.
* The session sidebar for the Agent name and exact pinned Agent version used by
  the session, alongside its model, Tools, Skills, files, cost, and token usage.
  Files present in the latest persistent workspace backup can be downloaded
  through the secured Files API without reopening the Daytona sandbox.
* The sessions overview and compact session facts for the latest run's elapsed
  time. The overview boxes active timers in a red live status badge with a play
  icon, and the detail view colors its active counter red as well. The timer
  updates every second while the run is active and freezes when the run settles.
* The message composer for another durable run after the latest run settles.
  Select **Send** or press <kbd>Command</kbd>+<kbd>Enter</kbd>; Enter by itself
  adds a new line. On viewports 1024px wide or narrower, or 820px tall or
  shorter, the composer starts
  minimized as a single **Send a message to the agent** bar so it does not
  cover the transcript; select it to open the form, and use **Minimize
  composer** to fold it away again. A draft and any attached files survive
  minimizing. The session header is also more compact there: the ID eyebrow is
  hidden and the fact strip scrolls horizontally in one row.
* **Debug** for a chronological waterfall assembled from timeline entries,
  public product events, model invocations, caller tool calls, sandbox commands,
  approvals, and sandboxes. Sandbox activity includes its model-facing
  arguments, result, state, and timing.
* The timeline strip above the transcript. Hovering a block shows its actor,
  a snippet, duration, and offset; hovering a message or row in the
  transcript highlights its block and shows the same tooltip there. Clicking
  a block jumps the transcript to that event.
* Reasoning and sandbox commands are expandable rows in **Transcript**. Each
  reasoning row shows the provider thinking text when supplied and the full
  model invocation duration. Each sandbox row shows its recorded input/output.
* The event inspector for rendered metadata. Its **Raw** view is available for
  reasoning and sandbox calls, and remains unavailable when only a redacted
  public projection exists.
* **Pending Work** to inspect approval gates and caller-owned tool calls.

Provider thinking, file/edit content, shell commands, browser arguments, and
tool output are part of the authorized session read model. Credentials,
authorization headers, provider reasoning signatures, and unrestricted product
events remain excluded. A model catalog's reasoning-support flag still
describes capability; thinking text appears only when the provider returns it.

### Through the API

Useful read endpoints are:

```text theme={null}
GET /v1/projects/:projectId/agents/:agentId
GET /v1/projects/:projectId/sessions/:sessionId
GET /v1/projects/:projectId/runs/:runId
GET /v1/projects/:projectId/runs/:runId/messages
GET /v1/projects/:projectId/runs/:runId/timeline
GET /v1/projects/:projectId/tool-calls?runId=:runId
GET /v1/projects/:projectId/approvals?runId=:runId
GET /v1/projects/:projectId/pending-work
```

List endpoints use cursor pagination. `limit` defaults to `50` and must be from
`1` through `200`; pass `pageInfo.nextCursor` as the next request's `cursor`.
The run timeline instead uses its numeric `entrySequence` as the cursor.

For live updates, connect to the durable project event stream:

```bash theme={null}
curl --no-buffer \
  --header "Authorization: Bearer $OAO_API_KEY" \
  --header 'Accept: text/event-stream' \
  "$OAO_API_URL/projects/$OAO_PROJECT_ID/events"
```

Each SSE frame has an opaque `id`, the product event kind in `event`, and a
public product-event object in `data`. Reconnect with the most recent ID:

```bash theme={null}
curl --no-buffer \
  --header "Authorization: Bearer $OAO_API_KEY" \
  --header 'Accept: text/event-stream' \
  --header "Last-Event-ID: $OAO_LAST_EVENT_ID" \
  "$OAO_API_URL/projects/$OAO_PROJECT_ID/events"
```

The cursor is backed by PostgreSQL project position; notifications only wake
readers and are not the source of truth. Use `?once=true` to read the currently
available page and close the connection.

### Resolve caller tools correctly

For a `caller` tool, claim the work, retain the returned positive-string
`fence`, and submit a versioned result envelope. The `value` must match the
published `outputSchema`:

```json theme={null}
{
  "fence": "1",
  "safeResult": {
    "version": 1,
    "status": "success",
    "value": {
      "found": true,
      "tier": "priority"
    }
  }
}
```

The relevant mutation endpoints are:

```text theme={null}
POST /v1/projects/:projectId/tool-calls/:toolCallId/claim
POST /v1/projects/:projectId/tool-calls/:toolCallId/renew
POST /v1/projects/:projectId/tool-calls/:toolCallId/release
POST /v1/projects/:projectId/tool-calls/:toolCallId/result
POST /v1/projects/:projectId/approvals/:approvalId/decision
```

Claims and results are fenced so a stale worker cannot submit after losing its
lease. Approval decisions use `{ "status": "approved" }` or
`{ "status": "denied" }`, with an optional `note` of at most 2,000 characters.

OAO validates successful `value` objects against the immutable agent version's
published `outputSchema` in the same transaction, before PostgreSQL persists
the result. A malformed success is replaced with a safe
`invalid_tool_result` failure; the raw malformed value is not persisted or sent
to the model. The `202` response includes a `normalizedFailure` object with the
first failing path, and Flue resumes the agent so it can make a new attempt:

```json theme={null}
{
  "outcome": "submitted",
  "normalizedFailure": {
    "code": "invalid_tool_result",
    "path": "safeResult.value.status"
  }
}
```

The Console **Pending Work** form accepts only the successful result value and
adds the versioned envelope automatically. Failure envelopes remain available
through the API and SDK for integrations that need to report an explicit tool
failure.

### Errors and cancellation

Errors use one public envelope:

```json theme={null}
{
  "error": {
    "code": "bad_request",
    "message": "config must match the managed-agent publication contract",
    "requestId": "7baecdc8-0cce-4b41-b0a3-32af443ac2ec"
  }
}
```

Possible codes are `bad_request`, `unauthenticated`, `forbidden`, `not_found`,
`conflict`, `idempotency_conflict`, `rate_limited`, and `internal_error`. Keep
the `x-request-id` response header or `error.requestId` when investigating.
Internal failures intentionally return a generic message.

Cancellation is asynchronous:

```bash theme={null}
curl --fail-with-body \
  --request POST \
  --header "Authorization: Bearer $OAO_API_KEY" \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: cancel-support-run-1' \
  --data '{}' \
  "$OAO_API_URL/projects/$OAO_PROJECT_ID/runs/$OAO_RUN_ID/cancel"
```

The `202 Accepted` response reports the current run. Continue reading the run
or event stream until it settles; acceptance is a cancellation request, not
proof that active model or tool work has already stopped.

## Delete an agent

Delete an agent from the console with the trash icon at the end of its row on
the **Agents** page, or with **Delete agent** in the header of its detail page.
Both ask for confirmation first. Through the API, send
`DELETE /v1/projects/{projectId}/agents/{agentId}` with an `Idempotency-Key`.

Deletion archives the agent instead of removing rows, because sessions, runs,
and delegate bindings reference its immutable versions:

* The agent leaves the agents list, the delegate picker, and every lookup;
  reading it returns `404`.
* It can no longer publish versions or start sessions, whether addressed by
  `agentId` or by one of its `agentVersionId`s.
* Existing sessions keep their full transcripts and still show the agent's
  name. Coordinators that already pin one of its versions keep delegating to it.
* Its key is released, so a new agent can reuse it.
* The audit log records `agent.deleted`. There is no restore.

## Current MVP limitations

* Agent creation always publishes version 1. There are no draft, rename, or
  description-edit routes. Deletion archives the agent (see
  [Delete an agent](#delete-an-agent)); there is no restore route.
* Versions are append-only and content-addressed. Identical content cannot be
  published twice for one agent.
* The console can edit instructions, model preset, sandbox provider, sandbox
  capabilities, network policy, and timeout while creating an agent and on its
  latest version. Caller/platform tools are authored as JSON in the **Tools**
  panel; there is no form-based schema builder.
* Model presets can be added, duplicated into a new key, and archived, but never
  edited in place. Preset rows are append-only by design.
* Sessions never float to a newer version, and only one unsettled run is
  allowed per session.
* The console exposes cancellation but not API run resume or branch replay.
* Maximum model turns is editable in agent configuration: a required integer from
  `1` through `256`. New agents default to `32` to preserve existing behavior;
  `128` is useful for longer bulk imports. The upper bound limits runaway work
  and cost. Publishing creates an immutable version; existing sessions retain
  their selected version and limit. Start a new session to use the new version.
* Turns are reserved durably per run before model invocation, including scratch
  model calls. Compaction does not reset the budget; concurrent calls share it.
  Replaying the same Flue turn ID does not consume an additional reservation.
  Provider-internal HTTP retries are not separate turns. Each new run has its own
  budget, and delegated runs use their own agent version's limit.
* Reaching the budget fails the run with `model_turn_limit_exceeded` and a safe
  explanation. Raising the limit does not extend the timeout.
* The console caps the execution budget `timeoutMs` at one hour, while the API
  publication schema currently enforces only the 1,000 ms minimum. Approval
  waiting pauses that budget. Flue separately caps each new submission at 48 hours
  of total wall time, shared across all approval waits and execution; submissions
  that already started retain their stored ceiling. See
  [Approval waiting window](/agents/add-tools#approval-waiting-window).
* Automated tests may inject deterministic provider doubles at adapter
  boundaries, but those providers cannot be selected through the API or
  console and are not runtime deployment options.
* `restricted` sandbox networking uses the selected project connection's
  mutable egress allowlist; the agent version records the policy mode and
  provider key, not a copy of that allowlist.
* API keys can be created and listed in the console. The plaintext secret is
  available only in the acknowledgement dialog immediately after creation.
* The console fetches up to 100 agents or sessions and then applies search,
  status, date, and ten-row pagination locally. Use API cursors for complete
  large collections.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.