openapi: 3.0.3
info:
  title: Agents API
  version: 1.0.0
  description: >
    AI Agents with tool-use capabilities. Create agents bound to AI providers,
    attach tools, run multi-step generations, and inspect execution traces.
  contact:
    name: SOAT API Support

servers:
  - url: '{baseUrl}'
    description: Base URL of your SOAT deployment (e.g. https://your-soat.com or http://localhost:5047)
    variables:
      baseUrl:
        description: The base URL of your SOAT deployment
        default: http://localhost:5047

tags:
  - name: Agents
    description: Manage AI agents
  - name: Agent Versions
    description: Agent config history and staged rollout
  - name: Agent Traces
    description: View agent traces
security:
  - bearerAuth: []

paths:
  /api/v1/agents:
    post:
      tags:
        - Agents
      summary: Create an agent
      description: Creates a new agent bound to an AI provider.
      operationId: createAgent
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateAgentRequest'
            examples:
              minimal:
                summary: Minimal agent
                value:
                  ai_provider_id: aip_V1StGXR8Z5jdHi6B
              full:
                summary: Agent with tools and instructions
                value:
                  ai_provider_id: aip_V1StGXR8Z5jdHi6B
                  name: Research Assistant
                  instructions: You are a helpful research assistant.
                  model: gpt-4o
                  tool_bindings:
                    - tool_id: tool_abc123
                  max_steps: 10
                  temperature: 0.7
      responses:
        '201':
          description: Agent created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Agent'
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '404':
          description: AI provider not found
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
    get:
      tags:
        - Agents
      summary: List agents
      description: Returns all agents in the project.
      operationId: listAgents
      parameters:
        - name: project_id
          in: query
          required: false
          schema:
            type: string
          description: Project public ID to filter by
        - $ref: './pagination.yaml#/components/parameters/Limit'
        - $ref: './pagination.yaml#/components/parameters/Offset'
      responses:
        '200':
          description: List of agents
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - total
                  - limit
                  - offset
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Agent'
                  total:
                    type: integer
                  limit:
                    type: integer
                  offset:
                    type: integer
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'

  /api/v1/agents/{agent_id}:
    get:
      tags:
        - Agents
      summary: Get an agent
      description: >
        Returns a single agent by ID. A credential scoped to a project the
        agent is shared with, through an accepted share, reads its `id` and
        `name` only.
      operationId: getAgent
      x-soat-resource:
        kind: agent
        from: agent_id
      parameters:
        - $ref: '#/components/parameters/AgentId'
      responses:
        '200':
          description: Agent details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Agent'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
    put:
      tags:
        - Agents
      summary: Update an agent
      description: Updates an existing agent. Identical to PATCH — both perform partial updates.
      operationId: updateAgent
      x-soat-resource:
        kind: agent
        from: agent_id
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - $ref: './concurrency.yaml#/components/parameters/IfMatchVersion'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateAgentRequest'
      responses:
        '200':
          description: Agent updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Agent'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '409':
          $ref: './concurrency.yaml#/components/responses/VersionConflict'
    patch:
      tags:
        - Agents
      summary: Partially update an agent
      description: Partially updates an existing agent. Identical to PUT — both perform partial updates.
      operationId: patchAgent
      x-soat-resource:
        kind: agent
        from: agent_id
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - $ref: './concurrency.yaml#/components/parameters/IfMatchVersion'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateAgentRequest'
      responses:
        '200':
          description: Agent updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Agent'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '409':
          $ref: './concurrency.yaml#/components/responses/VersionConflict'
    delete:
      tags:
        - Agents
      summary: Delete an agent
      description: >
        Deletes an agent by ID. Fails with `409` if the agent has dependent
        generations or traces, or another project has accepted a share of it,
        unless `force=true` is passed, in which case those generations and
        traces are deleted along with the agent and the shares are revoked.
        Every share of the agent is revoked when it is deleted.
      operationId: deleteAgent
      x-soat-resource:
        kind: agent
        from: agent_id
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - name: force
          in: query
          required: false
          description: >
            When `true`, deletes the agent's dependent generations and traces,
            revokes its accepted shares and leaves its ingestion rules naming
            it instead of returning `409 AGENT_HAS_DEPENDENTS`.
          schema:
            type: boolean
            default: false
      responses:
        '204':
          description: Deleted
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '409':
          description: >
            Agent has dependent generations, traces, accepted shares or
            ingestion rules in its own project (pass `force=true` to delete
            anyway; records and rules another project made through a share are
            kept). `error.meta` carries `generation_count`, `trace_count`,
            `accepted_share_count` and `ingestion_rule_count` so a caller can
            tell which one is nonzero.
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'

  /api/v1/agents/{agent_id}/generate:
    post:
      tags:
        - Agents
      summary: Run an agent generation
      description: >
        Sends messages to the agent, resolves its tools, and runs the AI model
        loop. Background by default: returns `202 Accepted` with a
        `generation_id` to poll via `GET /api/v1/generations/{generation_id}`.
        Pass `?wait=true` to block and receive the result inline, where client
        tools pause the generation and return `requires_action`. Streaming
        (`stream: true`) implies waiting. Pass `idempotency_key` to make a retry
        safe: a request whose key is already claimed runs nothing and answers
        `202` with the generation the key names.


        A credential scoped to a project the agent is shared with, through an
        accepted share, runs it in that project: on the publisher's
        configuration, recorded, metered and governed in the grantee project.
      operationId: createAgentGeneration
      x-soat-resource:
        kind: agent
        from: agent_id
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - $ref: './idempotency.yaml#/components/parameters/IdempotencyKey'
        - name: wait
          in: query
          required: false
          # Pinned to `true` for tool calls: a nested agent-to-agent call has no
          # channel to poll a background generation later, so it must block —
          # the mirror image of `stream`, which a tool call cannot receive.
          x-soat-tool-forced: true
          description: 'When omitted or `false` (default), the generation runs in the background and `202 Accepted` is returned immediately with a `generation_id` to poll. Pass `true` to block until the generation settles and receive the result. Mutually exclusive with `stream: true`. A tool call, `builtin` or MCP, always waits.'
          schema:
            type: boolean
            default: false
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateAgentGenerationRequest'
            examples:
              basic:
                summary: Simple generation
                value:
                  messages:
                    - role: user
                      content: What is the weather in Tokyo?
              toolOutput:
                summary: Use a tool output as user message content
                value:
                  messages:
                    - role: user
                      content:
                        type: tool_output
                        tool_id: tool_audio_to_text
                        input:
                          url: https://example.com/audio.mp3
                        output_path: text
              streaming:
                summary: Streaming generation
                value:
                  messages:
                    - role: user
                      content: Summarize the latest report.
                  stream: true
      responses:
        '200':
          description: 'Generation result or SSE stream (only when `?wait=true` or `stream: true`)'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentGenerationResponse'
            text/event-stream:
              schema:
                type: string
                description: >
                  SSE stream of delta chunks ending with `data: [DONE]`.


                  A turn that pauses on a `client` tool ends with one more frame
                  before `[DONE]`: the generation as the non-streaming path
                  returns it, `status: requires_action` with `required_action`
                  naming the pending calls. Submit their results to
                  `tool-outputs` to resume.


                  The response headers are written before the provider is
                  called, so a failure cannot become a status code once the
                  stream is open. It arrives instead as a terminal
                  `data: {"error": "..."}` frame carrying the same mapped
                  message the non-streaming path returns in its `502` body
                  (e.g. `Provider returned 404: ...`), and the stream then ends
                  **without** a `[DONE]` — the absence of that sentinel is how a
                  caller tells a truncated answer from a complete one. Chunks
                  produced before the failure are still delivered, and the
                  generation is recorded as `failed`.
        '202':
          description: >-
            Generation accepted and running in the background (default, when
            `wait` is omitted or `false`), or — in every mode, streamed
            included — a duplicate request: the generation the
            `idempotency_key` already names is returned in whatever state it
            has reached and nothing runs. Poll
            `GET /api/v1/generations/{generation_id}` for the result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AcceptedGenerationResponse'
        '400':
          description: >-
            Bad Request (e.g. an `idempotency_key` that is not a non-empty
            string of at most 255 characters)
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '404':
          description: Agent or AI provider not found
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '409':
          description: >-
            `IDEMPOTENCY_KEY_REUSED` — the key is already claimed by a
            generation started from a different request. Nothing runs.
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '429':
          description: >-
            `QUOTA_EXCEEDED`: an `enforce`-mode generation quota is exhausted;
            `error.meta` carries `quota_id`, `metric`, `limit`, `window` and
            `resets_at`. `SHARE_CAP_EXCEEDED`: the agent is another project's,
            reached through a share whose `cap` is spent for the window;
            `error.meta.retry_after` says when. Both carry a `Retry-After`
            header.
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '502':
          description: >
            Upstream AI provider error (AI_PROVIDER_ERROR); model output that
            does not satisfy the agent's `output_schema`
            (OUTPUT_SCHEMA_VALIDATION_FAILED — the violated field is named in
            the message); or a model that wrote a tool invocation out as plain
            assistant text instead of calling the tool, so the tool never ran
            (TEXT_ENCODED_TOOL_CALL — `meta.tool_name` names the tool). The
            error `meta` includes the `generation_id` and
            `trace_id` of the failed
            generation for post-mortem debugging via GET /api/v1/generations/{generation_id}.
            Streaming requests report the provider error in a terminal SSE frame
            instead, since their status line is already on the wire.
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'

  /api/v1/agents/{agent_id}/generate/{generation_id}/tool-outputs:
    post:
      tags:
        - Agents
      summary: Submit tool outputs for a paused generation
      description: >
        Resumes a generation that was paused due to client tool calls.
        Provide tool outputs for each pending tool call.
      operationId: submitAgentToolOutputs
      x-soat-resource:
        kind: agent
        from: agent_id
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - $ref: '#/components/parameters/GenerationId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SubmitToolOutputsRequest'
      responses:
        '200':
          description: Generation result after resuming
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentGenerationResponse'
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '404':
          description: Agent or generation not found
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '409':
          description: >
            The generation is not paused on client tool calls
            (GENERATION_NOT_AWAITING_TOOL_OUTPUTS): it never paused, or its
            outputs were already submitted. Each pause accepts outputs once.
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '502':
          description: >
            Upstream AI provider error (AI_PROVIDER_ERROR); model output that
            does not satisfy the agent's `output_schema`
            (OUTPUT_SCHEMA_VALIDATION_FAILED); or a model that wrote a tool
            invocation out as plain assistant text instead of calling the tool
            (TEXT_ENCODED_TOOL_CALL — `meta.tool_name` names the tool). The
            resumed generation is recorded `failed`.
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'

  /api/v1/agents/{agent_id}/versions:
    get:
      tags:
        - Agent Versions
      summary: List an agent's config versions
      description: >
        Returns the agent's archived configurations, newest first. A version is
        written on create and on every subsequent write that changes the config —
        through the REST API or a formation apply alike. See
        [Versioning and Staged Rollout](/docs/modules/agents#versioning-and-staged-rollout).
      operationId: listAgentVersions
      x-soat-resource:
        kind: agent
        from: agent_id
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - $ref: './pagination.yaml#/components/parameters/Limit'
        - $ref: './pagination.yaml#/components/parameters/Offset'
      responses:
        '200':
          description: List of agent versions, newest first
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - total
                  - limit
                  - offset
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/AgentVersion'
                  total:
                    type: integer
                  limit:
                    type: integer
                  offset:
                    type: integer
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '404':
          description: Agent not found
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'

  /api/v1/agents/{agent_id}/versions/{version}:
    get:
      tags:
        - Agent Versions
      summary: Get an archived agent config version
      description: >
        Returns the exact configuration the agent held at a given version, so a
        generation can be traced back to the config that produced it.
      operationId: getAgentVersion
      x-soat-resource:
        kind: agent
        from: agent_id
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - $ref: '#/components/parameters/AgentVersionNumber'
      responses:
        '200':
          description: Archived agent version
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentVersion'
        '400':
          description: Bad Request — version is not a positive integer
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '404':
          description: Agent or version not found
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'

  /api/v1/agents/{agent_id}/versions/{version}/restore:
    post:
      tags:
        - Agent Versions
      summary: Restore an archived config as a new version
      description: >
        Copies the named version's configuration onto the agent as a **new**
        version rather than rewinding the counter, so history stays append-only
        and the versions in between remain retrievable. Restoring the config the
        agent already holds is a no-op and creates no version.


        The restored config fully replaces the current one: a field the archived
        version did not set is cleared, not merged. Restore re-validates the
        config, so a tool, provider, or guardrail deleted since the snapshot was
        taken fails the request instead of writing a broken agent.
      operationId: restoreAgentVersion
      x-soat-resource:
        kind: agent
        from: agent_id
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - $ref: '#/components/parameters/AgentVersionNumber'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RestoreAgentVersionRequest'
      responses:
        '200':
          description: The agent, at its new version
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Agent'
        '400':
          description: Bad Request — invalid version, or the archived config no longer validates
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '404':
          description: Agent or version not found
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '409':
          $ref: './concurrency.yaml#/components/responses/VersionConflict'

  /api/v1/agents/{agent_id}/release:
    put:
      tags:
        - Agent Versions
      summary: Set or replace a staged rollout
      description: >
        Starts serving two archived versions side by side: `canary_percent` of
        traffic gets `canary_version`, the rest gets `stable_version`.


        Assignment is deterministic — it hashes the actor behind the request's
        session (falling back to the session itself), so one end user never
        flip-flops between configs mid-conversation. Requests with neither are
        split randomly.


        While a release is active the agent's live columns act as a **draft**:
        further edits archive new versions but do not disturb either side of the
        running split. End the rollout with `promote` or `abort`.
      operationId: setAgentRelease
      x-soat-resource:
        kind: agent
        from: agent_id
      parameters:
        - $ref: '#/components/parameters/AgentId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SetAgentReleaseRequest'
      responses:
        '200':
          description: The agent, with its active release set
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Agent'
        '400':
          description: Bad Request — malformed input, or a version that does not exist
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '404':
          description: Agent not found
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'

  /api/v1/agents/{agent_id}/release/promote:
    post:
      tags:
        - Agent Versions
      summary: Promote the canary and end the rollout
      description: >
        Makes the canary version's config the agent's live config and clears the
        release. The canary is pinned by version, so an edit that landed
        mid-rollout is not promoted in its place — it stays an unreleased draft
        in the version history.


        When the release carries a `promotion_gate`, the eval it names must have
        a run that finished `completed` with `passed: true` **and** was pinned to
        the canary version (`agent_version`); otherwise the call is a `409` and
        the rollout is left running untouched. The run that cleared the gate is
        recorded as `eval_run_id` on the version that goes live.
      operationId: promoteAgentRelease
      x-soat-resource:
        kind: agent
        from: agent_id
      parameters:
        - $ref: '#/components/parameters/AgentId'
      responses:
        '200':
          description: The agent, now serving the promoted config
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Agent'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '404':
          description: Agent not found
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '409':
          description: >-
            Conflict — the agent has no active release
            (`NO_ACTIVE_RELEASE`), or its `promotion_gate` has no passing eval
            run against the canary version (`PROMOTION_GATE_UNMET`)
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'

  /api/v1/agents/{agent_id}/release/abort:
    post:
      tags:
        - Agent Versions
      summary: Abort the rollout and roll back to stable
      description: >
        Restores the stable version's config as the agent's live config and
        clears the release, so all traffic returns to the configuration the
        rollout was measured against — not to whatever draft the live columns
        happened to hold.
      operationId: abortAgentRelease
      x-soat-resource:
        kind: agent
        from: agent_id
      parameters:
        - $ref: '#/components/parameters/AgentId'
      responses:
        '200':
          description: The agent, back on the stable config
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Agent'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '404':
          description: Agent not found
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '409':
          description: Conflict — the agent has no active release
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
components:
  parameters:
    AgentId:
      name: agent_id
      in: path
      required: true
      description: Agent public ID
      schema:
        type: string
        example: agent_V1StGXR8Z5jdHi6B
    GenerationId:
      name: generation_id
      in: path
      required: true
      description: Public ID of the generation paused at `requires_action`
      schema:
        type: string
        example: gen_V1StGXR8Z5jdHi6B
    AgentVersionNumber:
      name: version
      in: path
      required: true
      description: Archived config version number
      schema:
        type: integer
        minimum: 1
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: JWT token or sk_ api key
  schemas:
    Agent:
      type: object
      properties:
        id:
          type: string
          description: Public ID of the agent
          example: agent_V1StGXR8Z5jdHi6B
        project_id:
          type: string
          description: Public ID of the owning project
          x-soat-ref: projects
        ai_provider_id:
          type: string
          nullable: true
          description: >-
            Public ID of the pinned AI provider. Null when the agent resolves
            its model through `model_route_id` instead.
          x-soat-ref: ai-providers
        model_route_id:
          type: string
          nullable: true
          description: >-
            Public ID of the model route that resolves this agent's completion
            model. Null when the agent pins a provider through
            `ai_provider_id`. Mutually exclusive with `ai_provider_id` and
            `model`.
          x-soat-ref: model-routes
        name:
          type: string
          nullable: true
          description: Display name
        instructions:
          type: string
          nullable: true
          description: System instructions guiding behavior
        model:
          type: string
          nullable: true
          description: Model identifier
        tool_bindings:
          type: array
          nullable: true
          items:
            $ref: '#/components/schemas/ToolBindingRead'
          description: >-
            Tools attached to this agent, one binding object per tool — the
            canonical attachment field. See
            [Tool Bindings](/docs/modules/agents#tool-bindings).
        max_steps:
          type: integer
          nullable: true
          description: >-
            Maximum agent loop steps before stopping. The budget bounds a
            **turn**: a generation that pauses at `requires_action` and resumes
            after `submit-tool-outputs` continues the same turn and spends what
            is left of it, so a turn that arrives with nothing left completes
            with `stop_reason: "max_steps"` instead of calling the model again.
        tool_choice:
          description: 'Tool choice strategy. Accepts a string (`"auto"`, `"required"`) or an object (`{ "type": "tool", "tool_name": "my_tool" }`). A forcing value (`"required"` or the object form) forbids a final assistant message on every step of every turn, including a resumed or continued one, so it requires a `has_tool_call` entry in `stop_conditions` — otherwise the write is refused with `FORCED_TOOL_CHOICE_CANNOT_STOP`.'
        stop_conditions:
          type: array
          nullable: true
          items:
            type: object
          description: >-
            Conditions that end the agent's work early, on top of `max_steps` —
            turn-scoped (`has_tool_call`) or chain-scoped
            (`max_chain_generations`). See the create request body for the
            accepted shapes.
        active_tool_ids:
          x-soat-ref: tools
          type: array
          nullable: true
          items:
            type: string
          description: Subset of the bound tools that are active
        guardrail_ids:
          x-soat-ref: guardrails
          type: array
          nullable: true
          items:
            type: string
          description: >-
            Guardrails attached at the agent scope, governing every tool call
            the agent makes.
        step_rules:
          type: array
          nullable: true
          items:
            type: object
          description: >-
            Per-step overrides of `tool_choice` and `active_tool_ids`. Steps are
            numbered from the first step of the **turn**, and that numbering
            spans a `requires_action` pause — a rule fires once per turn, not
            once per resumption.
        boundary_policy:
          type: object
          nullable: true
          description: Allowed/denied SOAT actions
        temperature:
          type: number
          nullable: true
          description: Sampling temperature
        knowledge_config:
          type: object
          nullable: true
          description: Knowledge retrieval config injected before every generation
          properties:
            memory_store_ids:
              x-soat-ref: memory-stores
              type: array
              items:
                type: string
            document_ids:
              x-soat-ref: documents
              type: array
              items:
                type: string
            document_paths:
              type: array
              items:
                type: string
            tags:
              description: >-
                Key-value pairs a result's own `tags` must all contain (exact
                match). Scopes documents and memories alike.
              allOf:
                - $ref: './tags.yaml#/components/schemas/TagBag'
            min_score:
              type: number
              description: >
                Minimum raw cosine similarity (0–1) a vector candidate must
                reach to be ranked, the floor the search endpoint spells
                `min_similarity`. No default: omitted means no floor at all,
                and every one of the `limit` nearest chunks is injected however
                weak it is.
            rrf_k:
              type: integer
              description: >
                The `k` in the fusion term `1 / (k + rank)`, the same knob
                knowledge search takes. Smaller weights the top of each ranking
                more heavily. Omitted, the deployment's `KNOWLEDGE_RRF_K`
                applies.
            recency_half_life_days:
              type: number
              description: >
                Half-life in days of the decay applied to **memory** results
                after fusion, the same knob knowledge search takes. `0`
                disables it. Omitted, the deployment's
                `KNOWLEDGE_RECENCY_HALF_LIFE_DAYS` applies.
            limit:
              type: integer
              description: >
                Maximum number of results to inject. Omitted, 10 are injected.
            write_memory_store_id:
              x-soat-ref: memory-stores
              type: string
              nullable: true
              description: Public ID of the memory store the agent can write to during generation. When set, a write_memory tool is automatically available to the agent.
        output_schema:
          type: object
          nullable: true
          description: 'JSON Schema describing the structured object the model must return. When set, non-streaming generations constrain output to this schema and the parsed value is returned as `output.object`. The schema is enforced on the way back, not just sent to the model: an object that violates it fails the generation with 502 `OUTPUT_SCHEMA_VALIDATION_FAILED`, naming the violated field. Constraints beyond `required`/`type` (`minLength`, `enum`, `pattern`, `minItems`) are honored and are what reject a structurally valid but degenerate answer. See the Structured Output section in the Agents module docs.'
        prompt_caching:
          type: object
          nullable: true
          description: >-
            Prompt caching for this agent's turns. `{"enabled": true}` marks two
            cache breakpoints: the end of the system block, which covers the tool
            definitions and the instructions, and the last message of each step,
            which covers the conversation so far. A provider that caches by
            explicit breakpoint then reads, on every later step and every later
            turn of the session, the whole prefix the previous one sent, and
            writes only what was appended. Null or omitted is off: a cache write
            costs more than an uncached token, so an agent whose prefix is never
            re-read would pay for the privilege. A prefix below the model's
            minimum cacheable length is not cached. Cache reads are reported as
            `cached_tokens` and cache writes as `cache_write_tokens` on usage;
            a write with no `cache_write_tokens` price bills at the input rate.
          properties:
            enabled:
              type: boolean
              description: Whether the breakpoint is marked. Defaults to false.
        max_context_messages:
          type: integer
          nullable: true
          description: Maximum number of recent messages to include in the context window sent to the model. When null, all messages are included.
        max_output_tokens:
          type: integer
          nullable: true
          minimum: 1
          description: >-
            Maximum number of tokens the model may generate per step of a turn.
            A response that reaches it stops with `stop_reason` `length`. Null
            leaves the model's default.
          example: 1024
        reasoning:
          type: object
          nullable: true
          additionalProperties: false
          required: [effort]
          description: >-
            How much the model reasons before answering. Translated per provider
            (Anthropic thinking, Gemini thinking config, OpenAI reasoning effort)
            against what the model supports; a model that cannot reason ignores
            it. `none` turns reasoning off where the model allows it. Reasoning
            tokens are metered as output. Null leaves the model's default.
          properties:
            effort:
              type: string
              enum: [none, low, medium, high]
              example: low
        single_session_per_actor:
          type: boolean
          description: When true, only one open session per actor_id is allowed for this agent. Creating a second open session for the same actor returns 409.
        trace_content_mode:
          type: string
          nullable: true
          # `null` is a real value here (inherit the project), so it belongs in
          # the enum — otherwise the generated SDK type omits it.
          enum: [full, none, null]
          description: >-
            Agent-scope zero-retention setting. `null` (the default) inherits
            the project's `trace_content_mode`; `none` means this agent's trace
            and generation content is never persisted. An agent may tighten a
            storing project to `none` but cannot loosen a `none` project back
            to `full`.
        on_approval_expiry:
          type: string
          nullable: true
          # `null` is a real value here (the terminating default), so it belongs
          # in the enum — otherwise the generated SDK type omits it.
          enum: [terminate, react, null]
          description: >-
            What happens when one of this agent's held tool calls expires
            un-approved. `null` (the default) and `terminate` end the chain
            there — the expired approval, its `approvals.expired` event and the
            auto-filed `approval_expired` exception are the whole record.
            `react` spawns a continuation that reports the staleness to the
            agent, for an agent that acts on it.
        version:
          type: integer
          description: >-
            Current config version. Starts at 1 and increments on every write
            that changes the config; each increment archives the new config as an
            `AgentVersion`. A write that changes nothing leaves it untouched.
          example: 3
        active_release:
          nullable: true
          allOf:
            - $ref: '#/components/schemas/AgentRelease'
          description: >-
            Staged rollout in progress, or null when all traffic serves this
            config.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    AgentRelease:
      type: object
      description: >-
        A staged rollout splitting traffic between two archived versions. See
        [Versioning and Staged Rollout](/docs/modules/agents#versioning-and-staged-rollout).
      required:
        - stable_version
        - canary_version
        - canary_percent
      properties:
        stable_version:
          type: integer
          minimum: 1
          description: Version served to traffic not assigned to the canary
          example: 3
        canary_version:
          type: integer
          minimum: 1
          description: Version under trial. Must differ from `stable_version`.
          example: 4
        canary_percent:
          type: integer
          minimum: 0
          maximum: 100
          description: Percentage of traffic assigned to `canary_version`
          example: 20
        promotion_gate:
          x-soat-ref: evals
          type: string
          nullable: true
          description: >-
            Eval that must have a passing run against `canary_version` before
            `promote` is allowed, or null for an ungated rollout. The gate
            constrains only how the rollout ends — traffic is split the same way
            either way. See
            [Eval-gated promotion](/docs/modules/agents#eval-gated-promotion).
          example: eval_V1StGXR8Z5jdHi6B

    AgentVersion:
      type: object
      description: >-
        An immutable archive of an agent's configuration at one version.
      properties:
        id:
          type: string
          description: Public ID of the archived version
          example: agver_V1StGXR8Z5jdHi6B
        agent_id:
          x-soat-ref: agents
          type: string
          description: Public ID of the agent this version belongs to
          example: agent_V1StGXR8Z5jdHi6B
        version:
          type: integer
          description: The archived version number
          example: 1
        config:
          type: object
          additionalProperties: true
          description: >-
            The agent's configuration as it stood at this version: every mutable
            field of the `Agent` schema (`instructions`, `model`,
            `tool_bindings`, `max_steps`, `tool_choice`, `stop_conditions`,
            `active_tool_ids`, `step_rules`, `boundary_policy`, `temperature`,
            `knowledge_config`, `output_schema`, `max_context_messages`,
            `max_output_tokens`, `reasoning`,
            `single_session_per_actor`, `on_approval_expiry`,
            `guardrail_ids`, `ai_provider_id`,
            `model_route_id`, `name`), and none of its identity or bookkeeping
            fields (`id`, `project_id`, `version`, `active_release`, timestamps).
            A literal credential in an inline `tool_bindings` definition is
            masked as on the `Agent` read, and a restore or release of such a
            version is refused with `400 LITERAL_CREDENTIAL`.


            Deliberately open rather than a fixed schema: an archive written by
            an earlier release of SOAT reflects the agent surface **of its own
            time**, so it may carry fields the current schema no longer defines,
            or lack ones it has since gained. Knowledge retrieval is not part of
            the snapshot — a version records which `knowledge_config` applied,
            while the documents and memory stores it resolves keep their own
            histories and are pinned at generation time.
        label:
          type: string
          nullable: true
          description: >-
            Optional human tag, set with `version_label` on the write that
            created this version. Restore, promote and abort set one
            automatically (e.g. `restored from v1`).
          example: pre-tone-change
        eval_run_id:
          x-soat-ref: eval-runs
          type: string
          nullable: true
          description: >-
            The eval run that cleared the release's `promotion_gate` when this
            version was promoted. Null for every version that did not go live
            through a gated promotion — which is most of them.
          example: evrun_V1StGXR8Z5jdHi6B
        created_by_principal_type:
          nullable: true
          allOf:
            - $ref: './principals.yaml#/components/schemas/PrincipalType'
          description: >-
            Who produced this version. Null for a write with no principal behind
            it. A formation apply is attributed to the credential that deployed
            it.
        created_by_principal_id:
          type: string
          nullable: true
          description: >-
            Public id of that principal: the user (`user_…`), or for API key
            auth the key itself (`key_…`), so the record names which key acted.
          example: user_V1StGXR8Z5jdHi6B
        created_at:
          type: string
          format: date-time

    RestoreAgentVersionRequest:
      type: object
      additionalProperties: false
      properties:
        label:
          type: string
          description: >-
            Tag for the version this restore creates. Defaults to
            `restored from v{version}`.
          example: rollback-incident-42

    SetAgentReleaseRequest:
      type: object
      required:
        - stable_version
        - canary_version
        - canary_percent
      additionalProperties: false
      properties:
        stable_version:
          type: integer
          minimum: 1
          description: An existing version to serve as the baseline
          example: 3
        canary_version:
          type: integer
          minimum: 1
          description: An existing version to trial. Must differ from `stable_version`.
          example: 4
        canary_percent:
          type: integer
          minimum: 0
          maximum: 100
          description: Percentage of traffic to assign to `canary_version`
          example: 20
        promotion_gate:
          x-soat-ref: evals
          type: string
          nullable: true
          description: >-
            Eval to gate promotion on. It must belong to this project and
            evaluate this agent; anything else is a `400`. Omit it, or send
            null, for a rollout that can be promoted at will.
          example: eval_V1StGXR8Z5jdHi6B

    ToolBinding:
      type: object
      additionalProperties: false
      description: >-
        One agent↔tool attachment. Exactly one of `tool_id` (persisted tool
        reference) or `tool` (inline ephemeral definition) per entry. Tool-call
        gating is owned by [Guardrails](/docs/modules/guardrails), attached via
        `guardrail_ids` on the project, agent, or tool — not on the binding.
      properties:
        tool_id:
          x-soat-ref: tools
          type: string
          description: Public ID of a persisted tool in the agent's own project (`400 TOOL_NOT_FOUND` otherwise). Exactly one of `tool_id`/`tool`.
          example: tool_V1StGXR8Z5jdHi6B
        tool:
          # Inline (ephemeral) tool definition — resolved fresh at generation
          # time, never persisted as a Tool resource. Exactly one of
          # `tool_id`/`tool`. Cannot be of type `pipeline`.
          $ref: './tools.yaml#/components/schemas/CreateToolRequest'

    ToolBindingRead:
      type: object
      description: >-
        One agent↔tool attachment as read back: `tool_id`, or the inline `tool`
        definition as written, with every literal credential masked.
      properties:
        tool_id:
          x-soat-ref: tools
          type: string
          description: Public ID of a persisted tool in the agent's own project.
          example: tool_V1StGXR8Z5jdHi6B
        tool:
          type: object
          additionalProperties: true
          description: >-
            Inline tool definition, in the `CreateToolRequest` shape. A literal
            credential is refused on write and a stored one is masked as
            `{"no_echo": true}`, exactly as on a persisted tool. A
            `{{secret:...}}` reference stays readable.

    CreateAgentRequest:
      type: object
      description: >-
        Exactly one of `ai_provider_id` or `model_route_id` must be set (400
        otherwise). `model` names the model on a pinned provider and cannot be
        combined with `model_route_id`, whose targets each name their own model.
      additionalProperties: false
      properties:
        project_id:
          x-soat-ref: projects
          type: string
          description: Public ID of the project
        ai_provider_id:
          x-soat-ref: ai-providers
          type: string
          description: >-
            Public ID of the AI provider to pin. Mutually exclusive with
            `model_route_id`.
        model_route_id:
          x-soat-ref: model-routes
          type: string
          description: >-
            Public ID of a model route in the same project. The agent's
            completion model is then resolved through the route's ordered
            targets with failover. Mutually exclusive with `ai_provider_id` and
            `model`.
        name:
          description: >-
            Display name.
          type: string
        instructions:
          description: >-
            System instructions, sent as the system message of every
            generation.
          type: string
        model:
          description: >-
            Model identifier on the pinned AI provider. Omitted, the provider's
            default model is used. Cannot be combined with `model_route_id`.
          type: string
        tool_bindings:
          type: array
          items:
            $ref: '#/components/schemas/ToolBinding'
          description: >-
            Tools to attach, one binding object per tool — the only attachment
            field. An entry is either a reference (`{ "tool_id": … }`) or an
            inline definition (`{ "tool": … }`). See
            [Tool Bindings](/docs/modules/agents#tool-bindings).
        max_steps:
          description: >-
            Maximum agent loop steps per turn (default `20`).
          type: integer
        tool_choice:
          description: 'Tool choice strategy. Accepts a string (`"auto"`, `"required"`) or an object (`{ "type": "tool", "tool_name": "my_tool" }`). A forcing value (`"required"` or the object form) forbids a final assistant message on every step of every turn, including a resumed or continued one, so it requires a `has_tool_call` entry in `stop_conditions` — otherwise the write is refused with `FORCED_TOOL_CHOICE_CANNOT_STOP`.'
        stop_conditions:
          type: array
          items:
            $ref: '#/components/schemas/AgentStopCondition'
          description: >-
            Conditions that end the agent's work early, on top of `max_steps`.
            Two scopes:


            `{"type": "has_tool_call", "tool_name": "<resolved tool name>"}` ends
            the **turn** after the step that calls the named tool. It narrows
            when the loop ends — it never lets it run past `max_steps`.


            `{"type": "max_chain_generations", "max_generations": <n>}` bounds the
            **continuation chain** instead: once the chain has spawned that many
            generations, further resumptions stop with `chain_limit` rather than
            extending it. It never shortens a turn. The effective ceiling is the
            smaller of this and the deployment's
            `MAX_CONTINUATION_CHAIN_GENERATIONS`, so an agent can be stricter
            than the platform but never looser.


            An unknown `type`, a `has_tool_call` without a `tool_name`, a
            `max_chain_generations` whose `max_generations` is not a positive
            integer, or a non-object entry is rejected with 400.
        active_tool_ids:
          description: >-
            Persisted tools from `tool_bindings` the model sees on every step.
            Omitted or `[]` leaves every bound tool active. See
            [Active Tools](/docs/modules/agents#active-tools).
          x-soat-ref: tools
          type: array
          items:
            type: string
        guardrail_ids:
          x-soat-ref: guardrails
          type: array
          nullable: true
          items:
            type: string
          description: Guardrails attached at the agent scope.
        step_rules:
          description: >-
            Per-step overrides of `tool_choice` and `active_tool_ids`, numbered
            from the first step of the turn. See
            [Step Rules](/docs/modules/agents#step-rules).
          type: array
          items:
            $ref: '#/components/schemas/AgentStepRule'
        boundary_policy:
          $ref: '#/components/schemas/AgentBoundaryPolicy'
        temperature:
          description: >-
            Sampling temperature passed to the model. Omitted, the provider's
            default applies.
          type: number
        knowledge_config:
          description: >-
            Knowledge search run before every generation; its matches are
            prepended as reference context. See
            [Knowledge Config](/docs/modules/agents#knowledge-config).
          type: object
          additionalProperties: false
          properties:
            memory_store_ids:
              x-soat-ref: memory-stores
              type: array
              items:
                type: string
            document_ids:
              x-soat-ref: documents
              type: array
              items:
                type: string
            document_paths:
              type: array
              items:
                type: string
            tags:
              description: >-
                Key-value pairs a result's own `tags` must all contain (exact
                match). Scopes documents and memories alike.
              allOf:
                - $ref: './tags.yaml#/components/schemas/TagBag'
            min_score:
              type: number
              description: >
                Minimum raw cosine similarity (0–1) a vector candidate must
                reach to be ranked, the floor the search endpoint spells
                `min_similarity`. No default: omitted means no floor at all,
                and every one of the `limit` nearest chunks is injected however
                weak it is.
            rrf_k:
              type: integer
              description: >
                The `k` in the fusion term `1 / (k + rank)`, the same knob
                knowledge search takes. Smaller weights the top of each ranking
                more heavily. Omitted, the deployment's `KNOWLEDGE_RRF_K`
                applies.
            recency_half_life_days:
              type: number
              description: >
                Half-life in days of the decay applied to **memory** results
                after fusion, the same knob knowledge search takes. `0`
                disables it. Omitted, the deployment's
                `KNOWLEDGE_RECENCY_HALF_LIFE_DAYS` applies.
            limit:
              type: integer
              description: >
                Maximum number of results to inject. Omitted, 10 are injected.
            write_memory_store_id:
              x-soat-ref: memory-stores
              type: string
              nullable: true
              description: Public ID of the memory store the agent can write to during generation. When set, a write_memory tool is automatically available to the agent.
        output_schema:
          type: object
          nullable: true
          description: 'JSON Schema describing the structured object the model must return. When set, non-streaming generations constrain output to this schema and the parsed value is returned as `output.object`. The schema is enforced on the way back, not just sent to the model: an object that violates it fails the generation with 502 `OUTPUT_SCHEMA_VALIDATION_FAILED`, naming the violated field. Constraints beyond `required`/`type` (`minLength`, `enum`, `pattern`, `minItems`) are honored and are what reject a structurally valid but degenerate answer. See the Structured Output section in the Agents module docs.'
        prompt_caching:
          type: object
          nullable: true
          description: >-
            Prompt caching for this agent's turns. `{"enabled": true}` marks two
            cache breakpoints: the end of the system block, which covers the tool
            definitions and the instructions, and the last message of each step,
            which covers the conversation so far. A provider that caches by
            explicit breakpoint then reads, on every later step and every later
            turn of the session, the whole prefix the previous one sent, and
            writes only what was appended. Null or omitted is off: a cache write
            costs more than an uncached token, so an agent whose prefix is never
            re-read would pay for the privilege. A prefix below the model's
            minimum cacheable length is not cached. Cache reads are reported as
            `cached_tokens` and cache writes as `cache_write_tokens` on usage;
            a write with no `cache_write_tokens` price bills at the input rate.
          additionalProperties: false
          properties:
            enabled:
              type: boolean
              description: Whether the breakpoint is marked. Defaults to false.
        max_context_messages:
          type: integer
          description: Maximum number of recent messages included in the context window. Null means no limit.
        max_output_tokens:
          type: integer
          nullable: true
          minimum: 1
          description: >-
            Maximum number of tokens the model may generate per step of a turn.
            A response that reaches it stops with `stop_reason` `length`. Null
            leaves the model's default.
          example: 1024
        reasoning:
          type: object
          nullable: true
          additionalProperties: false
          required: [effort]
          description: >-
            How much the model reasons before answering. Translated per provider
            (Anthropic thinking, Gemini thinking config, OpenAI reasoning effort)
            against what the model supports; a model that cannot reason ignores
            it. `none` turns reasoning off where the model allows it. Reasoning
            tokens are metered as output. Null leaves the model's default.
          properties:
            effort:
              type: string
              enum: [none, low, medium, high]
              example: low
        single_session_per_actor:
          type: boolean
          description: When true, only one open session per actor_id is allowed for this agent.
        trace_content_mode:
          type: string
          nullable: true
          # `null` is a real value here (inherit the project), so it belongs in
          # the enum — otherwise the generated SDK type omits it.
          enum: [full, none, null]
          description: >-
            Zero-retention opt-in for this agent. `null` inherits the project's
            setting; `none` means trace and generation content is never
            written. Setting `full` under a project whose own mode is `none` is
            refused with 400 — the project is a floor an agent may only tighten.
        on_approval_expiry:
          type: string
          nullable: true
          # `null` is a real value here (the terminating default), so it belongs
          # in the enum — otherwise the generated SDK type omits it.
          enum: [terminate, react, null]
          description: >-
            What happens when one of this agent's held tool calls expires
            un-approved. `null` (the default) and `terminate` end the chain
            there — the expired approval, its `approvals.expired` event and the
            auto-filed `approval_expired` exception are the whole record.
            `react` spawns a continuation that reports the staleness to the
            agent, for an agent that acts on it.
        version_label:
          type: string
          nullable: true
          description: >-
            Optional tag for the config version this write archives (e.g.
            `initial`). Annotates the version only — it is not stored on the
            agent and is not part of the config, so labelling a change is never
            itself a change.
          example: initial

    UpdateAgentRequest:
      type: object
      description: >-
        The post-update state must still set exactly one of `ai_provider_id` or
        `model_route_id`. To switch a pinned agent to a route, send
        `model_route_id` together with `ai_provider_id: null` in the same
        request (and vice versa).
      additionalProperties: false
      properties:
        ai_provider_id:
          description: >-
            Public ID of the AI provider to pin. Mutually exclusive with
            `model_route_id`; set to `null` when switching to a route.
          x-soat-ref: ai-providers
          type: string
          nullable: true
        model_route_id:
          x-soat-ref: model-routes
          type: string
          nullable: true
          description: >-
            Model route in the same project. Mutually exclusive with
            `ai_provider_id` and `model`; set to null to clear.
        name:
          description: >-
            Display name; `null` clears it.
          type: string
          nullable: true
        instructions:
          description: >-
            System instructions, sent as the system message of every
            generation; `null` clears them.
          type: string
          nullable: true
        model:
          description: >-
            Model identifier on the pinned AI provider; `null` falls back to the
            provider's default model. Cannot be combined with `model_route_id`.
          type: string
          nullable: true
        tool_bindings:
          type: array
          nullable: true
          items:
            $ref: '#/components/schemas/ToolBinding'
          description: >-
            Tools attached to the agent — the only attachment field. Replaces
            the whole binding list; set to `null` to clear. See
            [Tool Bindings](/docs/modules/agents#tool-bindings).
        max_steps:
          description: >-
            Maximum agent loop steps per turn; `null` restores the default of
            `20`.
          type: integer
          nullable: true
        tool_choice:
          description: 'Tool choice strategy. Accepts a string (`"auto"`, `"required"`) or an object (`{ "type": "tool", "tool_name": "my_tool" }`). A forcing value (`"required"` or the object form) forbids a final assistant message on every step of every turn, including a resumed or continued one, so it requires a `has_tool_call` entry in `stop_conditions` — otherwise the write is refused with `FORCED_TOOL_CHOICE_CANNOT_STOP`.'
        stop_conditions:
          type: array
          nullable: true
          items:
            $ref: '#/components/schemas/AgentStopCondition'
          description: >-
            Conditions that end the agent's work early, on top of `max_steps`.
            Two scopes:


            `{"type": "has_tool_call", "tool_name": "<resolved tool name>"}` ends
            the **turn** after the step that calls the named tool. It narrows
            when the loop ends — it never lets it run past `max_steps`.


            `{"type": "max_chain_generations", "max_generations": <n>}` bounds the
            **continuation chain** instead: once the chain has spawned that many
            generations, further resumptions stop with `chain_limit` rather than
            extending it. It never shortens a turn. The effective ceiling is the
            smaller of this and the deployment's
            `MAX_CONTINUATION_CHAIN_GENERATIONS`, so an agent can be stricter
            than the platform but never looser.


            An unknown `type`, a `has_tool_call` without a `tool_name`, a
            `max_chain_generations` whose `max_generations` is not a positive
            integer, or a non-object entry is rejected with 400.
        active_tool_ids:
          description: >-
            Persisted tools from `tool_bindings` the model sees on every step.
            `null` or `[]` leaves every bound tool active. See
            [Active Tools](/docs/modules/agents#active-tools).
          x-soat-ref: tools
          type: array
          nullable: true
          items:
            type: string
        guardrail_ids:
          x-soat-ref: guardrails
          type: array
          nullable: true
          items:
            type: string
          description: Guardrails attached at the agent scope.
        step_rules:
          description: >-
            Per-step overrides of `tool_choice` and `active_tool_ids`, numbered
            from the first step of the turn; `null` clears them. See
            [Step Rules](/docs/modules/agents#step-rules).
          type: array
          nullable: true
          items:
            $ref: '#/components/schemas/AgentStepRule'
        boundary_policy:
          $ref: '#/components/schemas/AgentBoundaryPolicy'
        temperature:
          description: >-
            Sampling temperature passed to the model; `null` restores the
            provider's default.
          type: number
          nullable: true
        knowledge_config:
          description: >-
            Knowledge search run before every generation; its matches are
            prepended as reference context. `null` turns it off. See
            [Knowledge Config](/docs/modules/agents#knowledge-config).
          type: object
          nullable: true
          additionalProperties: false
          properties:
            memory_store_ids:
              x-soat-ref: memory-stores
              type: array
              items:
                type: string
            document_ids:
              x-soat-ref: documents
              type: array
              items:
                type: string
            document_paths:
              type: array
              items:
                type: string
            tags:
              description: >-
                Key-value pairs a result's own `tags` must all contain (exact
                match). Scopes documents and memories alike.
              allOf:
                - $ref: './tags.yaml#/components/schemas/TagBag'
            min_score:
              type: number
              description: >
                Minimum raw cosine similarity (0–1) a vector candidate must
                reach to be ranked, the floor the search endpoint spells
                `min_similarity`. No default: omitted means no floor at all,
                and every one of the `limit` nearest chunks is injected however
                weak it is.
            rrf_k:
              type: integer
              description: >
                The `k` in the fusion term `1 / (k + rank)`, the same knob
                knowledge search takes. Smaller weights the top of each ranking
                more heavily. Omitted, the deployment's `KNOWLEDGE_RRF_K`
                applies.
            recency_half_life_days:
              type: number
              description: >
                Half-life in days of the decay applied to **memory** results
                after fusion, the same knob knowledge search takes. `0`
                disables it. Omitted, the deployment's
                `KNOWLEDGE_RECENCY_HALF_LIFE_DAYS` applies.
            limit:
              type: integer
              description: >
                Maximum number of results to inject. Omitted, 10 are injected.
            write_memory_store_id:
              x-soat-ref: memory-stores
              type: string
              nullable: true
              description: Public ID of the memory store the agent can write to during generation. When set, a write_memory tool is automatically available to the agent.
        output_schema:
          type: object
          nullable: true
          description: 'JSON Schema describing the structured object the model must return. When set, non-streaming generations constrain output to this schema and the parsed value is returned as `output.object`. The schema is enforced on the way back, not just sent to the model: an object that violates it fails the generation with 502 `OUTPUT_SCHEMA_VALIDATION_FAILED`, naming the violated field. Constraints beyond `required`/`type` (`minLength`, `enum`, `pattern`, `minItems`) are honored and are what reject a structurally valid but degenerate answer. See the Structured Output section in the Agents module docs.'
        prompt_caching:
          type: object
          nullable: true
          description: >-
            Prompt caching for this agent's turns. `{"enabled": true}` marks two
            cache breakpoints: the end of the system block, which covers the tool
            definitions and the instructions, and the last message of each step,
            which covers the conversation so far. A provider that caches by
            explicit breakpoint then reads, on every later step and every later
            turn of the session, the whole prefix the previous one sent, and
            writes only what was appended. Null or omitted is off: a cache write
            costs more than an uncached token, so an agent whose prefix is never
            re-read would pay for the privilege. A prefix below the model's
            minimum cacheable length is not cached. Cache reads are reported as
            `cached_tokens` and cache writes as `cache_write_tokens` on usage;
            a write with no `cache_write_tokens` price bills at the input rate.
          additionalProperties: false
          properties:
            enabled:
              type: boolean
              description: Whether the breakpoint is marked. Defaults to false.
        max_context_messages:
          type: integer
          nullable: true
          description: Maximum number of recent messages included in the context window. Null means no limit.
        max_output_tokens:
          type: integer
          nullable: true
          minimum: 1
          description: >-
            Maximum number of tokens the model may generate per step of a turn.
            A response that reaches it stops with `stop_reason` `length`. Null
            leaves the model's default.
          example: 1024
        reasoning:
          type: object
          nullable: true
          additionalProperties: false
          required: [effort]
          description: >-
            How much the model reasons before answering. Translated per provider
            (Anthropic thinking, Gemini thinking config, OpenAI reasoning effort)
            against what the model supports; a model that cannot reason ignores
            it. `none` turns reasoning off where the model allows it. Reasoning
            tokens are metered as output. Null leaves the model's default.
          properties:
            effort:
              type: string
              enum: [none, low, medium, high]
              example: low
        single_session_per_actor:
          type: boolean
          nullable: true
          description: When true, only one open session per actor_id is allowed for this agent.
        trace_content_mode:
          type: string
          nullable: true
          # `null` is a real value here (inherit the project), so it belongs in
          # the enum — otherwise the generated SDK type omits it.
          enum: [full, none, null]
          description: >-
            Zero-retention opt-in for this agent. `null` inherits the project's
            setting; `none` means trace and generation content is never
            written. Setting `full` under a project whose own mode is `none` is
            refused with 400.
        on_approval_expiry:
          type: string
          nullable: true
          # `null` is a real value here (the terminating default), so it belongs
          # in the enum — otherwise the generated SDK type omits it.
          enum: [terminate, react, null]
          description: >-
            What happens when one of this agent's held tool calls expires
            un-approved. `null` (the default) and `terminate` end the chain
            there — the expired approval, its `approvals.expired` event and the
            auto-filed `approval_expired` exception are the whole record.
            `react` spawns a continuation that reports the staleness to the
            agent, for an agent that acts on it.
        version_label:
          type: string
          nullable: true
          description: >-
            Optional tag for the config version this write archives (e.g.
            `pre-tone-change`). Annotates the version only — it is not stored on
            the agent and is not part of the config, so labelling a change is
            never itself a change. Ignored when the write changes nothing, since
            no version is created.
          example: pre-tone-change

        expected_version:
          description: >-
            Refuses the write unless the resource is at this version.
          allOf:
            - $ref: './concurrency.yaml#/components/schemas/ExpectedVersion'
    CreateAgentGenerationRequest:
      type: object
      required:
        - messages
      additionalProperties: false
      properties:
        messages:
          description: >-
            Conversation turns, oldest first. Roles are `user` and `assistant`:
            the system prompt is the agent's `instructions`, so a `system` entry is
            refused with `400 SYSTEM_MESSAGE_NOT_ALLOWED`.
          type: array
          minItems: 1
          items:
            type: object
            additionalProperties: false
            required:
              - role
              - content
            properties:
              role:
                type: string
                # No `system`: an agent's system prompt is its `instructions`
                # field; a system entry here is refused with
                # 400 SYSTEM_MESSAGE_NOT_ALLOWED.
                enum:
                  - user
                  - assistant
              content:
                oneOf:
                  - type: string
                  - $ref: '#/components/schemas/ToolOutputMessageContent'
                  - $ref: '#/components/schemas/DocumentMessageContent'
        stream:
          type: boolean
          default: false
          # A tool call is one request and one result; there is no channel to
          # stream deltas over, and the route answers a streamed request by
          # writing SSE frames straight to the socket instead of setting a body
          # — which in-process would run the generation and then have nowhere to
          # put it. Hiding the field means a caller cannot ask for a mode that
          # cannot be delivered; the same action without it returns the
          # completed generation.
          x-soat-tool-unsupported: true
          description: When true the response is an SSE stream
        trace_id:
          x-soat-ref: traces
          type: string
          x-soat-server-managed: true
          description: >-
            Optional trace ID to group generations. Each generation appends its
            own steps to the trace's steps object, and `step_count` covers them
            all.
        parent_trace_id:
          x-soat-ref: traces
          type: string
          nullable: true
          x-soat-server-managed: true
          description: The trace ID of the parent agent generation that triggered this one (for agent-to-agent calls)
        root_trace_id:
          x-soat-ref: traces
          type: string
          nullable: true
          x-soat-server-managed: true
          description: The trace ID of the root generation in the call chain; if omitted, this generation is the root
        max_call_depth:
          type: integer
          minimum: 0
          default: 10
          x-soat-server-managed: true
          description: Maximum nested agent-call depth; 0 short-circuits with a depth-guard response
        tool_context:
          type: object
          additionalProperties:
            type: string
          nullable: true
          description: >-
            Key-value pairs forwarded as `X-Soat-Context-<key>` headers on every
            `http`, `mcp` and `builtin` tool call in this generation. The header name is
            the deployment's configured context prefix (`X-Soat-Context-` by
            default) plus the key verbatim — no character is re-cased.
            Keys are never case-converted — they round-trip exactly as sent.
            An invalid or colliding key is rejected with
            `400 INVALID_TOOL_CONTEXT_KEY`.
        action_id:
          type: string
          description: Logical action label recorded on the generation's usage meter, so spend can be rolled up per action (e.g. an A/B/C/D operating action).
        guardrail_context:
          type: object
          additionalProperties: true
          nullable: true
          description: >-
            Caller-supplied guardrail context (the `context.*` namespace guard
            and class expressions read at tool-dispatch time). Free-form and
            never interpreted by the platform; a guardrail may combine it with a
            `context_tool` per its `context_mode`. See the guardrails module.
        metadata:
          description: >-
            Caller-supplied key/value metadata attached to the generation
            record for per-run audit attribution (e.g. the ticket or case this
            action belongs to). Round-trips verbatim when the generation is
            fetched via the generations API. The bag is caller-owned and no key
            is reserved: server-owned state (usage attribution, the served agent
            version, the model route's record, the extraction summary, what
            knowledge retrieval served) lives in its own top-level generation
            fields and cannot be written from here. Use the request's own `action_id` field
            to set the usage-attribution label.
          allOf:
            - $ref: './metadata.yaml#/components/schemas/NullableMetadataBag'
        idempotency_key:
          type: string
          maxLength: 255
          description: >-
            Deduplication key, unique within the project, that makes a retry of
            an at-least-once delivery or an ambiguous failure safe. The first
            request under a key runs the generation; any later request carrying
            it runs nothing and answers `202` with the generation the key
            names, whatever state it has reached — including a retry that
            arrives while the original is still running. The key is claimed by
            the generation record for as long as the record exists.


            Every other body field except `stream` is the request the key
            names: reusing a key with any of them changed, or on another agent
            of the project, is `409 IDEMPOTENCY_KEY_REUSED`. `stream` and
            `wait` say how the caller receives the generation, not what it is,
            so a retry may flip them.
          example: discord-1287654321098765432
        knowledge_config:
          type: object
          nullable: true
          description: Per-generation knowledge retrieval override. Array filters (memory_store_ids, document_ids, document_paths) are unioned with the agent's stored knowledge_config; `tags` pairs are merged with the override winning per key; scalar fields (min_score, rrf_k, recency_half_life_days, limit) use the per-generation value when present.
          additionalProperties: false
          properties:
            memory_store_ids:
              x-soat-ref: memory-stores
              type: array
              items:
                type: string
            document_ids:
              x-soat-ref: documents
              type: array
              items:
                type: string
            document_paths:
              type: array
              items:
                type: string
            tags:
              description: >-
                Key-value pairs a result's own `tags` must all contain (exact
                match). Scopes documents and memories alike.
              allOf:
                - $ref: './tags.yaml#/components/schemas/TagBag'
            min_score:
              type: number
              description: >
                Minimum raw cosine similarity (0–1) a vector candidate must
                reach to be ranked, the floor the search endpoint spells
                `min_similarity`. No default: omitted means no floor at all,
                and every one of the `limit` nearest chunks is injected however
                weak it is.
            rrf_k:
              type: integer
              description: >
                The `k` in the fusion term `1 / (k + rank)`, the same knob
                knowledge search takes. Smaller weights the top of each ranking
                more heavily. Omitted, the deployment's `KNOWLEDGE_RRF_K`
                applies.
            recency_half_life_days:
              type: number
              description: >
                Half-life in days of the decay applied to **memory** results
                after fusion, the same knob knowledge search takes. `0`
                disables it. Omitted, the deployment's
                `KNOWLEDGE_RECENCY_HALF_LIFE_DAYS` applies.
            limit:
              type: integer
              description: >
                Maximum number of results to inject. Omitted, 10 are injected.

    ToolOutputMessageContent:
      type: object
      required:
        - type
        - tool_id
      additionalProperties: false
      properties:
        type:
          type: string
          enum:
            - tool_output
        tool_id:
          x-soat-ref: tools
          type: string
          description: Public ID of the tool to execute before generation.
        action:
          type: string
          nullable: true
          description: Optional action name for tools that require action selection (for example builtin and mcp tools).
        input:
          type: object
          nullable: true
          additionalProperties: true
          description: Input payload passed to the tool call.
        output_path:
          type: string
          nullable: true
          description: Optional dot-notation path used to extract a value from the tool output.

    DocumentMessageContent:
      type: object
      required:
        - type
        - document_id
      additionalProperties: false
      properties:
        type:
          type: string
          enum:
            - document
        document_id:
          x-soat-ref: documents
          type: string
          description: Public ID of a document to use as the message content.

    SubmitToolOutputsRequest:
      type: object
      required:
        - tool_outputs
      additionalProperties: false
      properties:
        tool_outputs:
          description: >-
            Results for the tool calls the paused generation is waiting on, one
            entry per `tool_call_id`.
          type: array
          minItems: 1
          items:
            type: object
            required:
              - tool_call_id
              - output
            additionalProperties: false
            properties:
              tool_call_id:
                type: string
                description: ID of the tool call to respond to
              output:
                description: Result of the tool execution

    AcceptedGenerationResponse:
      type: object
      description: >
        Handle for a generation running in the background. The generation
        record already exists when this is returned, so the id is immediately
        pollable via `GET /api/v1/generations/{generation_id}`.
      required:
        - status
        - generation_id
        - trace_id
      properties:
        status:
          type: string
          enum: [accepted]
          example: accepted
        generation_id:
          type: string
          example: 'gen_V1StGXR8Z5jdHi6B'
        trace_id:
          type: string
          example: 'trace_V1StGXR8Z5jdHi6B'

    AgentGenerationResponse:
      type: object
      description: >
        Result of an agent generation. Mirrors the server's `GenerationResult`.
        When `status` is `completed` the model output is under `output`; when it
        is `requires_action` the pending client tool calls are under
        `required_action`.
      required:
        - id
        - trace_id
        - status
      properties:
        id:
          type: string
          description: Public ID of the generation
          example: gen_V1StGXR8Z5jdHi6B
        trace_id:
          type: string
          description: Public ID of the trace for this generation
          example: trace_V1StGXR8Z5jdHi6B
        status:
          type: string
          enum:
            - completed
            - requires_action
          description: Generation status
        ai_provider_id:
          type: string
          x-soat-ref: ai-providers
          nullable: true
          description: >
            Public ID of the AI provider that served `output.model` — the target
            a model route picked, or the agent's pinned provider. A model string
            alone does not identify its provider: two providers in one project
            can serve byte-identical model names, so this is what makes the
            value safe to map back to a name a gateway in front of this runtime
            publishes. Null when the generation resolved no serving provider.
          example: aip_V1StGXR8Z5jdHi6B
        output:
          type: object
          nullable: true
          description: Model output (present when `status` is `completed`).
          required:
            - model
            - content
            - finish_reason
          properties:
            model:
              type: string
              description: Model that produced the output
            content:
              type: string
              description: Final text output
            finish_reason:
              type: string
              description: Reason the model stopped generating
            response_messages:
              type: array
              nullable: true
              description: Full AI SDK response messages (tool calls, tool results, final text)
              items:
                type: object
            object:
              type: object
              nullable: true
              description: Structured object matching the agent's `output_schema` (when `output_schema` is set)
        required_action:
          type: object
          nullable: true
          description: Pending action the caller must satisfy (present when `status` is `requires_action`).
          required:
            - type
            - tool_calls
          properties:
            type:
              type: string
              enum:
                - submit_tool_outputs
              description: The kind of action required
            tool_calls:
              type: array
              description: Pending tool calls to execute and submit outputs for
              items:
                type: object
                properties:
                  id:
                    type: string
                    description: Tool call ID
                  tool_name:
                    type: string
                    description: Name of the tool to invoke
                  args:
                    type: object
                    description: Arguments for the tool call
    AgentStopCondition:
      type: object
      additionalProperties: false
      required:
        - type
      description: >-
        One stop condition. `has_tool_call` ends the turn after the step that
        calls `tool_name`; `max_chain_generations` bounds the continuation
        chain at `max_generations`.
      properties:
        type:
          type: string
          enum:
            - has_tool_call
            - max_chain_generations
          description: Which scope the condition ends.
        tool_name:
          type: string
          nullable: true
          description: >-
            The resolved tool name a `has_tool_call` condition matches.
            Required for that type.
        max_generations:
          type: integer
          nullable: true
          description: >-
            Generations the continuation chain may reach before further
            resumptions stop with `chain_limit`. Required for
            `max_chain_generations`, and must be a positive integer.
    AgentStepRule:
      type: object
      additionalProperties: false
      required:
        - step
      description: >-
        One per-step override. Steps not named by a rule use the agent's own
        `tool_choice` and `active_tool_ids`.
      properties:
        step:
          type: integer
          description: >-
            The 1-indexed step of the turn this rule applies to. The numbering
            spans a `requires_action` pause.
        tool_choice:
          description: >-
            Tool choice for this step — `auto`, `required`, `null`, or
            `{ "type": "tool", "tool_name": "search" }`. Stored verbatim.
        active_tool_ids:
          x-soat-ref: tools
          type: array
          nullable: true
          items:
            type: string
          description: Tool IDs active on this step.
    AgentBoundaryPolicy:
      type: object
      nullable: true
      additionalProperties: false
      required:
        - statement
      description: >-
        Restricts which SOAT actions the agent may invoke. Evaluated as the
        intersection with the caller's own policy, so it can only narrow.
      properties:
        statement:
          type: array
          items:
            $ref: '#/components/schemas/AgentBoundaryPolicyStatement'
          description: IAM policy statements, in the policy document grammar.
    AgentBoundaryPolicyStatement:
      type: object
      additionalProperties: false
      required:
        - effect
        - action
      properties:
        effect:
          type: string
          enum:
            - Allow
            - Deny
          example: Allow
        action:
          type: array
          items:
            type: string
          example:
            - memories:*
            - agents:DeleteAgent
        resource:
          type: array
          nullable: true
          items:
            type: string
          description: Resource SRN patterns. Omit to match every resource.
          example:
            - srn:proj_abc:memories:*
        condition:
          type: object
          nullable: true
          additionalProperties: true
          description: >-
            Condition block, in the same grammar a policy document uses: keys
            are condition operators mapping to context-key/value maps.
          example:
            StringEquals:
              soat:ResourceTag/env: prod
