openapi: 3.0.3
info:
  title: SOAT Formations API
  version: 1.0.0
  description: >
    API for managing Formations — a CloudFormation-inspired declarative
    deployment layer that lets you describe an entire AI agent stack in a
    single template and deploy it with one API call.
  contact:
    name: SOAT Team
    url: https://github.com/ttoss/soat
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: Formations
    description: Manage declarative formation stacks
security:
  - bearerAuth: []
paths:
  /api/v1/formations/validate:
    post:
      tags:
        - Formations
      summary: Validate a formation template
      description: >
        Validates a formation template without creating any resources.
        Returns a list of errors and warnings. Accepts the template as a
        JSON object or as a YAML/JSON string.
      operationId: validateFormation
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                template:
                  $ref: '#/components/schemas/FormationTemplateInput'
                parameters:
                  type: object
                  additionalProperties:
                    type: string
                  description: >
                    Runtime parameter values that override or supply template
                    parameter defaults. Keys must match parameter names declared
                    in `template.parameters`. When provided, the validation
                    result also reports required parameters that are still
                    missing after applying these values.
                  nullable: true
      responses:
        '200':
          description: Validation result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationResult'
        '401':
          description: Unauthorized

  /api/v1/formations/plan:
    post:
      tags:
        - Formations
      summary: Plan a formation deployment
      description: >
        Computes a diff between the desired template and the current stack state
        without making any changes. Returns the list of planned actions.
      operationId: planFormation
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - template
              additionalProperties: false
              properties:
                project_id:
                  x-soat-ref: projects
                  type: string
                  description: Project ID. Optional when authenticating with a project-scoped API key, which defaults to the key's project; required otherwise.
                  example: proj_V1StGXR8Z5jdHi6B
                formation_id:
                  x-soat-ref: formations
                  type: string
                  description: >-
                    Existing formation ID to compare against. Omit for new
                    formation planning.
                template:
                  $ref: '#/components/schemas/FormationTemplateInput'
                parameters:
                  type: object
                  additionalProperties:
                    type: string
                  description: >
                    Runtime parameter values that override or supply template
                    parameter defaults. Keys must match parameter names declared
                    in `template.parameters`. A parameter declared with
                    `use_previous_value: true` may be omitted to reuse its
                    stored value.
                  nullable: true
      responses:
        '200':
          description: Plan result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlanResult'
        '400':
          description: Bad Request
        '401':
          description: Unauthorized
        '403':
          description: Forbidden

  /api/v1/formations:
    get:
      tags:
        - Formations
      summary: List formations
      description: Returns all formation stacks for a project
      operationId: listFormations
      parameters:
        - name: project_id
          in: query
          description: Project ID (required if not using project key auth)
          schema:
            type: string
            example: proj_V1StGXR8Z5jdHi6B
        - $ref: './pagination.yaml#/components/parameters/Limit'
        - $ref: './pagination.yaml#/components/parameters/Offset'
      responses:
        '200':
          description: List of formations
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - total
                  - limit
                  - offset
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Formation'
                  total:
                    type: integer
                  limit:
                    type: integer
                  offset:
                    type: integer
        '401':
          description: Unauthorized
        '403':
          description: Forbidden

    post:
      tags:
        - Formations
      summary: Create a new formation
      description: >
        Validates the template, creates the formation record, then provisions
        all declared resources in dependency order.


        A **template-shape** error is refused with `400`. A **deploy** failure
        is not: the operation ran, so the formation is returned with `201` and
        `status: "failed"`, and `error` explains why (the resources created
        before the failure are rolled back). Read `status` — a `2xx` here means
        the deploy was attempted, not that it worked. The `builtin` CLI exits
        non-zero on that body so `create-formation && …` does not lie.
      operationId: createFormation
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
                - template
              additionalProperties: false
              properties:
                project_id:
                  x-soat-ref: projects
                  type: string
                  description: Project ID. Optional when authenticating with a project-scoped API key, which defaults to the key's project; required otherwise.
                  example: proj_V1StGXR8Z5jdHi6B
                name:
                  type: string
                  description: Human-readable name for the formation stack
                  example: my-agent-stack
                template:
                  $ref: '#/components/schemas/FormationTemplateInput'
                parameters:
                  type: object
                  additionalProperties:
                    type: string
                  description: >
                    Runtime parameter values that override or supply template
                    parameter defaults. Keys must match parameter names declared
                    in `template.parameters`. Required parameters (those without
                    a default) must be provided here.
                  nullable: true
                metadata:
                  description: >
                    Static annotations stored on the formation record. This
                    field is NOT a substitution site: `sub`/`param`/`ref`
                    expressions are rejected with 400
                    (`FORMATION_INVALID_METADATA`). For deploy-time
                    substitution use the template's top-level `metadata` block,
                    which is resolved into `resolved_metadata`.
                  allOf:
                    - $ref: './metadata.yaml#/components/schemas/NullableMetadataBag'
      responses:
        '201':
          description: Formation created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Formation'
        '400':
          description: Bad Request
        '401':
          description: Unauthorized
        '403':
          description: >-
            Forbidden — either the caller may not operate on formations in the
            project, or it lacks an action a resource this template declares
            requires. `error.meta.denied_actions` names every missing action;
            nothing is applied.
        '409':
          description: Formation with this name already exists

  /api/v1/formations/{formation_id}:
    get:
      tags:
        - Formations
      summary: Get a specific formation
      description: Returns the formation stack including its current resources.
      operationId: getFormation
      parameters:
        - name: formation_id
          in: path
          required: true
          schema:
            type: string
          example: form_V1StGXR8Z5jdHi6B
      responses:
        '200':
          description: Formation details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Formation'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: Not Found

    put:
      tags:
        - Formations
      summary: Update an formation
      description: >
        Applies a new template to the formation. Resources are created,
        updated, or deleted to reconcile the current state with the desired
        state.


        A **template-shape** error is refused with `400`. A **deploy** failure
        is not: the operation ran, so the formation is returned with `200` and
        `status: "failed"`, and `error` explains why. Read `status` — a `2xx`
        here means the deploy was attempted, not that it worked. The `builtin` CLI
        exits non-zero on that body so `update-formation && …` does not lie.


        A deploy that replaced a resource and could not delete the superseded
        one answers `status: "active"` with
        `error.code: "FORMATION_REPLACE_CLEANUP_FAILED"` — the desired state is
        realised, and `error.meta.failures` names every resource still live. The
        next deploy retries the disposal.
      operationId: updateFormation
      parameters:
        - name: formation_id
          in: path
          required: true
          schema:
            type: string
          example: form_V1StGXR8Z5jdHi6B
      requestBody:
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                template:
                  $ref: '#/components/schemas/FormationTemplateInput'
                parameters:
                  type: object
                  additionalProperties:
                    type: string
                  description: >
                    Runtime parameter values that override or supply template
                    parameter defaults. Keys must match parameter names declared
                    in `template.parameters`. Required parameters (those without
                    a default) must be provided here, unless the parameter is
                    declared with `use_previous_value: true`, in which case
                    omitting it reuses the previously stored value.
                  nullable: true
                metadata:
                  description: >
                    Static annotations stored on the formation record. This
                    field is NOT a substitution site: `sub`/`param`/`ref`
                    expressions are rejected with 400
                    (`FORMATION_INVALID_METADATA`). For deploy-time
                    substitution use the template's top-level `metadata` block,
                    which is resolved into `resolved_metadata`.
                  allOf:
                    - $ref: './metadata.yaml#/components/schemas/NullableMetadataBag'
      responses:
        '200':
          description: Updated formation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Formation'
        '400':
          description: Bad Request
        '401':
          description: Unauthorized
        '403':
          description: >-
            Forbidden — either the caller may not operate on formations in the
            project, or it lacks an action a resource this template declares
            requires. `error.meta.denied_actions` names every missing action;
            nothing is applied.
        '404':
          description: Not Found

    delete:
      tags:
        - Formations
      summary: Delete an formation
      description: >
        Deletes the formation stack and all its managed resources in reverse
        dependency order.


        A resource the platform refuses to delete on its own — most often an
        agent that has generation or trace history — fails the teardown with
        `409 FORMATION_DELETE_FAILED`, naming every blocking resource in
        `error.meta.failures`. Resolve the blockers (for an agent, `DELETE
        /api/v1/agents/{agent_id}?force=true` also removes its generations and
        traces, and `deletion_policy: retain` exempts it from teardown entirely)
        and delete the formation again.


        A refusal the platform can foresee is found by a pre-flight, before the
        first delete: nothing is removed, and the formation stays `active` and
        intact for the retry. An unforeseeable error surfaces mid-teardown
        instead, where resources deleted before the blocker stay deleted and the
        formation is left in `delete_failed`. The error message states which
        happened.
      operationId: deleteFormation
      parameters:
        - name: formation_id
          in: path
          required: true
          schema:
            type: string
          example: form_V1StGXR8Z5jdHi6B
      responses:
        '200':
          description: Deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                required:
                  - success
        '401':
          description: Unauthorized
        '403':
          description: >-
            Forbidden — either the caller may not operate on formations in the
            project, or it lacks an action a resource this template declares
            requires. `error.meta.denied_actions` names every missing action;
            nothing is applied.
        '404':
          description: Not Found
        '409':
          description: >
            One or more resources could not be deleted
            (`FORMATION_DELETE_FAILED`). `error.meta.failures` lists each one as
            `{ logical_id, resource_type, error }`. The `message` says whether
            the pre-flight caught it (nothing deleted, formation still `active`)
            or it surfaced mid-teardown (formation left in `delete_failed`).

  /api/v1/formations/{formation_id}/events:
    get:
      tags:
        - Formations
      summary: List formation operation events
      description: >
        Returns all operations (create, update, delete) with their event logs
        for the formation, ordered chronologically.
      operationId: listFormationEvents
      parameters:
        - name: formation_id
          in: path
          required: true
          schema:
            type: string
          example: form_V1StGXR8Z5jdHi6B
        - $ref: './pagination.yaml#/components/parameters/Limit'
        - $ref: './pagination.yaml#/components/parameters/Offset'
      responses:
        '200':
          description: List of operations
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - total
                  - limit
                  - offset
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/FormationOperation'
                  total:
                    type: integer
                  limit:
                    type: integer
                  offset:
                    type: integer
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: Not Found

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

  schemas:
    FormationTemplateInput:
      description: >
        A formation template supplied as either a JSON object or a YAML/JSON
        string. When a string is provided the server parses it with a YAML
        parser (JSON is valid YAML) before processing.
      oneOf:
        - $ref: '#/components/schemas/FormationTemplate'
        - type: string
          description: YAML or JSON string representation of a FormationTemplate

    FormationTemplate:
      type: object
      required:
        - resources
      properties:
        parameters:
          type: object
          additionalProperties:
            $ref: '#/components/schemas/ParameterDeclaration'
          description: >
            Declared parameters for this template. Each parameter may have a
            default value and an optional description. Parameters without a
            default must be supplied in the `parameters` field of the deploy
            request.
          nullable: true
        resources:
          type: object
          additionalProperties:
            $ref: '#/components/schemas/ResourceDeclaration'
          description: Map of logical resource IDs to resource declarations
        outputs:
          type: object
          additionalProperties: true
          description: >
            Map of output names to values. Values may use `{ "ref": "logicalId" }`
            to reference physical IDs of created resources, or `{ "param": "ParamName" }`
            and `{ "sub": "text ${ParamName}" }` to embed parameter values.
            `{ "ref_attr": "LogicalId.attribute" }` resolves a named attribute of
            a created resource, except one carrying credential material — a
            trigger's or webhook's `secret` is refused with 400
            VALIDATION_FAILED, since a formation is readable by anyone holding
            `formations:GetFormation`. Read those from the resource's own secret
            route instead.
          nullable: true
        metadata:
          description: >
            Arbitrary metadata attached to the template. Supports the same
            substitution as `outputs`: `{ "ref": "logicalId" }` resolves to a
            created resource's physical ID, and `{ "param": "ParamName" }` /
            `{ "sub": "text ${ParamName}" }` embed parameter values. The raw
            expressions are preserved here; the resolved values from the last
            deploy are exposed on the formation's `resolved_metadata` field.
          allOf:
            - $ref: './metadata.yaml#/components/schemas/NullableMetadataBag'

    ParameterDeclaration:
      type: object
      properties:
        type:
          type: string
          description: Parameter type (currently only 'string' is supported)
          example: string
        default:
          type: string
          description: Default value used when the parameter is not supplied at deploy time
          nullable: true
        description:
          type: string
          description: Human-readable description of what this parameter represents
          nullable: true
        no_echo:
          type: boolean
          description: >
            When true, the parameter value should be treated as sensitive and
            not echoed in logs or UI. Analogous to NoEcho in CloudFormation.
          nullable: true
        use_previous_value:
          type: boolean
          description: >
            When true, omitting this parameter on update reuses its previously
            stored value instead of failing the required-parameter check —
            analogous to CloudFormation's UsePreviousValue, declared in the
            template. An explicitly supplied value still overrides. Has no
            effect on create (there is no previous value yet). The value is
            reused only where the underlying resource retains it (e.g. a
            secret's encrypted value); otherwise the last-applied value is used.
          nullable: true

    AgentResourceProperties:
      description: >-
        Creates an AI agent backed by a provider. The agent handles requests,
        runs tools, and can be attached to actors. Exactly one of
        `ai_provider_id` or `model_route_id` must be declared. Switching an
        existing agent between the two declares the new field together with an
        explicit `null` for the old one.
      type: object
      additionalProperties: false
      properties:
        ai_provider_id:
          x-soat-ref: ai-providers
          type: string
          nullable: true
          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
          nullable: true
          description: >-
            Public ID of a model route in the same project — the agent's
            completion model is resolved through the route's ordered targets
            with failover. Mutually exclusive with `ai_provider_id` and `model`.
        name:
          type: string
          nullable: true
          description: Agent display name
        instructions:
          type: string
          nullable: true
          description: System instructions for the agent
        model:
          type: string
          nullable: true
          description: Model identifier (overrides provider default)
        tool_bindings:
          type: array
          nullable: true
          items:
            type: object
            additionalProperties: false
            properties:
              tool_id:
                x-soat-ref: tools
                type: string
                description: Public ID of the tool to attach.
              tool:
                type: object
                nullable: true
                description: >-
                  Inline tool definition. Not supported in a template: declare
                  a tool resource and reference it via `tool_id`.
          description: >-
            Tools to attach, one binding object per tool: `{ tool_id }`.
            Tool-call gating is owned by guardrails (attached via `guardrail_ids`
            on the project, agent, or tool), not by the binding. Inline `tool`
            entries are not supported in templates; declare a tool resource and
            reference it via `tool_id` (a `{ "ref": … }` to a tool resource in
            the same template resolves at deploy time).
        max_steps:
          type: integer
          nullable: true
          description: Maximum number of agentic steps per generation
        tool_choice:
          description: 'Controls how the model selects tools. Accepts a string (`"auto"`, `"required"`) or an object (`{ "type": "tool", "tool_name": "my_tool" }`).'
        stop_conditions:
          type: array
          nullable: true
          description: Conditions that stop the agent's work early — turn-scoped (`has_tool_call`) or chain-scoped (`max_chain_generations`).
          items:
            type: object
            additionalProperties: false
            properties:
              type:
                type: string
                description: 'Condition type — `has_tool_call` or `max_chain_generations`'
              tool_name:
                type: string
                nullable: true
                description: Tool name to match when type is `has_tool_call`
              max_generations:
                type: integer
                nullable: true
                description: Generations the continuation chain may reach when type is `max_chain_generations`
        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.
        step_rules:
          type: array
          nullable: true
          description: Per-step overrides applied during multi-step generation. Steps not covered by a rule use the agent defaults.
          items:
            type: object
            additionalProperties: false
            properties:
              step:
                type: integer
                description: 1-indexed step number this rule applies to
              tool_choice:
                type: object
                nullable: true
                description: 'Tool choice override for this step, e.g. `auto`, `required`, or `{ type: tool, tool_name: search }`'
              active_tool_ids:
                x-soat-ref: tools
                type: array
                nullable: true
                items:
                  type: string
                description: Tool IDs active on this step
        boundary_policy:
          type: object
          nullable: true
          description: Restricts which SOAT actions the agent may invoke. Evaluated as the intersection with the caller's own policy.
          additionalProperties: false
          properties:
            statement:
              type: array
              description: List of IAM policy statements
              items:
                type: object
                additionalProperties: false
                properties:
                  effect:
                    type: string
                    description: 'Effect — `Allow` or `Deny`'
                  action:
                    type: array
                    items:
                      type: string
                    description: IAM action strings, e.g. `memories:*` or `agents:DeleteAgent`
                  resource:
                    type: array
                    nullable: true
                    items:
                      type: string
                    description: Resource SRN patterns (optional; omit to match all resources)
                  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.
        temperature:
          type: number
          nullable: true
          description: Sampling temperature
        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
          nullable: true
          description: When true, only one open session per actor_id is allowed for this agent.
        trace_content_mode:
          type: string
          nullable: true
          description: >-
            Agent-scope zero-retention setting (`full` or `none`). `null`
            inherits the project's setting. `full` is refused when the project's
            own mode is `none`.
        on_approval_expiry:
          type: string
          nullable: true
          description: >-
            What happens when a held tool call expires un-approved:
            `terminate` (the default when null) ends the chain, `react` spawns
            a continuation that reports the staleness to the agent.
        knowledge_config:
          type: object
          nullable: true
          description: Knowledge retrieval configuration. When set, relevant documents and memories are injected into every generation.
          additionalProperties: false
          properties:
            memory_store_ids:
              x-soat-ref: memory-stores
              type: array
              items:
                type: string
              description: Public IDs of memory stores to retrieve from
            document_ids:
              x-soat-ref: documents
              type: array
              items:
                type: string
              description: Public IDs of documents to retrieve from
            document_paths:
              type: array
              items:
                type: string
              description: Retrieve from all documents matching these path prefixes
            tags:
              description: >-
                Retrieve from documents and memories whose tags contain
                all these key-value pairs.
              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. Omitted, there is no floor.
            rrf_k:
              type: integer
              description: The `k` in the fusion term `1 / (k + rank)`; smaller weights the top of each ranking more heavily
            recency_half_life_days:
              type: number
              description: Half-life in days of the decay applied to memory results after fusion; `0` disables it
            limit:
              type: integer
              description: Maximum number of chunks to inject
            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. 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. Non-streaming generations are constrained to this schema; the parsed value is returned as `output.object`.
        prompt_caching:
          type: object
          nullable: true
          description: >-
            Prompt caching for this agent's turns. `{"enabled": true}` marks a
            cache breakpoint at the end of the system block and on the last
            message of each step. Null or omitted is off.
          additionalProperties: false
          properties:
            enabled:
              type: boolean
              description: Whether the breakpoint is marked. Defaults to false.

    ActorResourceProperties:
      description: >-
        Creates a stateful conversation actor that wraps an agent or chat
        session and optionally links to a memory store.
      type: object
      additionalProperties: false
      required:
        - name
      properties:
        name:
          type: string
          description: Actor display name
        external_id:
          type: string
          nullable: true
          description: External identifier for idempotent actor creation
        instructions:
          type: string
          nullable: true
          description: Persona-specific instructions
        agent_id:
          x-soat-ref: agents
          type: string
          nullable: true
          description: Linked agent ID (mutually exclusive with chat_id)
        chat_id:
          x-soat-ref: chats
          type: string
          nullable: true
          description: Linked chat ID (mutually exclusive with agent_id)

    AiProviderResourceProperties:
      description: >-
        Configures an LLM provider connection (API key, model, endpoint)
        that agents use to generate responses.
      type: object
      additionalProperties: false
      required:
        - name
        - provider
        - default_model
      properties:
        name:
          type: string
          description: Provider display name
        provider:
          type: string
          enum:
            - openai
            - anthropic
            - google
            - xai
            - groq
            - ollama
            - azure
            - bedrock
            - vertex
            - gateway
            - custom
          description: Provider type
        default_model:
          type: string
          description: Default model identifier (e.g. gpt-4o, claude-3-7-sonnet)
        secret_id:
          x-soat-ref: secrets
          type: string
          nullable: true
          description: Public ID of the secret containing the API key
        base_url:
          type: string
          nullable: true
          description: Custom base URL for the provider API (self-hosted or proxy)
        config:
          type: object
          nullable: true
          description: Provider-specific extra configuration

    ToolResourceProperties:
      description: >-
        Defines a tool (HTTP endpoint, MCP server, SOAT action, or pipeline)
        that agents can invoke during a generation.
      type: object
      additionalProperties: false
      required:
        - name
      properties:
        name:
          type: string
          description: Tool display name
        type:
          type: string
          nullable: true
          description: Tool type hint (e.g. http, mcp, builtin, pipeline)
        description:
          type: string
          nullable: true
          description: Tool description shown to the model
        parameters:
          type: object
          nullable: true
          description: JSON Schema describing the tool's input parameters (free-form, user-defined)
        execute:
          type: object
          nullable: true
          description: HTTP execution configuration. Required for `http` tools.
          additionalProperties: false
          properties:
            url:
              type: string
              description: 'Endpoint URL. Supports `{param}` placeholders resolved from tool arguments.'
            method:
              type: string
              nullable: true
              description: 'HTTP method (default: `POST`)'
            headers:
              type: object
              nullable: true
              description: Static headers included in every request
            body_mode:
              type: string
              nullable: true
              description: >-
                Request body encoding for `POST`/`PUT`/`PATCH`: `json`
                (default) or `multipart`. Incompatible with
                `auth.type: aws_sigv4`.
            response_mode:
              type: string
              nullable: true
              description: >-
                How a 2xx response body is returned: `json` (default) or
                `base64`, which returns `{ content_type, filename,
                data_base64 }` for binary targets.
            auth:
              type: object
              nullable: true
              additionalProperties: false
              description: >-
                Computed request credential. `type` is `aws_sigv4` (with
                `region`, `service`, `access_key_id`, `secret_access_key` and
                optional `session_token`) or `gcp_service_account` (with
                `credentials` and `scopes`). Credential fields accept
                `{{secret:...}}` references.
              properties:
                type:
                  type: string
                  description: '`aws_sigv4` or `gcp_service_account`'
                region:
                  type: string
                  nullable: true
                  description: '`aws_sigv4`: the signing region'
                service:
                  type: string
                  nullable: true
                  description: '`aws_sigv4`: the signing service'
                access_key_id:
                  type: string
                  nullable: true
                  description: '`aws_sigv4`: the access key id'
                secret_access_key:
                  type: string
                  nullable: true
                  description: '`aws_sigv4`: the secret access key'
                session_token:
                  type: string
                  nullable: true
                  description: '`aws_sigv4`: the temporary credential''s session token'
                credentials:
                  type: string
                  nullable: true
                  description: >-
                    `gcp_service_account`: the service account key file JSON,
                    as a string
                scopes:
                  type: array
                  nullable: true
                  items:
                    type: string
                  description: '`gcp_service_account`: the scopes to mint for'
        mcp:
          type: object
          nullable: true
          description: MCP server connection configuration. Required for `mcp` tools.
          additionalProperties: false
          properties:
            url:
              type: string
              description: MCP server URL
            headers:
              type: object
              nullable: true
              description: Headers included in every MCP request
        actions:
          type: array
          nullable: true
          items:
            type: string
          description: >-
            Allowlist of actions the tool exposes. For `builtin` tools: SOAT
            platform action names. For `mcp` tools: an optional allowlist of MCP
            tool names to scope the server surface (`null` exposes every tool).
        denied_actions:
          type: array
          nullable: true
          items:
            type: string
          description: >-
            For `mcp` tools: an optional denylist of MCP tool names to hide.
            Applied after `actions` and taking precedence over it — the
            ergonomic way to scope a read+write MCP server read-only by denying
            just the write tools. `null` denies nothing.
        context_keys:
          type: array
          nullable: true
          items:
            type: string
          description: >-
            Optional allowlist of `tool_context` keys forwarded to this tool as
            prefixed context headers. `null` or omitted forwards every key;
            `[]` forwards none. The server-pinned identity keys (`session_id`,
            `actor_id`, `actor_external_id`) are always forwarded, and a key
            consumed by a `{{context:<key>}}` token in this tool's own headers
            is substituted regardless of this list.
        preset_parameters:
          type: object
          nullable: true
          description: Pre-filled parameter values injected at execution time
        pipeline:
          type: object
          nullable: true
          description: >-
            Pipeline definition for `pipeline` tools: an ordered `steps` array,
            each invoking another tool by `tool_id` (optional `action`) with an
            `input` built from earlier results via JSON Logic over
            `{ input, steps }`, plus an optional `output` mapping. Step `input`
            keys and `var` paths use camelCase (the runtime form). Free-form,
            user-defined.
        output_mapping:
          type: object
          nullable: true
          description: >-
            Universal JSON Logic mapping applied to the tool's raw result, for
            every tool type. Evaluated over `{ output: <raw result> }`, e.g.
            `{ "var": "output.text" }`. For `pipeline` tools this runs after
            the pipeline's own `output` mapping.
        guardrail_ids:
          x-soat-ref: guardrails
          type: array
          nullable: true
          items:
            type: string
          description: Guardrails attached at the tool scope.

    DatasetResourceProperties:
      description: >-
        Declares an evaluation dataset — the named fixture suite an eval runs an
        agent against. Its test cases are declared separately as `dataset_item`
        resources, so an item curated through the API is never collateral of a
        formation apply. Deleting the dataset deletes its items and the evals
        bound to it.
      type: object
      additionalProperties: false
      required:
        - name
      properties:
        name:
          type: string
          description: Dataset name, unique within the project
        description:
          type: string
          nullable: true
          description: Optional description

    DatasetItemResourceProperties:
      description: >-
        One test case in a dataset: the messages sent to the agent under test
        and, optionally, the reference answer scorers compare against. Editing
        or removing an item never rewrites a run that already scored it — each
        result froze its own copy.
      type: object
      additionalProperties: false
      required:
        - dataset_id
        - input
      properties:
        dataset_id:
          x-soat-ref: datasets
          type: string
          description: Public ID of the parent dataset (or ref expression)
        input:
          type: array
          items:
            type: object
          description: The messages sent to the agent, as `{role, content}` objects
        expected_output:
          type: string
          nullable: true
          description: Reference answer for exact_match / contains / embedding_similarity / llm_judge scorers
        metadata:
          description: >-
            Free-form tags on the case, e.g. `{"topic": "billing"}`
          allOf:
            - $ref: './metadata.yaml#/components/schemas/NullableMetadataBag'

    EvalResourceProperties:
      description: >-
        Binds an agent under test to a dataset and the scorers its outputs are
        judged by. `pass_threshold` is the pass rate a run must reach for its
        `passed` verdict — the gate an agent-version promotion consumes.
      type: object
      additionalProperties: false
      required:
        - name
        - agent_id
        - dataset_id
        - scorers
      properties:
        name:
          type: string
          description: Eval name, unique within the project
        agent_id:
          x-soat-ref: agents
          type: string
          description: Public ID of the agent under test (or ref expression)
        dataset_id:
          x-soat-ref: datasets
          type: string
          description: Public ID of the dataset to run against (or ref expression)
        scorers:
          type: array
          items:
            type: object
          description: >-
            Scorer configs — `exact_match`, `contains`, `json_logic`,
            `output_schema`, `embedding_similarity`, `llm_judge`, `tool`, or
            `decider`.
            Same shape as the evals REST contract.
        pass_threshold:
          type: number
          nullable: true
          description: >-
            0–1. A run passes when its pass rate over non-errored items reaches
            this. Omit for a run that reports scores without a verdict.
        group_by:
          type: string
          nullable: true
          minLength: 1
          maxLength: 255
          description: >-
            A key of the items' `metadata`; a run rolls its scores up per
            string value of it. Omit for no grouping.

    DocumentResourceProperties:
      description: >-
        Stores a text document in a project, optionally indexing it for
        knowledge retrieval.
      type: object
      additionalProperties: false
      required:
        - content
      properties:
        content:
          type: string
          description: Document text content
        path:
          type: string
          nullable: true
          description: Virtual path for organising the document
        filename:
          type: string
          nullable: true
          description: Original filename
        title:
          type: string
          nullable: true
          description: Document title
        metadata:
          description: Arbitrary metadata key-value pairs
          allOf:
            - $ref: './metadata.yaml#/components/schemas/NullableMetadataBag'
        tags:
          type: object
          nullable: true
          description: Tag key-value pairs for filtering
        chunk_strategy:
          type: string
          enum: [page, whole, size]
          description: >-
            How to split the content into embeddable chunks, matching
            `POST /documents`. `whole` (default) stores the content as a single
            chunk; `size` splits into fixed-size character windows with overlap.
            `page` is equivalent to `whole` for plain text.
          default: whole
        chunk_size:
          type: integer
          description: Window size in characters when `chunk_strategy=size`. Defaults to 1000.
        chunk_overlap:
          type: integer
          description: Overlap in characters between consecutive windows when `chunk_strategy=size`. Defaults to 200.

    MemoryStoreResourceProperties:
      description: >-
        Creates a named memory store that actors can read from and write to
        across conversations.
      type: object
      additionalProperties: false
      required:
        - name
      properties:
        name:
          type: string
          description: Memory store display name
        description:
          type: string
          nullable: true
          description: What this memory store stores
        tags:
          $ref: './tags.yaml#/components/schemas/NullableTagBag'
        duplicate_threshold:
          type: number
          nullable: true
          minimum: 0
          maximum: 1
          description: >-
            Cosine similarity at or above which an incoming fact is already
            known and the write is skipped. Null uses the algorithm constant
            (0.95). This is where a template sets the corpus's dedup policy:
            a `memory` resource has no threshold of its own.
        supersede_threshold:
          type: number
          nullable: true
          minimum: 0
          maximum: 1
          description: >-
            Cosine similarity at or above which an incoming fact restates a
            known one that has changed, retiring it. Null uses the algorithm
            constant (0.90). Must be lower than the effective
            `duplicate_threshold`.
    MemoryResourceProperties:
      description: Adds a single memory to a memory store.
      type: object
      additionalProperties: false
      required:
        - memory_store_id
        - content
      properties:
        memory_store_id:
          x-soat-ref: memory-stores
          type: string
          description: Public ID of the parent memory store (or ref expression)
        content:
          type: string
          description: Text content of the memory
        source_type:
          type: string
          enum: [manual, conversation]
          description: >-
            Whether there is a source to point at (defaults to manual).
            `conversation` requires `source_id`.
        source_id:
          type: string
          nullable: true
          description: >-
            The conversation this fact was learned in, when `source_type` is
            `conversation`; null when it is `manual`.
        tags:
          description: >-
            Per-memory key-value tags for memory-granularity filtering in
            knowledge search.
          allOf:
            - $ref: './tags.yaml#/components/schemas/NullableTagBag'
        metadata:
          description: Arbitrary structured metadata attached to the memory
          allOf:
            - $ref: './metadata.yaml#/components/schemas/NullableMetadataBag'

    ModelRouteResourceProperties:
      description: >-
        Declares a model route within the formation's project: a named, ordered
        list of provider+model failover targets with retry and circuit-breaker
        configuration. Consumers reference it through their own `model_route_id`,
        or inherit it as the project's `default_model_route_id`.
      type: object
      additionalProperties: false
      required:
        - name
        - targets
      properties:
        name:
          type: string
          description: Route name, unique within the project
        targets:
          type: array
          description: >-
            Ordered failover targets, tried in array order. Each entry is
            `{ ai_provider_id, model, timeout_seconds?, max_retries? }`; every
            provider must belong to this project, and the total attempt budget
            (sum of `1 + max_retries`) is capped at 10.
          items:
            type: object
            additionalProperties: false
            required:
              - ai_provider_id
              - model
            properties:
              ai_provider_id:
                x-soat-ref: ai-providers
                type: string
                description: AI provider in the route's project.
              model:
                type: string
                description: Model name to call on that provider.
              timeout_seconds:
                type: integer
                nullable: true
                description: >-
                  Per-attempt deadline. Omitted means no per-target deadline.
              max_retries:
                type: integer
                nullable: true
                description: >-
                  Retries on this target before falling through to the next
                  one.
        retry_on:
          type: array
          description: >-
            Which failure classes fail over: any of `provider_error`, `timeout`,
            `rate_limited`. Defaults to all three. Deterministic rejections
            (400-class, auth, content policy) never fail over.
          items:
            type: string
        failure_threshold:
          type: integer
          nullable: true
          description: >-
            Consecutive retryable failures before a target is skipped (default 3)
        cooldown_seconds:
          type: integer
          nullable: true
          description: How long a tripped target is skipped before being probed again (default 60)

    WebhookResourceProperties:
      description: >-
        Registers an HTTPS endpoint to receive SOAT platform event
        notifications.
      type: object
      additionalProperties: false
      required:
        - name
        - url
        - events
      properties:
        name:
          type: string
          description: Webhook display name
        description:
          type: string
          nullable: true
          description: Optional description
        url:
          type: string
          description: HTTPS endpoint that receives event payloads
        events:
          type: array
          items:
            type: string
          description: Event types to subscribe to (e.g. `documents.ingested`)

    TriggerResourceProperties:
      description: >-
        Binds a starter (manual, webhook, schedule, or event) to an executable
        target (orchestration, agent, tool, or eval). Firings run under the
        confined run-as identity of the caller who deployed the formation, so a
        firing never exceeds what that caller could do directly.
      type: object
      additionalProperties: false
      required:
        - name
        - type
        - target_type
        - target_id
      properties:
        name:
          type: string
          description: Trigger display name (unique within the project)
        description:
          type: string
          nullable: true
          description: Optional description
        type:
          type: string
          enum:
            - manual
            - webhook
            - schedule
            - event
          description: Starter type. Immutable after creation
        target_type:
          type: string
          enum:
            - orchestration
            - agent
            - tool
            - eval
          description: The kind of resource this trigger activates
        target_id:
          type: string
          description: >-
            Public ID of the target resource. Use { "ref": "LogicalId" } to
            reference an orchestration, agent, tool, or eval defined in the
            template.
        action:
          type: string
          nullable: true
          description: Tool targets only — the action for builtin/mcp tools
        input:
          type: object
          nullable: true
          description: Static input shallow-merged under each firing's runtime input
        tool_context:
          type: object
          nullable: true
          additionalProperties:
            type: string
          description: >-
            Caller context every firing forwards to the run it starts, so an
            agent whose tools authorize through `{{context:<key>}}` can be
            scheduled. Write-only. A value may be a `{{secret:sec_...}}`
            reference, which is what a template should carry — the credential
            stays in the secret store and only its id is checked in (a `sub`
            such as `{{secret:${MySecret}}}` for a template secret); a secret's
            name does not resolve and is refused
        cron:
          type: string
          nullable: true
          description: 5-field cron expression (UTC). Required when type is schedule
        event_pattern:
          type: string
          nullable: true
          description: >-
            Internal-event subscription pattern (`*`, `prefix.*`, or an exact
            event name). Required when type is event, rejected otherwise
        active:
          type: boolean
          description: Whether the trigger fires (default true)
        policy_id:
          x-soat-ref: policies
          type: string
          nullable: true
          description: >-
            Optional boundary policy that further confines the run-as identity

    ApiKeyResourceProperties:
      description: >-
        Creates an API key scoped to the formation's project and optionally
        restricted by a set of policies. The key is owned by the caller who
        deployed the formation, exactly as one created through the API is, so it
        never carries more access than they already have.
      type: object
      additionalProperties: false
      required:
        - name
      properties:
        name:
          type: string
          description: Human-readable label for the API key
        policy_ids:
          x-soat-ref: policies
          type: array
          items:
            type: string
          description: >-
            Optional list of policy public IDs that further restrict the key's
            permissions

    ChatResourceProperties:
      description: >-
        Creates a chat within the formation's project, connected either to an AI
        provider or — by declaring no provider — to the project's
        `default_model_route_id`.
      type: object
      additionalProperties: false
      properties:
        ai_provider_id:
          x-soat-ref: ai-providers
          type: string
          nullable: true
          description: >-
            Public ID of the AI provider to use for this chat. Omit (or declare
            `null`) to inherit the project's `default_model_route_id`, which
            requires the project to have one and cannot be combined with `model`.
        name:
          type: string
          nullable: true
          description: Human-readable label for the chat
        instructions:
          type: string
          nullable: true
          description: System message to set the assistant behaviour
        model:
          type: string
          nullable: true
          description: Model override; defaults to the AI provider's default model

    ConversationResourceProperties:
      description: >-
        Creates a conversation within the formation's project.
      type: object
      additionalProperties: false
      properties:
        name:
          type: string
          nullable: true
          description: Human-readable label for the conversation
        status:
          type: string
          description: Initial status of the conversation (open or closed)
        actor_id:
          x-soat-ref: actors
          type: string
          nullable: true
          description: Public ID of an actor to associate with this conversation

    FileResourceProperties:
      description: >-
        Registers a file record within the formation's project.
      type: object
      additionalProperties: false
      properties:
        prefix:
          type: string
          nullable: true
          description: Directory within the project. Optional; defaults to / (root). Combined with filename to form the file's key (path).
        filename:
          type: string
          nullable: true
          description: Original / download name and the key's leaf segment.
        content_type:
          type: string
          nullable: true
          description: MIME type of the file
        size:
          type: integer
          nullable: true
          description: File size in bytes
        metadata:
          description: >-
            Caller-owned annotations on the file, stored as the object they
            were written as; `null` clears the bag. Keys are stored and
            returned verbatim in the casing supplied.
          allOf:
            - $ref: './metadata.yaml#/components/schemas/NullableMetadataBag'

    PolicyResourceProperties:
      description: >-
        Creates an access-control policy within the formation's project.
      type: object
      additionalProperties: false
      required:
        - document
      properties:
        name:
          type: string
          nullable: true
          description: Human-readable label for the policy
        description:
          type: string
          nullable: true
          description: Description of what the policy grants
        document:
          type: object
          additionalProperties: false
          description: Policy document containing an array of statements
          properties:
            statement:
              type: array
              description: IAM policy statements.
              items:
                type: object
                additionalProperties: false
                required:
                  - effect
                  - action
                properties:
                  effect:
                    type: string
                    description: '`Allow` or `Deny`'
                  action:
                    type: array
                    items:
                      type: string
                    description: IAM action strings, e.g. `memories:*`.
                  resource:
                    type: array
                    nullable: true
                    items:
                      type: string
                    description: >-
                      Resource SRN patterns. Omit to match every resource.
                  condition:
                    type: object
                    nullable: true
                    additionalProperties: true
                    description: >-
                      Condition block: keys are condition operators mapping to
                      context-key/value maps.

    SecretResourceProperties:
      description: >-
        Creates an encrypted secret within the formation's project.
      type: object
      additionalProperties: false
      required:
        - name
        - value
      properties:
        name:
          type: string
          description: Human-readable label for the secret
        value:
          type: string
          description: The secret value to encrypt and store

    ProjectPriceResourceProperties:
      description: >-
        Upserts a project-scoped price row so a deployed stack produces
        billing-grade usage cost with no out-of-band pricing step. The row is
        keyed on (provider, model, component, effective_from) within the
        formation's project — the middle pricing tier that covers every one of
        the project's instances of a given provider slug. When `effective_from`
        is omitted the price takes effect at deploy time, so generations run
        right after deploy are priced.
      type: object
      additionalProperties: false
      required:
        - provider
        - model
        - component
        - unit
        - unit_price
      properties:
        provider:
          type: string
          description: SKU vendor slug the price applies to (e.g. openai, anthropic, soat)
        model:
          type: string
          description: SKU identifier — the model id for LLM SKUs, the platform unit otherwise
        component:
          type: string
          description: The billable component this row prices (input_tokens, output_tokens, cached_tokens, cache_write_tokens, compute_second, …)
        unit:
          type: string
          description: Unit the unit_price is denominated in (token, compute_second, …); must match the metered component's unit
        unit_price:
          type: number
          description: USD per unit. Must be a non-negative number
        meter_type:
          type: string
          description: Meter type this SKU belongs to (defaults to llm_tokens)
        effective_from:
          type: string
          format: date-time
          description: >-
            Timestamp from which this price applies. Omit to take effect at
            deploy time. The row with the latest effective_from at or before
            now() prices a call.

    SessionResourceProperties:
      description: >-
        Creates a session attached to an agent within the formation's project.
      type: object
      additionalProperties: false
      required:
        - agent_id
      properties:
        agent_id:
          x-soat-ref: agents
          type: string
          description: Public ID of the agent that owns this session
        name:
          type: string
          nullable: true
          description: Human-readable label for the session
        actor_id:
          x-soat-ref: actors
          type: string
          nullable: true
          description: Public ID of an actor to associate with this session
        auto_generate:
          type: boolean
          description: Whether to automatically generate a response when messages are sent
        inactivity_ttl_seconds:
          type: integer
          description: Number of seconds of inactivity after which the session expires. 0 means never expires.
        max_context_messages:
          type: integer
          nullable: true
          minimum: 1
          description: >-
            Maximum number of recent conversation messages sent to the model on
            each generation, taking precedence over the agent's. null defers to
            the agent.
        tool_context:
          type: object
          additionalProperties:
            type: string
          nullable: true
          description: >-
            Optional context passed to tool calls, a bag of string values; a
            non-string value is refused. Write-only: no read of a session
            returns it.

    IngestionRuleResourceProperties:
      description: >-
        Routes a file content_type to a converter (tool or agent) so
        ingestion can turn non-native files (images, audio, scanned PDFs)
        into Documents. See the Ingestion Rules module docs for the matching
        and converter-invocation model.
      type: object
      additionalProperties: false
      required:
        - content_type_glob
      properties:
        content_type_glob:
          type: string
          description: MIME type glob matched against a file's content_type (e.g. image/*, audio/mpeg, application/pdf)
        tool_id:
          x-soat-ref: tools
          type: string
          nullable: true
          description: Converter tool ID (mutually exclusive with agent_id)
        agent_id:
          x-soat-ref: agents
          type: string
          nullable: true
          description: Converter agent ID (mutually exclusive with tool_id)
        action:
          type: string
          nullable: true
          description: Operation id, required for builtin/mcp tool converters
        preset_parameters:
          type: object
          nullable: true
          description: Merged into the tool input before invocation (tool converters only)
        native_extraction:
          type: string
          nullable: true
          description: >-
            For native types (PDF/text): `first` (default) converts only
            when native extraction yields no text; `skip` always converts.
        file_delivery:
          type: string
          nullable: true
          description: How the file reaches a tool converter — base64 (default) or download_url
        chunk_strategy:
          type: string
          nullable: true
          description: Default chunk strategy (page/whole/size), overridable per ingest request
        chunk_size:
          type: integer
          nullable: true
          description: Default window size in characters for the size strategy
        chunk_overlap:
          type: integer
          nullable: true
          description: Default overlap in characters for the size strategy
        metadata:
          description: Arbitrary JSON metadata
          allOf:
            - $ref: './metadata.yaml#/components/schemas/NullableMetadataBag'

    MemoryRuleResourceProperties:
      description: >-
        Declares what a memory store accepts from completed agent turns: a
        selector, an event, and a handler. With neither `agent_id` nor `tool_id`
        the built-in extractor runs, configurable through `prompt`,
        `ai_provider_id` and `model`. See the Memory Rules section of the
        Memories module docs.
      type: object
      additionalProperties: false
      required:
        - memory_store_id
        - on
      properties:
        memory_store_id:
          x-soat-ref: memory-stores
          type: string
          description: Public ID of the destination memory store
        on:
          type: string
          description: >-
            `generations.completed` (once per completed turn, and the only
            event the built-in extractor may bind to) or
            `conversation_messages.generated` (per persisted assistant reply,
            custom handlers only).
        source_agent_ids:
          x-soat-ref: agents
          type: array
          nullable: true
          description: Agents whose turns this rule reads; null is every agent in the project
          items:
            type: string
        agent_id:
          x-soat-ref: agents
          type: string
          nullable: true
          description: Handler agent ID (mutually exclusive with tool_id)
        tool_id:
          x-soat-ref: tools
          type: string
          nullable: true
          description: Handler tool ID (mutually exclusive with agent_id)
        action:
          type: string
          nullable: true
          description: Operation id, for a tool handler
        preset_parameters:
          type: object
          nullable: true
          description: Merged into a tool handler's input before invocation
        prompt:
          type: string
          nullable: true
          description: Replaces the built-in extractor's task instructions (no handler only)
        ai_provider_id:
          x-soat-ref: ai-providers
          type: string
          nullable: true
          description: Provider override for the built-in extractor (no handler only)
        model:
          type: string
          nullable: true
          description: Model override for the built-in extractor (no handler only)
        enabled:
          type: boolean
          description: A disabled rule is kept and never fires

    OrchestrationResourceProperties:
      description: >-
        Creates a DAG orchestration that wires agents, tools, and knowledge
        lookups into a repeatable pipeline within the formation's project. Node
        resource references (`agent_id`, `tool_id`, `memory_store_id`,
        `orchestration_id`) accept `{ "ref": "LogicalId" }` expressions to point
        at other resources declared in the same template — the basis for
        deploying an agent "squad" (a team of agents plus the flow that
        coordinates them) as a single stack.
      type: object
      additionalProperties: false
      required:
        - name
        - nodes
        - edges
      properties:
        name:
          type: string
          description: Human-readable name for the orchestration
        description:
          type: string
          nullable: true
          description: Optional description of what the orchestration does
        nodes:
          type: array
          description: >-
            Ordered list of node definitions. A node's resource references
            (`agent_id`, `tool_id`, `memory_store_id`, `orchestration_id`) may use
            `{ "ref": "LogicalId" }` to bind to other resources in the template.
          items:
            type: object
            additionalProperties: true
        edges:
          type: array
          description: Directed connections between nodes
          items:
            type: object
            additionalProperties: true
        state_schema:
          type: object
          nullable: true
          additionalProperties: true
          description: Optional JSON Schema describing the run state
        input_schema:
          type: object
          nullable: true
          additionalProperties: true
          description: Optional JSON Schema describing the run input
        output_mapping:
          type: object
          nullable: true
          additionalProperties: true
          description: >-
            Optional shape of a completed run's `output`: each key an output
            field, each value JSON Logic over `{ "state": <final run state> }`.
            Omitted keys `output` by terminal node id.

    WorkflowResourceProperties:
      description: >-
        Creates a workflow — a state-machine definition (named states, allowed
        transitions, guards, and per-state automation) that tasks live in. State
        and transition dispatch references (`agent_id`, `orchestration_id`,
        `tool_id` inside an `on_enter` block) accept `{ "ref": "LogicalId" }`
        expressions to point at agents, orchestrations or tools declared in the
        same template, so a workflow plus the agents and tools that service its
        states can deploy as one stack. Mirrors
        the workflows REST contract (`states`, `transitions`, `payload_schema`).
      type: object
      additionalProperties: false
      required:
        - name
        - states
        - transitions
      properties:
        name:
          type: string
          description: Human-readable name for the workflow, unique within the project
        description:
          type: string
          nullable: true
          description: Optional description of what the workflow models
        states:
          type: array
          description: >-
            Named states. Exactly one must be `initial: true`; any number may be
            `terminal: true`. A `kind: human` state parks the task until a
            transition fires; an `on_enter` block dispatches one agent generation
            or orchestration run on entry.
          items:
            type: object
            additionalProperties: true
        transitions:
          type: array
          description: >-
            Named, directional moves between states. Each has `from` (source
            states) and `to` (one target), an optional JSON Logic `guard`, and an
            optional `requires_approval` gate.
          items:
            type: object
            additionalProperties: true
        payload_schema:
          type: object
          nullable: true
          additionalProperties: true
          description: Optional JSON Schema describing a task's payload

    MetadataSchemaResourceProperties:
      description: >-
        Declares what `metadata` must satisfy for one resource type under one
        selector, so a template ships the corpus and the rule governing it
        together. `resource_type` is immutable: changing it in a template is a
        delete and a create.
      type: object
      additionalProperties: false
      required:
        - resource_type
        - schema
      properties:
        resource_type:
          type: string
          enum: [document]
          description: The resource whose metadata this declaration governs.
        path_prefix:
          type: string
          description: >-
            The selector a `document` declaration must carry: the directory it
            governs, matched on a path boundary.
        schema:
          type: object
          description: A JSON Schema, stored as written.
    QuotaResourceProperties:
      description: >-
        Creates a quota — a project-scoped cap that blocks (`enforce`) or reports
        (`monitor`) when a windowed aggregate is exceeded. `requests` quotas are
        enforced by the request middleware; `tokens`/`cost_usd` quotas at the
        pre-generation check. Mirrors the quotas REST contract; `scope`,
        `metric`, `window`, and `meter_type` are immutable after creation (only
        `limit`, `mode`, and `on_unpriced` update).
      type: object
      additionalProperties: false
      required:
        - scope
        - metric
        - window
        - limit
      properties:
        scope:
          type: string
          enum: [project, api_key, agent, actor]
          description: The scope the quota applies to
        scope_ref:
          type: string
          nullable: true
          description: >-
            Public id of the api key / agent / actor the quota applies to. For
            `api_key` and `agent` scope, NULL means all entities of that scope
            type in the project. For `actor` scope, NULL means one budget *per*
            actor rather than a pooled total across all actors.
        metric:
          type: string
          enum: [requests, tokens, cost_usd, storage_bytes]
          description: The metric being capped
        window:
          type: string
          enum: [rolling_1m, rolling_1h, rolling_24h, calendar_month, current]
          description: >-
            The window over which the metric is aggregated. storage_bytes caps
            a stored total rather than a windowed one, so it takes current and
            refuses every other value; current is refused for every other
            metric.
        limit:
          type: number
          description: >-
            The cap. Positive integer for requests/tokens/storage_bytes (bytes);
            fractional allowed for cost_usd.
        mode:
          type: string
          enum: [enforce, monitor]
          description: enforce blocks with 429; monitor fires the webhook only
        on_unpriced:
          type: string
          enum: [block, allow]
          description: >-
            Only for metric cost_usd. What an enforce quota does over a pricing
            blackout — block (the default) refuses generations with 409
            QUOTA_UNENFORCEABLE, allow accepts the unmeasurable spend. See the
            quotas REST contract.
        meter_type:
          type: string
          enum: [llm_tokens, compute_execution, api_request, storage, tool_execution]
          description: >-
            Only for metric cost_usd. The meter this cap answers for; omit it
            and the cap sums every priced meter. See the quotas REST contract.

    GuardrailResourceProperties:
      description: >-
        Creates a guardrail — an action-class document (`class`/`guard`) that
        gates tool-call autonomy. Attach it to a tool or agent via that
        resource's `guardrail_ids` (a `{ "ref": … }` to this resource in the
        same template resolves to its physical id at deploy time). Mirrors the
        guardrails REST contract; `class`/`default_class`/`guard`/`escalate`
        are flattened here from the REST API's single `document` object.
      type: object
      additionalProperties: false
      required:
        - name
        - class
      properties:
        name:
          type: string
          description: Human-readable name
        description:
          type: string
          nullable: true
          description: Optional description
        class:
          description: >-
            A class literal (`A` / `B` / `C` / `D`) or a JSON Logic expression
            returning one. An invalid result resolves to `default_class`.
          oneOf:
            - type: string
              enum: [A, B, C, D]
            - type: object
        default_class:
          type: string
          enum: [A, B, C, D]
          description: >-
            Applied when the `class` expression returns anything other than a
            valid class. Defaults to `C` (fail-closed).
        guard:
          type: object
          nullable: true
          description: >-
            A single JSON Logic expression; when the call classifies as `B` it
            executes only if this evaluates truthy.
        escalate:
          type: boolean
          nullable: true
          description: When true, a passing guard still files an approval item.
        context_tool_id:
          x-soat-ref: tools
          type: string
          nullable: true
          description: >-
            Optional tool the platform calls at evaluation time to fetch fresh
            guardrail context.
        context_mode:
          type: string
          nullable: true
          enum: [merge, replace, null]
          description: >-
            How tool-fetched context combines with the caller-supplied context.

    DeciderResourceProperties:
      description: >-
        Creates a decider — a versioned question set answered against a
        caller's input. Names exactly one backend: `agent_id` (a tool-less
        agent) or `tool_id` (an `http` or `pipeline` tool), either a `{ "ref":
        … }` to a resource in the same template. Only a change to `questions`
        archives a new version. Mirrors the deciders REST contract.
      type: object
      additionalProperties: false
      required:
        - name
        - questions
      properties:
        name:
          type: string
          description: Human-readable name, unique per project
        description:
          type: string
          nullable: true
          description: Optional description
        agent_id:
          x-soat-ref: agents
          type: string
          description: The tool-less agent that answers
        tool_id:
          x-soat-ref: tools
          type: string
          description: The http or pipeline tool that answers
        questions:
          type: array
          items:
            type: object
            additionalProperties: true
          description: >-
            The questions (`type`, `name`, `instructions`, `choices` or
            `levels`), 1 to 20 of them, validated as the deciders REST contract
            validates them.

    PlaybookResourceProperties:
      description: >-
        Creates a playbook — a versioned template of nodes that cases are
        filled against. An executor's `id` and a transformation's `tool` accept
        a `{ "ref": … }` to a resource in the same template. The version a
        deploy writes runs as the credential that deployed it, checked as a
        direct write is: `playbooks:CreatePlaybook` plus the start action of
        every executor it names. Only a change to `definition` archives a new
        version; a playbook with cases is not deleted. Mirrors the playbooks
        REST contract.
      type: object
      additionalProperties: false
      required:
        - name
        - definition
      properties:
        name:
          type: string
          description: Human-readable name, unique per project
        description:
          type: string
          nullable: true
          description: What the playbook is for
        definition:
          description: The nodes and how their inputs are sourced
          allOf:
            - $ref: './playbooks.yaml#/components/schemas/PlaybookDefinition'

    ResourceDeclaration:
      type: object
      required:
        - type
        - properties
      properties:
        type:
          type: string
          pattern: '^[a-z][a-z0-9_]*$'
          description: >
            Resource type. The built-in types are `ai_provider`, `tool`,
            `agent`, `actor`, `api_key`, `chat`, `conversation`, `dataset`,
            `dataset_item`, `decider`, `document`, `file`, `guardrail`, `ingestion_rule`,
            `memory_store`, `memory`, `memory_rule`, `metadata_schema`,
            `model_route`, `eval`, `orchestration`, `playbook`,
            `policy`, `project_price`, `quota`, `secret`, `session`, `webhook`,
            `trigger` and `workflow`.


            This is deliberately not an enum: a deployment operator can
            register additional resource types backed by their own handler, and
            those are declared here exactly like a built-in one. The set a given
            deployment accepts is authoritative in the server, which rejects an
            unregistered type with `VALIDATION_FAILED` and lists what it does
            support.
        properties:
          type: object
          additionalProperties: true
          description: >
            Resource properties, as authored in the template and echoed back
            verbatim. The allowed fields, required fields, and field types for
            each resource `type` are defined by the corresponding
            `<Type>ResourceProperties` schema in this document (e.g.
            `model_route` → `ModelRouteResourceProperties`), which the server
            enforces at validate/deploy time. The declaration itself is
            free-form here because property values may be substitution
            expressions rather than final values: `{ "ref": "logicalId" }`
            references another resource's physical ID,
            `{ "param": "ParamName" }` substitutes a parameter value, and
            `{ "sub": "text ${ParamName}" }` interpolates parameters into a
            string.
        depends_on:
          type: array
          items:
            type: string
          description: Explicit dependency list. In addition to implicit `ref` dependencies.
          nullable: true
        deletion_policy:
          type: string
          enum:
            - delete
            - retain
          description: >
            Controls what happens to the physical resource when it is removed
            from the stack. `delete` (default) deletes the physical resource.
            `retain` keeps the physical resource alive and only removes the
            formation record. Omit it to get `delete`; an explicit `null` is
            rejected.
        metadata:
          $ref: './metadata.yaml#/components/schemas/NullableMetadataBag'

    FormationResource:
      type: object
      properties:
        id:
          type: string
          description: Public ID of the resource record
        logical_id:
          type: string
          description: Logical identifier from the template
        resource_type:
          type: string
          description: Resource type (e.g. `agent`, `memory_store`)
        physical_resource_id:
          type: string
          nullable: true
          description: Public ID of the physical SOAT resource
        status:
          type: string
          enum:
            - pending
            - created
            - updated
            - deleted
            - failed
          description: Current resource status

    Formation:
      type: object
      properties:
        id:
          type: string
          description: Public ID of the formation
          example: form_V1StGXR8Z5jdHi6B
        project_id:
          x-soat-ref: projects
          type: string
          description: Project public ID
        name:
          type: string
          description: Human-readable formation name
        template:
          allOf:
            - $ref: '#/components/schemas/FormationTemplate'
          description: >
            The template as deployed. Credential-bearing properties — a `secret`
            resource's `value`, and any property a custom resource type declares
            `write_only` — read back as `{ "no_echo": true }` rather than the
            value that was supplied.
        outputs:
          type: object
          additionalProperties:
            type: string
          nullable: true
          description: >
            Resolved output values after stack deployment. An output that
            resolved a credential attribute on a formation deployed before those
            were refused is omitted.
        status:
          type: string
          enum:
            - creating
            - active
            - updating
            - failed
            - deleting
            - deleted
            - delete_failed
          description: Formation status
        metadata:
          description: >
            Static annotations stored on the formation record (supplied at
            create/update). Not a substitution site — `sub`/`param`/`ref`
            expressions are rejected. Use the template's top-level `metadata`
            block for deploy-time substitution (see `resolved_metadata`).
          allOf:
            - $ref: './metadata.yaml#/components/schemas/MetadataBag'
        resolved_metadata:
          type: object
          additionalProperties: true
          nullable: true
          description: >
            The template's top-level `metadata` block after parameter (`sub`/`param`)
            and resource (`ref`) substitution at the last deploy. Null when the
            template declares no metadata.
        resolved_parameters:
          type: object
          additionalProperties:
            type: string
          nullable: true
          description: >
            Parameter values applied at the last deploy, for auditability.
            `no_echo` parameters are masked (`***`). Null when the template
            declares no parameters.
        error:
          allOf:
            - $ref: './errors.yaml#/components/schemas/StoredError'
          nullable: true
          description: >
            Why the formation is `failed` or `delete_failed`. Null in every
            other status, and cleared by the next successful deploy. This is the
            reason a `2xx` deploy response can report `status: "failed"` without
            a second call to `list-formation-events`. A failed apply names the
            resource that broke it in `meta` (`logical_id`, `resource_type`); a
            failed teardown lists every blocker under `meta.failures`.


            One case carries an error while the formation is `active`:
            `FORMATION_REPLACE_CLEANUP_FAILED`, when a deploy replaced a
            resource and the superseded one could not be deleted. The desired
            state is realised, so the deploy succeeded — but the old resource is
            still live, and `meta.failures` names it. It stays on the formation
            as pending cleanup and is retried on the next deploy or teardown,
            which clears the error once it is gone.
        resources:
          type: array
          items:
            $ref: '#/components/schemas/FormationResource'
          description: Resources managed by this formation (present on get/create/update)
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    ValidationError:
      type: object
      properties:
        path:
          type: string
          description: JSON path to the field with the error
        message:
          type: string
          description: Error description

    ValidationResult:
      type: object
      properties:
        valid:
          type: boolean
        errors:
          type: array
          items:
            $ref: '#/components/schemas/ValidationError'
        warnings:
          type: array
          items:
            $ref: '#/components/schemas/ValidationError'

    PlanChange:
      type: object
      properties:
        logical_id:
          type: string
        resource_type:
          type: string
        action:
          type: string
          enum:
            - create
            - update
            - delete
            - no-op
        physical_resource_id:
          type: string
          description: >-
            The existing resource's physical ID. Present for update / no-op /
            delete actions, absent for create.
        diff:
          type: object
          description: >-
            Resolved desired-state properties (post parameter/ref
            substitution) and, when available, the current live or
            last-applied properties they were compared against. Omitted when
            neither side could be computed (e.g. an unregistered resource
            type).
          properties:
            desired:
              type: object
              additionalProperties: true
            current:
              type: object
              additionalProperties: true
              nullable: true

    PlanResult:
      type: object
      properties:
        changes:
          type: array
          items:
            $ref: '#/components/schemas/PlanChange'
        unauthorized_actions:
          type: array
          description: >-
            The per-resource actions the caller may not perform. A formation may
            only do what the caller could do directly, so applying this template
            would be refused while any of these remain. Absent when the caller
            may perform every action the plan implies. A plan itself changes
            nothing, so it reports them rather than failing.
          items:
            $ref: '#/components/schemas/UnauthorizedFormationAction'

    UnauthorizedFormationAction:
      type: object
      required:
        - logical_id
        - resource_type
        - action
      properties:
        logical_id:
          type: string
          description: The template's own name for the resource.
          example: MyGuardrail
        resource_type:
          type: string
          description: The declared resource type.
          example: guardrail
        action:
          type: string
          description: The action the caller lacks.
          example: guardrails:CreateGuardrail

    FormationEvent:
      type: object
      properties:
        timestamp:
          type: string
          format: date-time
        logical_id:
          type: string
        resource_type:
          type: string
        action:
          type: string
          description: >-
            What the deploy did to the resource: `create`, `update`, `delete`,
            `no-op`, `rollback` (a resource created earlier in this deploy that
            was walked back after a later failure), or `rollback-skipped` (a
            `deletion_policy: retain` resource left standing by that unwind).
          example: rollback
        status:
          type: string
          enum:
            - completed
            - failed
        physical_resource_id:
          type: string
          nullable: true
        error:
          allOf:
            - $ref: './errors.yaml#/components/schemas/StoredError'
          nullable: true
          description: Why the resource's step failed, when `status` is `failed`.

    FormationOperation:
      type: object
      properties:
        id:
          type: string
          description: Public ID of the operation
        operation_type:
          type: string
          enum:
            - validate
            - plan
            - create
            - update
            - delete
        status:
          type: string
          enum:
            - queued
            - running
            - completed
            - failed
        events:
          type: array
          items:
            $ref: '#/components/schemas/FormationEvent'
          nullable: true
        plan:
          allOf:
            - $ref: '#/components/schemas/PlanResult'
          nullable: true
        error:
          allOf:
            - $ref: './errors.yaml#/components/schemas/StoredError'
          nullable: true
          description: >-
            Why this operation failed. Null for a completed or running
            operation. The same bag the formation itself carries while that
            failure is its current state.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
