openapi: "3.1.0"
info:
  title: AgentClash API
  version: 1.0.0
  description: >
    B2B AI agent evaluation platform. Pits agent deployments against challenge
    packs, with replay inspection, scoring, comparison, and release gating.
  license:
    name: MIT
    url: https://github.com/agentclash/agentclash/blob/main/LICENSE
  contact:
    url: https://github.com/agentclash/agentclash

servers:
  - url: https://api.agentclash.dev
    description: AgentClash hosted API
  - url: http://localhost:8080
    description: Local development

security:
  - bearerJWT: []

tags:
  - name: Health
    description: Liveness and readiness probes
  - name: Session
    description: Current user session and identity
  - name: Workspace
    description: Workspace access verification
  - name: Runs
    description: Create, read, and list evaluation runs
  - name: EvalSessions
    description: Create evaluation sessions that fan out into repeated runs
  - name: Replays
    description: Step-by-step execution replays
  - name: Scorecards
    description: Agent scoring and evaluation results
  - name: Compare
    description: Side-by-side run comparison
  - name: ReleaseGates
    description: Release gate policy evaluation
  - name: Regressions
    description: Workspace regression suite and case management
  - name: Datasets
    description: Workspace eval datasets and versioned examples
  - name: Artifacts
    description: File upload, download, and content delivery
  - name: Billing
    description: Billing plans, entitlements, checkout, and Dodo webhook handling
  - name: ChallengePacks
    description: Challenge pack management and authoring
  - name: AgentBuilds
    description: Agent build and version lifecycle
  - name: AgentDeployments
    description: Agent deployment management
  - name: AgentHarnesses
    description: Long-running coding harness configuration and execution controls
  - name: HostedRuns
    description: External hosted agent event ingestion
  - name: AgentTryouts
    description: Public and workspace agent tryout templates, sandbox runs, claiming, and sharing
  - name: Publications
    description: Explicitly indexable, redacted public artifacts addressed by non-secret share record IDs
  - name: UserProfile
    description: Current user profile with org/workspace hierarchy
  - name: Onboarding
    description: First-time organization and workspace setup
  - name: Organizations
    description: Organization CRUD and management
  - name: OrgMemberships
    description: Organization membership invitations and updates
  - name: Workspaces
    description: Workspace details and management
  - name: WorkspaceMemberships
    description: Workspace membership invitations and updates
  - name: WorkspaceSecrets
    description: Encrypted workspace secret management
  - name: GitHubIntegrations
    description: GitHub App installation, repository selection, and CI setup
  - name: RuntimeProfiles
    description: Execution runtime profile configuration
  - name: ProviderAccounts
    description: LLM provider account credentials
  - name: Tools
    description: Workspace tool definitions
  - name: Policies
    description: Routing and spend policy configuration

paths:
  /public/publications:
    get:
      operationId: listPublications
      tags: [Publications]
      summary: List active indexable publications
      description: >
        Returns only active, unexpired, search-indexing-enabled artifacts that
        successfully pass a supported redacted public serializer. Capability
        tokens are never returned.
      security: []
      parameters:
        - name: cursor
          in: query
          schema:
            type: string
            format: uuid
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
      responses:
        "200":
          description: Publication page.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicPublicationList"
        "400":
          $ref: "#/components/responses/ValidationError"
        "500":
          $ref: "#/components/responses/InternalError"

  /public/publications/{publicationID}:
    get:
      operationId: getPublication
      tags: [Publications]
      summary: Get one active indexable publication
      description: >
        Resolves the non-secret public share record ID. Returns 404 when the
        share is inactive, revoked, expired, indexing-disabled, unsupported,
        malformed, or no longer serializable through its redacted renderer.
      security: []
      parameters:
        - name: publicationID
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Redacted publication.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicPublicationPayload"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/workspaces/{workspaceID}/agent-harness-executions/{executionID}/cancel:
    post:
      operationId: cancelAgentHarnessExecution
      tags: [AgentHarnesses]
      summary: Cancel an Agent Harness execution
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/ExecutionID"
      responses:
        "200":
          description: Execution cancelled, or already terminal.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentHarnessExecution"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/workspaces/{workspaceID}/agent-harness-executions/{executionID}/retry:
    post:
      operationId: retryAgentHarnessExecution
      tags: [AgentHarnesses]
      summary: Retry an Agent Harness execution
      description: >
        Retries a terminal harness execution using frozen snapshots from the
        original execution. The idempotency key scopes duplicate retry requests
        for the same source execution.
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/ExecutionID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RetryAgentHarnessExecutionRequest"
      responses:
        "201":
          description: Retry execution queued or existing retry returned.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentHarnessExecution"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/workspaces/{workspaceID}/agent-harness-executions/{executionID}/failure-review:
    get:
      operationId: getAgentHarnessFailureReview
      tags: [AgentHarnesses]
      summary: Get an Agent Harness failure review
      description: >
        Returns the rule or LLM-suggested failure classification for a harness
        execution, preserving any human-edited class and summary as the effective
        review.
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/ExecutionID"
      responses:
        "200":
          description: Failure review for the execution.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentHarnessFailureReview"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "500":
          $ref: "#/components/responses/InternalError"
    patch:
      operationId: updateAgentHarnessFailureReview
      tags: [AgentHarnesses]
      summary: Update an Agent Harness failure review
      description: >
        Stores LLM-assisted suggestions and human edits separately so later
        automated suggestions do not overwrite the human-curated class.
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/ExecutionID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateAgentHarnessFailureReviewRequest"
      responses:
        "200":
          description: Updated failure review.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentHarnessFailureReview"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/workspaces/{workspaceID}/agent-harness-executions/{executionID}/promote-task:
    post:
      operationId: promoteAgentHarnessExecutionToSuite
      tags: [AgentHarnesses]
      summary: Promote a prior harness run into a private suite task
      description: >
        Creates a new immutable suite version and appends a private
        prior_harness_run task using sanitized public prompt metadata plus the
        frozen harness, evaluation, validator, judge, and artifact references
        from the source execution.
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/ExecutionID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PromoteAgentHarnessExecutionRequest"
      responses:
        "201":
          description: Suite version advanced and task promoted.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PromoteAgentHarnessExecutionResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/workspaces/{workspaceID}/agent-harness-failures/summary:
    get:
      operationId: listAgentHarnessFailureSummary
      tags: [AgentHarnesses]
      summary: Summarize Agent Harness failure modes
      description: >
        Groups effective failure classes by repository, task type, harness,
        model, E2B template, and suite for dashboard analysis.
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      responses:
        "200":
          description: Failure summary groups.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListAgentHarnessFailureSummaryResponse"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/workspaces/{workspaceID}/agent-harness-suites/{suiteID}/rankings:
    get:
      operationId: getAgentHarnessSuiteRankings
      tags: [AgentHarnesses]
      summary: Rank Agent Harness suite results
      description: >
        Aggregates immutable Agent Harness execution snapshots, canonical
        run-agent scorecards, cost summaries, and execution events into suite
        rankings with success@1, pass@k, pass^k, confidence intervals, budgets,
        latency, cost, failure modes, and fair pairwise comparisons.
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/AgentHarnessSuiteID"
        - name: k
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            default: 1
          description: k value used for pass@k and pass^k reporting.
        - name: version_id
          in: query
          required: false
          schema:
            type: string
            format: uuid
          description: Immutable suite version to aggregate. Defaults to the current active version.
      responses:
        "200":
          description: Ranking evidence for the active suite version.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentHarnessSuiteRankingEnvelope"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "500":
          $ref: "#/components/responses/InternalError"

  # ---------------------------------------------------------------------------
  # Health
  # ---------------------------------------------------------------------------
  /healthz:
    get:
      operationId: getHealth
      tags: [Health]
      summary: Health check
      description: Returns service health status for load balancers and orchestrators.
      security: []
      responses:
        "200":
          description: Service is healthy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"
        "500":
          $ref: "#/components/responses/InternalError"

  /healthz/ready:
    get:
      operationId: getHealthReady
      tags: [Health]
      summary: Readiness check
      description: >-
        Reports whether the API server's dependencies (Postgres, Temporal) are
        reachable. Intended for load balancer / orchestrator readiness probes;
        unlike /healthz, this checks dependencies on every call rather than
        just confirming the process is up.
      security: []
      responses:
        "200":
          description: Postgres and Temporal are both reachable.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReadyResponse"
        "503":
          description: One or more dependencies are unreachable; see `checks`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReadyResponse"

  # ---------------------------------------------------------------------------
  # Public artifact content (HMAC-signed URL)
  # ---------------------------------------------------------------------------
  /artifacts/{artifactID}/content:
    get:
      operationId: getArtifactContent
      tags: [Artifacts]
      summary: Download artifact content
      description: >
        Streams artifact binary content. Requires a valid HMAC-signed URL
        obtained from the authenticated download endpoint. A 30-second grace
        period is allowed past the expiry timestamp.
      security:
        - artifactSignature: []
      parameters:
        - $ref: "#/components/parameters/ArtifactID"
        - name: expires
          in: query
          required: true
          schema:
            type: integer
            format: int64
          description: Unix timestamp when the signed URL expires
        - name: signature
          in: query
          required: true
          schema:
            type: string
          description: HMAC-SHA256 signature
      responses:
        "200":
          description: Artifact binary content
          headers:
            Content-Type:
              schema:
                type: string
            Content-Disposition:
              schema:
                type: string
            Content-Security-Policy:
              schema:
                type: string
                example: "sandbox; default-src 'none'"
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  # ---------------------------------------------------------------------------
  # Hosted integration
  # ---------------------------------------------------------------------------
  /v1/integrations/hosted-runs/{runID}/events:
    post:
      operationId: ingestHostedRunEvent
      tags: [HostedRuns]
      summary: Ingest hosted run event
      description: >
        Webhook-style endpoint for externally hosted agents to report execution
        events. Authenticated via an HMAC-signed callback token (not JWT).
      security:
        - hostedRunToken: []
      parameters:
        - $ref: "#/components/parameters/RunID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/HostedRunEvent"
      responses:
        "202":
          description: Event accepted
          content:
            application/json:
              schema:
                type: object
                required: [accepted]
                properties:
                  accepted:
                    type: boolean
                    example: true
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"

  # ---------------------------------------------------------------------------
  # Billing webhooks
  # ---------------------------------------------------------------------------
  /v1/dodo/webhooks:
    post:
      operationId: processDodoWebhook
      tags: [Billing]
      summary: Process a Dodo webhook
      description: >
        Accepts signed Dodo billing webhooks and applies subscription
        entitlement changes idempotently. The signature timestamp must be
        within the server tolerance window.
      security: []
      parameters:
        - name: webhook-id
          in: header
          required: true
          schema:
            type: string
        - name: webhook-timestamp
          in: header
          required: true
          schema:
            type: string
        - name: webhook-signature
          in: header
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
      responses:
        "202":
          description: Webhook accepted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProcessDodoWebhookResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "413":
          description: Request body exceeds 1 MiB
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"
        "500":
          $ref: "#/components/responses/InternalError"

  # ---------------------------------------------------------------------------
  # Session
  # ---------------------------------------------------------------------------
  /v1/auth/session:
    get:
      operationId: getSession
      tags: [Session]
      summary: Get current session
      description: Returns the authenticated user's identity and workspace memberships.
      security:
        - bearerJWT: []
      responses:
        "200":
          description: Current user session
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionResponse"
        "401":
          $ref: "#/components/responses/UnauthorizedError"

  # ---------------------------------------------------------------------------
  # CLI Auth
  # ---------------------------------------------------------------------------
  /v1/cli-auth/device:
    post:
      operationId: createCliDeviceCode
      tags: [CLIAuth]
      summary: Start a CLI verification login
      security: []
      description: >
        Creates a short-lived device verification code for `agentclash auth login`.
        The response includes a browser-safe verification URL and the user-facing code.
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EmptyObject"
      responses:
        "201":
          description: Device verification created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreateDeviceCodeResponse"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/cli-auth/device/token:
    post:
      operationId: pollCliDeviceToken
      tags: [CLIAuth]
      summary: Poll a pending CLI verification login
      security: []
      description: >
        Polls the device verification flow until the browser approval has issued a CLI token.
        Returns `authorization_pending`, `access_denied`, or `expired_token` errors while polling.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PollDeviceTokenRequest"
      responses:
        "200":
          description: CLI token issued
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PollDeviceTokenResponse"
        "400":
          description: Polling state or invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/cli-auth/device/approve:
    post:
      operationId: approveCliDeviceCode
      tags: [CLIAuth]
      summary: Approve a pending CLI verification login
      description: >
        Approves a pending CLI login from the authenticated web session and issues
        the corresponding CLI token for the waiting terminal.
      security:
        - bearerJWT: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ApproveDeviceCodeRequest"
      responses:
        "200":
          description: Device verification approved
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DeviceApprovalResponse"
        "400":
          description: Invalid or expired verification code
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"

  /v1/cli-auth/device/deny:
    post:
      operationId: denyCliDeviceCode
      tags: [CLIAuth]
      summary: Deny a pending CLI verification login
      description: >
        Denies a pending CLI login from the authenticated web session. The
        waiting terminal receives an `access_denied` polling response.
      security:
        - bearerJWT: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DenyDeviceCodeRequest"
      responses:
        "200":
          description: Device verification denied
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DeviceApprovalResponse"
        "400":
          description: Invalid or expired verification code
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"

  /v1/cli-auth/tokens:
    get:
      operationId: listCliTokens
      tags: [CLIAuth]
      summary: List CLI tokens
      description: Lists active CLI tokens owned by the authenticated user.
      security:
        - bearerJWT: []
      responses:
        "200":
          description: Active CLI tokens
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListCLITokensResponse"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "500":
          $ref: "#/components/responses/InternalError"
    post:
      operationId: createCliToken
      tags: [CLIAuth]
      summary: Create a CLI token
      description: Creates a long-lived CLI token and returns the raw token exactly once.
      security:
        - bearerJWT: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateCLITokenRequest"
      responses:
        "201":
          description: CLI token created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreateCLITokenResponse"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/cli-auth/tokens/{id}:
    delete:
      operationId: revokeCliToken
      tags: [CLIAuth]
      summary: Revoke a CLI token
      security:
        - bearerJWT: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "204":
          description: CLI token revoked
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  # ---------------------------------------------------------------------------
  # Workspace
  # ---------------------------------------------------------------------------
  /v1/workspaces/{workspaceID}/auth-check:
    get:
      operationId: checkWorkspaceAccess
      tags: [Workspace]
      summary: Verify workspace access
      description: Returns success if the caller has access to the specified workspace.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      responses:
        "200":
          description: Workspace access confirmed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WorkspaceAccessCheckResponse"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"

  /v1/workspaces/{workspaceID}/ci-profiles:
    get:
      operationId: listCIProfiles
      tags: [GitHubIntegrations]
      summary: List saved CI profiles
      description: Lists reusable AgentClash CI setup profiles for a workspace.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      responses:
        "200":
          description: Saved CI profiles
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CIProfilesResponse"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
    post:
      operationId: createCIProfile
      tags: [GitHubIntegrations]
      summary: Create a saved CI profile
      description: Saves a complete CI setup profile for later reuse in the workspace UI.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SaveCIProfileRequest"
      responses:
        "201":
          description: CI profile created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CIProfile"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "409":
          $ref: "#/components/responses/ConflictError"

  /v1/workspaces/{workspaceID}/ci-profiles/{profileID}:
    patch:
      operationId: updateCIProfile
      tags: [GitHubIntegrations]
      summary: Update a saved CI profile
      description: Replaces the saved CI setup profile snapshot with the supplied values.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/ProfileID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SaveCIProfileRequest"
      responses:
        "200":
          description: CI profile updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CIProfile"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "409":
          $ref: "#/components/responses/ConflictError"

  /v1/workspaces/{workspaceID}/github/ci-setup-pull-request:
    post:
      operationId: createCISetupPullRequest
      tags: [GitHubIntegrations]
      summary: Open a GitHub setup PR for AgentClash CI
      description: >
        Creates a draft pull request containing generated AgentClash CI files.
        If target files already exist and overwrite_existing is false, returns a
        conflict preflight response without creating a pull request.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateCISetupPullRequestRequest"
      responses:
        "201":
          description: Setup pull request created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreateCISetupPullRequestResponse"
        "200":
          description: Conflict preflight completed without creating a pull request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreateCISetupPullRequestResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "503":
          $ref: "#/components/responses/InternalError"

  # ---------------------------------------------------------------------------
  # Billing
  # ---------------------------------------------------------------------------
  /v1/billing/plans:
    get:
      operationId: listBillingPlans
      tags: [Billing]
      summary: List billing plans
      description: Returns the public billing plan catalog. Dodo product mappings are present only when paid checkout is configured.
      security:
        - bearerJWT: []
      responses:
        "200":
          description: Billing plans
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListBillingPlansResponse"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/workspaces/{workspaceID}/entitlements:
    get:
      operationId: getWorkspaceEntitlements
      tags: [Billing]
      summary: Get workspace billing entitlements
      description: >
        Returns the workspace organization entitlements, current usage window,
        and the current run gate decision.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      responses:
        "200":
          description: Workspace entitlement snapshot
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WorkspaceEntitlementsResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/workspaces/{workspaceID}/quota:
    get:
      operationId: getWorkspaceQuota
      tags: [Billing]
      summary: Get workspace quota usage
      description: >
        Returns the workspace plan and quota counters for monthly race usage,
        active concurrent race usage, and organization seats.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      responses:
        "200":
          description: Workspace quota snapshot
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WorkspaceQuotaResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "500":
          $ref: "#/components/responses/InternalError"

  # ---------------------------------------------------------------------------
  # User Profile
  # ---------------------------------------------------------------------------
  /v1/users/me:
    get:
      operationId: getUserProfile
      tags: [UserProfile]
      summary: Get current user profile
      description: >
        Returns the authenticated user's profile with full org/workspace
        hierarchy including roles at each level.
      security:
        - bearerJWT: []
      responses:
        "200":
          description: User profile
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserProfileResponse"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "500":
          $ref: "#/components/responses/InternalError"

  # ---------------------------------------------------------------------------
  # Onboarding
  # ---------------------------------------------------------------------------
  /v1/onboarding:
    post:
      operationId: onboard
      tags: [Onboarding]
      summary: First-time onboarding
      description: >
        Creates a new organization and workspace for a first-time user.
        Fails if the user already has an organization.
      security:
        - bearerJWT: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/OnboardRequest"
      responses:
        "201":
          description: Onboarding complete
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OnboardResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "409":
          $ref: "#/components/responses/ConflictError"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"
        "500":
          $ref: "#/components/responses/InternalError"

  # ---------------------------------------------------------------------------
  # Organizations
  # ---------------------------------------------------------------------------
  /v1/organizations:
    get:
      operationId: listOrganizations
      tags: [Organizations]
      summary: List organizations
      description: Lists organizations the caller belongs to.
      security:
        - bearerJWT: []
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            format: int32
            default: 50
            minimum: 1
            maximum: 100
        - name: offset
          in: query
          schema:
            type: integer
            format: int32
            default: 0
            minimum: 0
      responses:
        "200":
          description: Paginated list of organizations
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListOrganizationsResponse"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "500":
          $ref: "#/components/responses/InternalError"
    post:
      operationId: createOrganization
      tags: [Organizations]
      summary: Create an organization
      security:
        - bearerJWT: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateOrganizationRequest"
      responses:
        "201":
          description: Organization created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrganizationResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "409":
          $ref: "#/components/responses/ConflictError"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/organizations/{organizationID}:
    get:
      operationId: getOrganization
      tags: [Organizations]
      summary: Get an organization
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/OrganizationID"
      responses:
        "200":
          description: Organization details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrganizationResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
    patch:
      operationId: updateOrganization
      tags: [Organizations]
      summary: Update an organization
      description: Requires org_admin role. Setting status to archived cascades to all workspaces.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/OrganizationID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateOrganizationRequest"
      responses:
        "200":
          description: Organization updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrganizationResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "409":
          $ref: "#/components/responses/ConflictError"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"

  /v1/organizations/{organizationID}/billing:
    get:
      operationId: getOrganizationBilling
      tags: [Billing]
      summary: Get organization billing
      description: Requires org_admin role.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/OrganizationID"
      responses:
        "200":
          description: Organization billing overview
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BillingOverviewResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/organizations/{organizationID}/billing/trial:
    post:
      operationId: startBillingTrial
      tags: [Billing]
      summary: Start a 45-day billing trial
      description: Requires org_admin role. Only Free organizations can start one self-serve trial per organization.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/OrganizationID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StartBillingTrialRequest"
      responses:
        "201":
          description: Billing trial started
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BillingOverviewResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/organizations/{organizationID}/billing/checkout:
    post:
      operationId: createBillingCheckout
      tags: [Billing]
      summary: Create a billing checkout
      description: Requires org_admin role.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/OrganizationID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateBillingCheckoutRequest"
      responses:
        "201":
          description: Billing checkout created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreateBillingCheckoutResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "409":
          $ref: "#/components/responses/ConflictError"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/organizations/{organizationID}/billing/portal:
    post:
      operationId: createBillingPortal
      tags: [Billing]
      summary: Create a billing portal link
      description: Requires org_admin role and an existing Dodo customer for the organization.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/OrganizationID"
      responses:
        "200":
          description: Billing portal link
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreateBillingPortalResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/organizations/{organizationID}/workspaces:
    get:
      operationId: listOrganizationWorkspaces
      tags: [Organizations]
      summary: List workspaces in an organization
      description: org_admin sees all workspaces; org_member sees only their own.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/OrganizationID"
        - name: limit
          in: query
          schema:
            type: integer
            format: int32
            default: 50
            minimum: 1
            maximum: 100
        - name: offset
          in: query
          schema:
            type: integer
            format: int32
            default: 0
            minimum: 0
      responses:
        "200":
          description: Paginated list of workspaces
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListWorkspacesResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
    post:
      operationId: createWorkspace
      tags: [Organizations]
      summary: Create a workspace in an organization
      description: Requires org_admin role.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/OrganizationID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateWorkspaceRequest"
      responses:
        "201":
          description: Workspace created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WorkspaceDetailResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "409":
          $ref: "#/components/responses/ConflictError"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"

  # ---------------------------------------------------------------------------
  # Organization Memberships
  # ---------------------------------------------------------------------------
  /v1/organizations/{organizationID}/memberships:
    get:
      operationId: listOrgMemberships
      tags: [OrgMemberships]
      summary: List organization memberships
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/OrganizationID"
        - name: limit
          in: query
          schema:
            type: integer
            format: int32
            default: 50
            minimum: 1
            maximum: 100
        - name: offset
          in: query
          schema:
            type: integer
            format: int32
            default: 0
            minimum: 0
      responses:
        "200":
          description: Paginated list of memberships
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListOrgMembershipsResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
    post:
      operationId: inviteOrgMember
      tags: [OrgMemberships]
      summary: Invite a member to an organization
      description: Requires org_admin role.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/OrganizationID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InviteOrgMemberRequest"
      responses:
        "201":
          description: Member invited. Pending invitations include accept_url for copy-link fallback.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrgMembershipResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "409":
          $ref: "#/components/responses/ConflictError"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"

  /v1/organization-memberships/{membershipID}:
    patch:
      operationId: updateOrgMembership
      tags: [OrgMemberships]
      summary: Update an organization membership
      description: >
        Org admins can change role or status. Invited users can accept
        (transition invited to active). Invite expires after 7 days.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/MembershipID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateOrgMembershipRequest"
      responses:
        "200":
          description: Membership updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrgMembershipResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "409":
          $ref: "#/components/responses/ConflictError"
        "410":
          $ref: "#/components/responses/GoneError"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"

  # ---------------------------------------------------------------------------
  # Workspace Details
  # ---------------------------------------------------------------------------
  /v1/workspaces/{workspaceID}/details:
    get:
      operationId: getWorkspace
      tags: [Workspaces]
      summary: Get workspace details
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      responses:
        "200":
          description: Workspace details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WorkspaceDetailResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
    patch:
      operationId: updateWorkspace
      tags: [Workspaces]
      summary: Update workspace details
      description: Requires workspace_admin or org_admin. Setting status to archived cascades.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateWorkspaceRequest"
      responses:
        "200":
          description: Workspace updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WorkspaceDetailResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "409":
          $ref: "#/components/responses/ConflictError"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"

  # ---------------------------------------------------------------------------
  # Workspace Memberships
  # ---------------------------------------------------------------------------
  /v1/workspaces/{workspaceID}/memberships:
    get:
      operationId: listWorkspaceMemberships
      tags: [WorkspaceMemberships]
      summary: List workspace memberships
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - name: limit
          in: query
          schema:
            type: integer
            format: int32
            default: 50
            minimum: 1
            maximum: 100
        - name: offset
          in: query
          schema:
            type: integer
            format: int32
            default: 0
            minimum: 0
      responses:
        "200":
          description: Paginated list of memberships
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListWorkspaceMembershipsResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
    post:
      operationId: inviteWorkspaceMember
      tags: [WorkspaceMemberships]
      summary: Invite a member to a workspace
      description: Requires workspace_admin or org_admin. User must have active org membership first.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InviteWorkspaceMemberRequest"
      responses:
        "201":
          description: Member invited. Pending invitations include accept_url for copy-link fallback.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WorkspaceMembershipDetailResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "409":
          $ref: "#/components/responses/ConflictError"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"

  /v1/workspace-memberships/{membershipID}:
    patch:
      operationId: updateWorkspaceMembership
      tags: [WorkspaceMemberships]
      summary: Update a workspace membership
      description: >
        Workspace admins or org admins can change role or status. Invited
        users can accept (transition invited to active).
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/MembershipID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateWorkspaceMembershipRequest"
      responses:
        "200":
          description: Membership updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WorkspaceMembershipDetailResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "409":
          $ref: "#/components/responses/ConflictError"
        "410":
          $ref: "#/components/responses/GoneError"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"

  # ---------------------------------------------------------------------------
  # Workspace Secrets
  # ---------------------------------------------------------------------------
  /v1/workspaces/{workspaceID}/secrets:
    get:
      operationId: listWorkspaceSecrets
      tags: [WorkspaceSecrets]
      summary: List workspace secret keys
      description: Returns secret metadata only — values are never exposed.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      responses:
        "200":
          description: List of secret keys
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListWorkspaceSecretsResponse"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/workspaces/{workspaceID}/secrets/{secretKey}:
    put:
      operationId: upsertWorkspaceSecret
      tags: [WorkspaceSecrets]
      summary: Create or update a workspace secret
      description: Maximum value size is 64 KiB.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/SecretKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpsertWorkspaceSecretRequest"
      responses:
        "204":
          description: Secret created or updated
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "500":
          $ref: "#/components/responses/InternalError"
    delete:
      operationId: deleteWorkspaceSecret
      tags: [WorkspaceSecrets]
      summary: Delete a workspace secret
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/SecretKey"
      responses:
        "204":
          description: Secret deleted
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  # ---------------------------------------------------------------------------
  # Runtime Profiles
  # ---------------------------------------------------------------------------
  /v1/workspaces/{workspaceID}/runtime-profiles:
    post:
      operationId: createRuntimeProfile
      tags: [RuntimeProfiles]
      summary: Create a runtime profile
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateRuntimeProfileRequest"
      responses:
        "201":
          description: Runtime profile created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RuntimeProfileResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "409":
          $ref: "#/components/responses/ConflictError"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"
    get:
      operationId: listRuntimeProfiles
      tags: [RuntimeProfiles]
      summary: List runtime profiles in a workspace
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      responses:
        "200":
          description: List of runtime profiles
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListRuntimeProfilesResponse"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"

  /v1/runtime-profiles/{profileID}:
    get:
      operationId: getRuntimeProfile
      tags: [RuntimeProfiles]
      summary: Get a runtime profile
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/ProfileID"
      responses:
        "200":
          description: Runtime profile details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RuntimeProfileResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/runtime-profiles/{profileID}/archive:
    post:
      operationId: archiveRuntimeProfile
      tags: [RuntimeProfiles]
      summary: Archive a runtime profile
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/ProfileID"
      responses:
        "204":
          description: Runtime profile archived
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  # ---------------------------------------------------------------------------
  # Provider Accounts
  # ---------------------------------------------------------------------------
  /v1/workspaces/{workspaceID}/provider-accounts:
    post:
      operationId: createProviderAccount
      tags: [ProviderAccounts]
      summary: Create a provider account
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateProviderAccountRequest"
      responses:
        "201":
          description: Provider account created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProviderAccountResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "409":
          $ref: "#/components/responses/ConflictError"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"
    get:
      operationId: listProviderAccounts
      tags: [ProviderAccounts]
      summary: List provider accounts in a workspace
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      responses:
        "200":
          description: List of provider accounts
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListProviderAccountsResponse"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"

  /v1/provider-accounts/{accountID}:
    get:
      operationId: getProviderAccount
      tags: [ProviderAccounts]
      summary: Get a provider account
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/AccountID"
      responses:
        "200":
          description: Provider account details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProviderAccountResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
    delete:
      operationId: deleteProviderAccount
      tags: [ProviderAccounts]
      summary: Delete (archive) a provider account
      description: Soft-deletes the provider account by setting its status to archived.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/AccountID"
      responses:
        "204":
          description: Provider account deleted
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/provider-accounts/{accountID}/test:
    post:
      operationId: testProviderAccount
      tags: [ProviderAccounts]
      summary: Test a provider account
      description: >
        Sends a minimal prompt through the provider account. A model is required
        for custom providers and optional for providers with a configured default.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/AccountID"
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProviderAccountTestRequest"
      responses:
        "200":
          description: Provider account test result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProviderAccountTestResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"

  /v1/provider-accounts/{accountID}/models:
    get:
      operationId: listProviderAccountModels
      tags: [ProviderAccounts]
      summary: List models available to a provider connection
      description: >
        Returns the live model list reachable with the connection's credential,
        fetched from the provider's API and cached briefly. Pricing is for picker
        display only and is not used for scoring.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/AccountID"
      responses:
        "200":
          description: Models available to the connection
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProviderConnectionModelsResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "502":
          description: The provider returned an error while listing models

  # ---------------------------------------------------------------------------
  # Tools
  # ---------------------------------------------------------------------------
  /v1/workspaces/{workspaceID}/tools:
    post:
      operationId: createTool
      tags: [Tools]
      summary: Create a tool definition
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateToolRequest"
      responses:
        "201":
          description: Tool created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ToolResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "409":
          $ref: "#/components/responses/ConflictError"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"
    get:
      operationId: listTools
      tags: [Tools]
      summary: List tools in a workspace
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      responses:
        "200":
          description: List of tools
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListToolsResponse"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"

  /v1/workspaces/{workspaceID}/tools/from-library:
    post:
      operationId: createToolsFromLibrary
      tags: [Tools]
      summary: Add tools from the library into a workspace
      description: >-
        Instantiate one or more catalog entries (by slug) as real, editable
        workspace tools. The server owns the definitions; the client sends only
        slugs and optional variant/conflict choices. Returns the created tools
        and any that were skipped (e.g. already added).
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateToolsFromLibraryRequest"
      responses:
        "201":
          description: Tools added from the library
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreateToolsFromLibraryResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"

  /v1/tool-library:
    get:
      operationId: listToolLibrary
      tags: [Tools]
      summary: List the prebuilt tool library
      description: >-
        Returns the global, read-only catalog of ready-made tools. Filter by
        ?category=. Instantiate entries with POST
        /v1/workspaces/{workspaceID}/tools/from-library. Not workspace-scoped.
      security:
        - bearerJWT: []
      parameters:
        - name: category
          in: query
          required: false
          schema:
            type: string
          description: Optional category filter (see the response's categories list).
      responses:
        "200":
          description: Tool library catalog
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListToolLibraryResponse"
        "401":
          $ref: "#/components/responses/UnauthorizedError"

  /v1/tool-primitives:
    get:
      operationId: listToolPrimitives
      tags: [Tools]
      summary: List base primitive tools available as building blocks
      description: >-
        Returns the static catalog of base primitives that primitive tools can
        delegate to and composed tools can chain. Read-only and not workspace-scoped.
      security:
        - bearerJWT: []
      responses:
        "200":
          description: Primitive catalog
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListToolPrimitivesResponse"
        "401":
          $ref: "#/components/responses/UnauthorizedError"

  /v1/tools/{toolID}:
    get:
      operationId: getTool
      tags: [Tools]
      summary: Get a tool definition
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/ToolID"
      responses:
        "200":
          description: Tool details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ToolResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
    patch:
      operationId: updateTool
      tags: [Tools]
      summary: Update a tool definition
      description: >-
        Updates the mutable fields of a tool. The slug and tool_kind are
        immutable so composed tools that reference this tool by slug keep
        resolving. The definition is validated before it is persisted.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/ToolID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateToolRequest"
      responses:
        "200":
          description: Tool updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ToolResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"
    delete:
      operationId: deleteTool
      tags: [Tools]
      summary: Archive a tool definition
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/ToolID"
      responses:
        "204":
          description: Tool archived
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  # ---------------------------------------------------------------------------
  # Routing Policies
  # ---------------------------------------------------------------------------
  /v1/workspaces/{workspaceID}/routing-policies:
    post:
      operationId: createRoutingPolicy
      tags: [Policies]
      summary: Create a routing policy
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateRoutingPolicyRequest"
      responses:
        "201":
          description: Routing policy created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RoutingPolicyResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "409":
          $ref: "#/components/responses/ConflictError"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"
    get:
      operationId: listRoutingPolicies
      tags: [Policies]
      summary: List routing policies in a workspace
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      responses:
        "200":
          description: List of routing policies
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListRoutingPoliciesResponse"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"

  # ---------------------------------------------------------------------------
  # Spend Policies
  # ---------------------------------------------------------------------------
  /v1/workspaces/{workspaceID}/spend-policies:
    post:
      operationId: createSpendPolicy
      tags: [Policies]
      summary: Create a spend policy
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateSpendPolicyRequest"
      responses:
        "201":
          description: Spend policy created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SpendPolicyResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "409":
          $ref: "#/components/responses/ConflictError"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"
    get:
      operationId: listSpendPolicies
      tags: [Policies]
      summary: List spend policies in a workspace
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      responses:
        "200":
          description: List of spend policies
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListSpendPoliciesResponse"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"

  # ---------------------------------------------------------------------------
  # Artifacts (authenticated)
  # ---------------------------------------------------------------------------
  /v1/artifacts/{artifactID}/download:
    get:
      operationId: getArtifactDownload
      tags: [Artifacts]
      summary: Get signed download URL
      description: >
        Returns a time-limited signed URL that can be used to download the
        artifact content from the public content endpoint.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/ArtifactID"
      responses:
        "200":
          description: Signed download URL
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ArtifactDownloadResponse"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/workspaces/{workspaceID}/artifacts:
    get:
      operationId: listWorkspaceArtifacts
      tags: [Artifacts]
      summary: List workspace artifacts
      description: >
        Returns one page of the workspace's active artifacts, ordered by
        creation date descending, with the total count for pagination.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - name: limit
          in: query
          description: Page size (server default 20, capped at 100).
          schema:
            type: integer
            format: int32
            default: 20
            minimum: 1
            maximum: 100
        - name: offset
          in: query
          description: Number of items to skip before the first result.
          schema:
            type: integer
            format: int32
            default: 0
            minimum: 0
      responses:
        "200":
          description: List of artifacts
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListArtifactsResponse"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
    post:
      operationId: uploadArtifact
      tags: [Artifacts]
      summary: Upload an artifact
      description: >
        Uploads a file artifact associated with a workspace, run, or run-agent.
        Blocked MIME types: application/javascript, application/xhtml+xml,
        image/svg+xml, text/html, text/javascript.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file, artifact_type]
              properties:
                file:
                  type: string
                  format: binary
                  description: The file to upload
                artifact_type:
                  type: string
                  pattern: "^[a-z0-9][a-z0-9._-]{0,63}$"
                  description: Artifact type identifier
                run_id:
                  type: string
                  format: uuid
                  description: Associated run ID
                run_agent_id:
                  type: string
                  format: uuid
                  description: Associated run-agent ID
                metadata:
                  type: string
                  description: JSON object as string with additional metadata
      responses:
        "201":
          description: Artifact uploaded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ArtifactUploadResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "413":
          description: Artifact exceeds maximum upload size
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"

  # ---------------------------------------------------------------------------
  # Runs
  # ---------------------------------------------------------------------------
  /v1/runs:
    post:
      operationId: createRun
      tags: [Runs]
      summary: Create a new run
      description: >
        Creates a new evaluation run pitting agent deployments against a
        challenge pack version. Starts a Temporal workflow on success.
      security:
        - bearerJWT: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateRunRequest"
      responses:
        "201":
          description: Run created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreateRunResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "413":
          description: Request body exceeds 1 MiB
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"
        "502":
          description: Run created but workflow failed to start
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreateRunErrorResponse"

  /v1/eval-sets/expand:
    post:
      operationId: expandEvalSet
      tags: [EvalSets]
      summary: Dry-run expand an eval-set manifest
      description: >
        Expands an `evalset/v1` manifest into the full packs × agents × models ×
        repeats combination list with deterministic `matrix_key`s. Does not create
        sessions or runs. Rejects manifests that exceed the combination cap
        (default 2000).
      security:
        - bearerJWT: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ExpandEvalSetRequest"
      responses:
        "200":
          description: Expansion report
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EvalSetExpansionReport"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"

  /v1/eval-sets:
    get:
      operationId: listEvalSets
      tags: [EvalSets]
      summary: List eval sets for a workspace
      security:
        - bearerJWT: []
      parameters:
        - in: query
          name: workspace_id
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Eval sets for the workspace
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListEvalSetsResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
    post:
      operationId: createEvalSet
      tags: [EvalSets]
      summary: Create and start an eval set from a manifest
      description: >
        Expands the manifest, creates one eval session per pack (with per-combination
        `run_matrix` entries), and starts `EvalSetWorkflow`. Pack and agent refs must
        be UUIDs (`challenge_pack_version_id` / `agent_deployment_id`); catalog slug
        resolution is CLI-side.
      security:
        - bearerJWT: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateEvalSetRequest"
      responses:
        "201":
          description: Eval set created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreateEvalSetResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"

  /v1/eval-sets/{evalSetID}:
    get:
      operationId: getEvalSet
      tags: [EvalSets]
      summary: Get eval set status and roll-up
      security:
        - bearerJWT: []
      parameters:
        - in: path
          name: evalSetID
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Eval set detail
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GetEvalSetResponse"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/eval-sets/{evalSetID}/cancel:
    post:
      operationId: cancelEvalSet
      tags: [EvalSets]
      summary: Cancel an in-flight eval set
      description: >
        Cancels the parent `EvalSetWorkflow`, which request-cancels child
        `EvalSessionWorkflow`s, and settles the eval set to `cancelled`.
      security:
        - bearerJWT: []
      parameters:
        - in: path
          name: evalSetID
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Eval set after cancel
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EvalSet"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/workspaces/{workspaceID}/emergency-stop:
    post:
      operationId: emergencyStopWorkspace
      tags: [EvalSets]
      summary: Cancel every in-flight eval set in a workspace
      description: >
        Admin-only kill switch. Cancels all queued/expanding/running/aggregating
        eval sets in the workspace and audit-logs the request. Partial results
        from cancelled sets are retained.
      security:
        - bearerJWT: []
      parameters:
        - in: path
          name: workspaceID
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Emergency stop summary
          content:
            application/json:
              schema:
                type: object
                required: [workspace_id, cancelled_sets, eval_set_ids, audit_event]
                properties:
                  workspace_id:
                    type: string
                    format: uuid
                  cancelled_sets:
                    type: integer
                  eval_set_ids:
                    type: array
                    items:
                      type: string
                      format: uuid
                  requested_at:
                    type: string
                    format: date-time
                  requested_by:
                    type: string
                    format: uuid
                  audit_event:
                    type: string
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"

  /v1/eval-sets/{evalSetID}/search:
    get:
      operationId: searchEvalSetCases
      tags: [EvalSets]
      summary: Trigram search over case_results transcripts
      security:
        - bearerJWT: []
      parameters:
        - in: path
          name: evalSetID
          required: true
          schema: { type: string, format: uuid }
        - in: query
          name: q
          required: true
          schema: { type: string }
        - in: query
          name: min_score
          schema: { type: number }
        - in: query
          name: max_score
          schema: { type: number }
        - in: query
          name: agent
          schema: { type: string, format: uuid }
        - in: query
          name: pack
          schema: { type: string }
        - in: query
          name: verdict
          schema: { type: string }
      responses:
        "200":
          description: Matching case rows with snippets
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/eval-sets/{evalSetID}/cases:
    get:
      operationId: listEvalSetCases
      tags: [EvalSets]
      summary: Paginated case_results drill-down
      security:
        - bearerJWT: []
      parameters:
        - in: path
          name: evalSetID
          required: true
          schema: { type: string, format: uuid }
        - in: query
          name: cursor
          schema: { type: string, format: uuid }
        - in: query
          name: limit
          schema: { type: integer, minimum: 1, maximum: 500, default: 50 }
      responses:
        "200":
          description: Case rows
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/eval-sets/{evalSetID}/report:
    get:
      operationId: reportEvalSet
      tags: [EvalSets]
      summary: Set-level marginals, win matrix, score distributions
      security:
        - bearerJWT: []
      parameters:
        - in: path
          name: evalSetID
          required: true
          schema: { type: string, format: uuid }
      responses:
        "200":
          description: Report roll-up from case_results
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/eval-sets/{evalSetID}/scan:
    post:
      operationId: scanEvalSet
      tags: [EvalSets]
      summary: Start post-hoc scanner workflow for an eval set
      description: >
        Starts `ScanEvalSetWorkflow` on the background task queue. Body may list
        built-in scanner names; when omitted, scanners are taken from the set
        manifest `scanners:` field.
      security:
        - bearerJWT: []
      parameters:
        - in: path
          name: evalSetID
          required: true
          schema: { type: string, format: uuid }
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ScanEvalSetRequest"
      responses:
        "202":
          description: Scan workflow accepted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ScanEvalSetResponse"
        "400":
          description: Malformed JSON body or invalid scan request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "409":
          $ref: "#/components/responses/ConflictError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/eval-sets/{evalSetID}/findings:
    get:
      operationId: listEvalSetFindings
      tags: [EvalSets]
      summary: List scanner findings for an eval set
      security:
        - bearerJWT: []
      parameters:
        - in: path
          name: evalSetID
          required: true
          schema: { type: string, format: uuid }
        - in: query
          name: severity
          schema: { type: string, enum: [low, medium, high, critical] }
        - in: query
          name: status
          schema: { type: string, enum: [open, triaged, dismissed] }
        - in: query
          name: limit
          schema: { type: integer, minimum: 1, maximum: 500, default: 100 }
      responses:
        "200":
          description: Findings list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListScanFindingsResponse"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/scan-findings/{findingID}:
    patch:
      operationId: updateScanFindingStatus
      tags: [EvalSets]
      summary: Update finding triage status (audits actor)
      security:
        - bearerJWT: []
      parameters:
        - in: path
          name: findingID
          required: true
          schema: { type: string, format: uuid }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateScanFindingStatusRequest"
      responses:
        "200":
          description: Updated finding
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ScanFinding"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/eval-sets/{evalSetID}/export:
    get:
      operationId: exportEvalSetCases
      tags: [EvalSets]
      summary: Stream case_results as CSV or JSONL
      description: >
        Streams the case_results projection without buffering the full response.
        CSV columns: id, eval_set_id, run_id, run_agent_id, matrix_key, pack_ref,
        case_key, agent_deployment_id, model, score, correctness, verdict, cost_usd,
        duration_ms, failure_class, transcript_text.
      security:
        - bearerJWT: []
      parameters:
        - in: path
          name: evalSetID
          required: true
          schema: { type: string, format: uuid }
        - in: query
          name: format
          schema: { type: string, enum: [csv, jsonl], default: jsonl }
      responses:
        "200":
          description: Streaming export body
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/compare/eval-sets:
    get:
      operationId: compareEvalSets
      tags: [EvalSets]
      summary: Cross-set score deltas and regressions on shared matrix keys
      security:
        - bearerJWT: []
      parameters:
        - in: query
          name: a
          required: true
          schema: { type: string, format: uuid }
        - in: query
          name: b
          required: true
          schema: { type: string, format: uuid }
      responses:
        "200":
          description: Deltas and regressions
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/eval-sessions:
    get:
      operationId: listEvalSessions
      tags: [EvalSessions]
      summary: List eval sessions for a workspace
      description: >
        Lists recent eval sessions for the requested workspace, including child-run
        summary counts and aggregation-readiness warnings.
      security:
        - bearerJWT: []
      parameters:
        - in: query
          name: workspace_id
          required: true
          schema:
            type: string
            format: uuid
        - in: query
          name: limit
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - in: query
          name: offset
          required: false
          schema:
            type: integer
            minimum: 0
            default: 0
      responses:
        "200":
          description: Eval sessions for the workspace
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListEvalSessionsResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
    post:
      operationId: createEvalSession
      tags: [EvalSessions]
      summary: Create a new eval session
      description: >
        Creates an eval session and queues one child run per requested repetition.
        Participants are resolved from agent build versions to active deployments
        in the selected workspace before any runs are created.
      security:
        - bearerJWT: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateEvalSessionRequest"
      responses:
        "201":
          description: Eval session created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreateEvalSessionResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "413":
          description: Request body exceeds 1 MiB
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"
        "422":
          description: Eval session configuration validation failed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EvalSessionValidationEnvelope"

  /v1/eval-sessions/{evalSessionID}:
    get:
      operationId: getEvalSession
      tags: [EvalSessions]
      summary: Get eval session details
      description: >
        Returns the eval session, attached child runs, child-run summary counts,
        and aggregation-readiness warnings for inspection workflows.
      security:
        - bearerJWT: []
      parameters:
        - in: path
          name: evalSessionID
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Eval session details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EvalSessionDetail"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          description: Eval session not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"

  /v1/runs/{runID}:
    get:
      operationId: getRun
      tags: [Runs]
      summary: Get run details
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/RunID"
      responses:
        "200":
          description: Run details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RunDetail"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/runs/{runID}/cancel:
    post:
      operationId: cancelRun
      tags: [Runs]
      summary: Cancel a run
      description: >
        Cancels an active run. If the run has already reached a terminal status,
        the endpoint returns the current run details without changing it.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/RunID"
      responses:
        "200":
          description: Run cancelled or already terminal
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RunDetail"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "502":
          description: Run workflow could not be cancelled
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RunWorkflowErrorResponse"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/runs/{runID}/ranking:
    get:
      operationId: getRunRanking
      tags: [Runs]
      summary: Get run ranking
      description: >
        Returns composite scores and rankings for agents in a run.
        Returns 202 if ranking is still being computed, 409 if generation failed.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/RunID"
        - name: sort_by
          in: query
          schema:
            type: string
            enum: [composite, correctness, reliability, latency, cost]
          description: Dimension to sort rankings by. Defaults to correctness then reliability.
      responses:
        "200":
          description: Ranking ready
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RunRankingResponse"
        "202":
          description: Ranking is still being computed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RunRankingResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "409":
          description: Ranking generation failed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RunRankingResponse"

  /v1/runs/{runID}/ranking-insights:
    post:
      operationId: createRunRankingInsights
      tags: [Runs]
      summary: Generate ranking insights
      description: >
        Generates an advisory summary for a completed comparison run using the
        selected workspace provider account and model alias. The output is
        grounded only in the current run's ranking evidence.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/RunID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateRunRankingInsightsRequest"
      responses:
        "200":
          description: Ranking insights generated successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RunRankingInsightsResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "429":
          description: Insight generation or the selected provider is rate limited
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "502":
          description: Selected provider failed to generate insights
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "503":
          description: Selected provider is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"

  /v1/runs/{runID}/agents:
    get:
      operationId: listRunAgents
      tags: [Runs]
      summary: List agents in a run
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/RunID"
      responses:
        "200":
          description: List of run agents
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListRunAgentsResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/workspaces/{workspaceID}/runs/{runID}/failures:
    get:
      operationId: listRunFailures
      tags: [Runs]
      summary: List failure review items for a run
      description: >
        Returns a normalized, cursor-paginated stream of per-case failure review
        items for the selected run.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/RunID"
        - name: agent_id
          in: query
          description: Limit results to a single run agent.
          schema:
            type: string
            format: uuid
        - name: severity
          in: query
          description: Filter by derived failure severity.
          schema:
            type: string
            enum: [info, warning, blocking]
        - name: failure_class
          in: query
          description: Filter by the inferred failure classification.
          schema:
            $ref: "#/components/schemas/FailureReviewFailureClass"
        - name: evidence_tier
          in: query
          description: Filter by available evidence fidelity.
          schema:
            $ref: "#/components/schemas/FailureReviewEvidenceTier"
        - name: challenge_key
          in: query
          description: Limit results to a single challenge key.
          schema:
            type: string
        - name: case_key
          in: query
          description: Limit results to a single case key.
          schema:
            type: string
        - name: failure_cluster_key
          in: query
          description: Limit results to a single derived failure cluster key.
          schema:
            type: string
        - name: cursor
          in: query
          description: Opaque base64url-encoded pagination cursor returned by a previous response.
          schema:
            type: string
        - name: limit
          in: query
          description: Page size. Defaults to 50 and is capped at 200.
          schema:
            type: integer
            default: 50
            minimum: 1
            maximum: 200
      responses:
        "200":
          description: Paginated list of failure review items
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListRunFailuresResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/workspaces/{workspaceID}/runs/{runID}/failures/{challengeIdentityID}/promote:
    post:
      operationId: promoteFailureReviewItem
      tags: [Runs, Regression Suites]
      summary: Promote a failure review item into a regression case
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/RunID"
        - name: challengeIdentityID
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PromoteFailureInput"
      responses:
        "200":
          description: Existing regression case returned for an idempotent re-promotion
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RegressionCase"
        "201":
          description: Failure promoted into a new regression case
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RegressionCase"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/workspaces/{workspaceID}/runs/{runID}/run-agents/{runAgentID}/turns:
    post:
      operationId: submitMultiTurnHumanTurn
      tags: [Runs, MultiTurn]
      summary: Submit a human user message for an awaiting multi-turn phase
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/RunID"
        - $ref: "#/components/parameters/RunAgentID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SubmitHumanTurnRequest"
      responses:
        "202":
          description: Human message accepted for the active turn
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SubmitHumanTurnResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "409":
          description: Run agent is not awaiting human input
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"

  /v1/workspaces/{workspaceID}/runs/{runID}/run-agents/{runAgentID}/turns/status:
    get:
      operationId: getMultiTurnHumanTurnStatus
      tags: [Runs, MultiTurn]
      summary: Poll whether a run agent is awaiting human input
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/RunID"
        - $ref: "#/components/parameters/RunAgentID"
      responses:
        "200":
          description: Current human-turn gate state
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HumanTurnStatusResponse"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/workspaces/{workspaceID}/calibration-reviews:
    post:
      operationId: createCalibrationReview
      tags: [MultiTurn]
      summary: Record a human calibration score for a multi-turn turn
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CalibrationReviewRequest"
      responses:
        "201":
          description: Calibration review recorded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MultiTurnStatusResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
    get:
      operationId: listCalibrationReviews
      tags: [MultiTurn]
      summary: List recent calibration reviews for a workspace
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      responses:
        "200":
          description: Calibration reviews
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListCalibrationReviewsResponse"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"

  /v1/workspaces/{workspaceID}/arena/tasks:
    get:
      operationId: listArenaTasks
      tags: [MultiTurn]
      summary: List pending pairwise arena comparison tasks
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      responses:
        "200":
          description: Pending arena tasks
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListArenaTasksResponse"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"

  /v1/workspaces/{workspaceID}/arena/votes:
    post:
      operationId: submitArenaVote
      tags: [MultiTurn]
      summary: Submit a human preference vote for an arena task
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ArenaVoteRequest"
      responses:
        "201":
          description: Arena vote recorded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MultiTurnStatusResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"

  /v1/runs/{runID}/events/stream:
    get:
      operationId: streamRunEvents
      tags: [Runs]
      summary: Stream live run events via SSE
      security: []
      description: >
        Server-Sent Events endpoint for live run event streaming. Authentication
        is via the Authorization header, with a `token` query-parameter fallback
        for EventSource clients. Returns a stream of run-event envelopes.
        Oversized payloads may be persisted as claim-check stubs
        (`{"$ref","bytes","type"}`); the API hydrates stubs to the original JSON
        before emitting SSE frames (including Last-Event-ID replay from the DB
        and live Redis frames). When Redis is unavailable the handler falls back
        to DB polling.
      parameters:
        - $ref: "#/components/parameters/RunID"
        - name: token
          in: query
          required: true
          description: Bearer access token (required because EventSource cannot set Authorization header)
          schema:
            type: string
      responses:
        "200":
          description: SSE event stream
          content:
            text/event-stream:
              schema:
                type: string
                description: >
                  Stream of SSE frames. Each event has `event: run_event`,
                  `id: <sequence_number>`, and `data: <JSON Envelope>`.
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "503":
          description: Real-time streaming not configured (Redis unavailable)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"

  /v1/runs/{runID}/events/export:
    get:
      operationId: exportRunEventsJSONL
      tags: [Runs]
      summary: Export run events as JSONL
      description: >
        Exports the full persisted run-agent event stream for a run as JSONL.
        Events are ordered by occurrence time, run-agent lane, per-agent
        sequence number, and stable database id so trace packs are deterministic.
        Offloaded payload stubs are hydrated so the export is byte-equivalent to
        the non-offloaded (inline) case.
      parameters:
        - $ref: "#/components/parameters/RunID"
      responses:
        "200":
          description: Ordered run-event JSONL export
          headers:
            Content-Disposition:
              description: Suggested trace-pack filename.
              schema:
                type: string
          content:
            application/x-ndjson:
              schema:
                type: string
                description: >
                  One JSON run-event envelope per line. Each envelope includes
                  event_id, schema_version, run_id, run_agent_id,
                  sequence_number, event_type, source, occurred_at, and payload.
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/workspaces/{workspaceID}/runs:
    get:
      operationId: listRuns
      tags: [Runs]
      summary: List runs in a workspace
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - name: limit
          in: query
          schema:
            type: integer
            format: int32
            default: 20
            minimum: 1
            maximum: 100
        - name: offset
          in: query
          schema:
            type: integer
            format: int32
            default: 0
            minimum: 0
      responses:
        "200":
          description: Paginated list of runs
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListRunsResponse"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"

  /v1/workspaces/{workspaceID}/regression-suites:
    post:
      operationId: createRegressionSuite
      tags: [Regressions]
      summary: Create a regression suite
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateRegressionSuiteInput"
      responses:
        "201":
          description: Regression suite created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RegressionSuite"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "409":
          $ref: "#/components/responses/ConflictError"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"
    get:
      operationId: listRegressionSuites
      tags: [Regressions]
      summary: List regression suites in a workspace
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - name: limit
          in: query
          schema:
            type: integer
            format: int32
            default: 50
            minimum: 1
            maximum: 100
        - name: offset
          in: query
          schema:
            type: integer
            format: int32
            default: 0
            minimum: 0
      responses:
        "200":
          description: Paginated regression suites
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListRegressionSuitesResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"

  /v1/workspaces/{workspaceID}/regression-suites/{suiteID}:
    get:
      operationId: getRegressionSuite
      tags: [Regressions]
      summary: Get a regression suite
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/RegressionSuiteID"
      responses:
        "200":
          description: Regression suite detail
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RegressionSuite"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
    patch:
      operationId: patchRegressionSuite
      tags: [Regressions]
      summary: Patch a regression suite
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/RegressionSuiteID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PatchRegressionSuiteInput"
      responses:
        "200":
          description: Regression suite updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RegressionSuite"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "409":
          $ref: "#/components/responses/ConflictError"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"

  /v1/workspaces/{workspaceID}/regression-suites/{suiteID}/cases:
    get:
      operationId: listRegressionCases
      tags: [Regressions]
      summary: List cases in a regression suite
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/RegressionSuiteID"
      responses:
        "200":
          description: Regression suite cases
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListRegressionCasesResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/workspaces/{workspaceID}/regression-suites/{suiteID}/production-failures:
    post:
      operationId: captureProductionFailure
      tags: [Regressions]
      summary: Capture a production failure as a proposed regression case
      description: >
        Creates a proposed regression case from an external or production
        incident without requiring a run-agent failure review item.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/RegressionSuiteID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CaptureProductionFailureInput"
      responses:
        "201":
          description: Production failure captured as a proposed regression case
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RegressionCase"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "409":
          $ref: "#/components/responses/ConflictError"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"

  /v1/workspaces/{workspaceID}/regression-cases:
    get:
      operationId: listWorkspaceRegressionCases
      tags: [Regressions]
      summary: List regression cases across a workspace
      description: >
        Lists regression cases across all suites in the workspace. Use
        `status=proposed` to power the CI-generated candidate triage queue.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - name: status
          in: query
          schema:
            $ref: "#/components/schemas/RegressionCaseStatus"
        - name: limit
          in: query
          schema:
            type: integer
            format: int32
            default: 50
            minimum: 1
            maximum: 100
        - name: offset
          in: query
          schema:
            type: integer
            format: int32
            default: 0
            minimum: 0
      responses:
        "200":
          description: Paginated workspace regression cases
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListWorkspaceRegressionCasesResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"

  /v1/workspaces/{workspaceID}/regression-cases/{caseID}:
    patch:
      operationId: patchRegressionCase
      tags: [Regressions]
      summary: Patch a regression case
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/RegressionCaseID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PatchRegressionCaseInput"
      responses:
        "200":
          description: Regression case updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RegressionCase"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "409":
          $ref: "#/components/responses/ConflictError"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"

  /v1/workspaces/{workspaceID}/datasets:
    get:
      operationId: listDatasets
      tags: [Datasets]
      summary: List datasets
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      responses:
        "200":
          description: Paginated datasets
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListDatasetsResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
    post:
      operationId: createDataset
      tags: [Datasets]
      summary: Create a dataset
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateDatasetInput"
      responses:
        "201":
          description: Dataset created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Dataset"
        "400":
          $ref: "#/components/responses/ValidationError"

  /v1/workspaces/{workspaceID}/datasets/{datasetID}:
    get:
      operationId: getDataset
      tags: [Datasets]
      summary: Get a dataset
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/DatasetID"
      responses:
        "200":
          description: Dataset
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Dataset"
        "404":
          $ref: "#/components/responses/NotFoundError"
    patch:
      operationId: patchDataset
      tags: [Datasets]
      summary: Patch a dataset
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/DatasetID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PatchDatasetInput"
      responses:
        "200":
          description: Dataset updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Dataset"
        "400":
          $ref: "#/components/responses/ValidationError"
    delete:
      operationId: deleteDataset
      tags: [Datasets]
      summary: Archive a dataset
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/DatasetID"
      responses:
        "204":
          description: Dataset archived
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/workspaces/{workspaceID}/datasets/{datasetID}/import:
    post:
      operationId: importDataset
      tags: [Datasets]
      summary: Import dataset examples from an external format
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/DatasetID"
        - name: format
          in: query
          schema:
            $ref: "#/components/schemas/DatasetInteropFormat"
        - name: mode
          in: query
          schema:
            type: string
            enum: [add, replace]
            default: add
        - name: dry_run
          in: query
          schema:
            type: boolean
            default: false
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/DatasetImportMultipartRequest"
          application/x-ndjson:
            schema:
              type: string
          text/csv:
            schema:
              type: string
      responses:
        "200":
          description: Import preview or completed import result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatasetImportResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/workspaces/{workspaceID}/datasets/{datasetID}/export:
    get:
      operationId: exportDataset
      tags: [Datasets]
      summary: Export dataset examples in an external format
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/DatasetID"
        - name: format
          in: query
          schema:
            $ref: "#/components/schemas/DatasetInteropFormat"
        - name: version_id
          in: query
          schema:
            type: string
            format: uuid
          description: Optional immutable dataset version to export. Defaults to active examples.
      responses:
        "200":
          description: Dataset export stream
          content:
            application/x-ndjson:
              schema:
                type: string
            text/csv:
              schema:
                type: string
        "400":
          $ref: "#/components/responses/ValidationError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/workspaces/{workspaceID}/datasets/{datasetID}/evals:
    post:
      operationId: startDatasetEval
      tags: [Datasets]
      summary: Run an eval over a pinned dataset version
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/DatasetID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StartDatasetEvalInput"
      responses:
        "201":
          description: Dataset eval run queued
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StartDatasetEvalResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/workspaces/{workspaceID}/datasets/{datasetID}/results:
    get:
      operationId: listDatasetResults
      tags: [Datasets]
      summary: List per-example dataset eval results
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/DatasetID"
        - name: version_id
          in: query
          schema:
            type: string
            format: uuid
          description: Optional dataset version filter.
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
            minimum: 1
            maximum: 100
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
            minimum: 0
      responses:
        "200":
          description: Paginated dataset eval results
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListDatasetResultsResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/workspaces/{workspaceID}/datasets/{datasetID}/traces/import:
    post:
      operationId: importDatasetTraces
      tags: [Datasets]
      summary: Import production traces as reviewable dataset candidates
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/DatasetID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ImportDatasetTracesInput"
      responses:
        "201":
          description: Trace import batch and staged candidates
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ImportDatasetTracesResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/workspaces/{workspaceID}/datasets/{datasetID}/trace-candidates:
    get:
      operationId: listDatasetTraceCandidates
      tags: [Datasets]
      summary: List trace candidates awaiting promotion
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/DatasetID"
        - name: status
          in: query
          schema:
            type: string
            enum: [pending, promoted, rejected]
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
            minimum: 1
            maximum: 100
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
            minimum: 0
      responses:
        "200":
          description: Paginated trace candidates
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListDatasetTraceCandidatesResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/workspaces/{workspaceID}/datasets/{datasetID}/trace-candidates/{candidateID}/promote:
    post:
      operationId: promoteDatasetTraceCandidate
      tags: [Datasets]
      summary: Promote a trace candidate into a dataset example
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/DatasetID"
        - name: candidateID
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PromoteDatasetTraceCandidateInput"
      responses:
        "200":
          description: Existing promoted example returned
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PromoteDatasetTraceCandidateResponse"
        "201":
          description: Candidate promoted to a new dataset example and version
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PromoteDatasetTraceCandidateResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "409":
          $ref: "#/components/responses/ValidationError"

  /v1/workspaces/{workspaceID}/datasets/{datasetID}/baselines:
    get:
      operationId: listDatasetBaselines
      tags: [Datasets]
      summary: List dataset eval baselines
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/DatasetID"
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
            minimum: 1
            maximum: 100
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
            minimum: 0
      responses:
        "200":
          description: Paginated dataset baselines
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListDatasetBaselinesResponse"
        "404":
          $ref: "#/components/responses/NotFoundError"
    post:
      operationId: createDatasetBaseline
      tags: [Datasets]
      summary: Record a baseline from a completed dataset eval run
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/DatasetID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateDatasetBaselineInput"
      responses:
        "201":
          description: Baseline recorded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatasetBaseline"
        "400":
          $ref: "#/components/responses/ValidationError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "409":
          description: Run is not completed or has no eval outcomes yet
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"

  /v1/workspaces/{workspaceID}/datasets/{datasetID}/regression-suite:
    get:
      operationId: getDatasetRegressionSuiteLink
      tags: [Datasets]
      summary: Get the regression suite linked to a dataset
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/DatasetID"
      responses:
        "200":
          description: Dataset regression suite link
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatasetRegressionSuiteLink"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          description: Dataset or regression suite link not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"

  /v1/workspaces/{workspaceID}/datasets/{datasetID}/regression-suite/sync:
    post:
      operationId: syncDatasetRegressionSuite
      tags: [Datasets]
      summary: Promote dataset examples into a linked regression suite
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/DatasetID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SyncDatasetRegressionSuiteInput"
      responses:
        "200":
          description: Regression suite synced
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SyncDatasetRegressionSuiteResult"
        "400":
          $ref: "#/components/responses/ValidationError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "409":
          description: Regression suite name conflict
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"

  /v1/workspaces/{workspaceID}/datasets/{datasetID}/generate:
    post:
      operationId: startDatasetGeneration
      tags: [Datasets]
      summary: Start in-house synthetic dataset generation
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/DatasetID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StartDatasetGenerationInput"
      responses:
        "202":
          description: Generation job accepted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatasetGenerationJob"
        "400":
          $ref: "#/components/responses/ValidationError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/workspaces/{workspaceID}/datasets/{datasetID}/generations/{jobID}:
    get:
      operationId: getDatasetGenerationJob
      tags: [Datasets]
      summary: Get a synthetic dataset generation job
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/DatasetID"
        - name: jobID
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Generation job detail
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatasetGenerationJob"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          description: Dataset or generation job not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"

  /v1/workspaces/{workspaceID}/datasets/{datasetID}/generations/{jobID}/rejections:
    get:
      operationId: listDatasetGenerationRejections
      tags: [Datasets]
      summary: List rejection records for a synthetic dataset generation job
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/DatasetID"
        - name: jobID
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
        - name: offset
          in: query
          schema:
            type: integer
            minimum: 0
            default: 0
      responses:
        "200":
          description: Generation rejection records
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatasetGenerationRejectionsResult"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/workspaces/{workspaceID}/datasets/{datasetID}/gate:
    post:
      operationId: evaluateDatasetGate
      tags: [Datasets]
      summary: Compare a candidate dataset eval run against a baseline
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/DatasetID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EvaluateDatasetGateInput"
      responses:
        "200":
          description: Gate passed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EvaluateDatasetGateResponse"
        "422":
          description: Gate failed (regressions or threshold violation)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EvaluateDatasetGateResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "409":
          description: Candidate run is not completed, has no eval outcomes, or uses a different dataset input set than the baseline
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"

  /v1/workspaces/{workspaceID}/datasets/{datasetID}/examples:
    get:
      operationId: listDatasetExamples
      tags: [Datasets]
      summary: List dataset examples
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/DatasetID"
      responses:
        "200":
          description: Paginated dataset examples
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListDatasetExamplesResponse"
        "404":
          $ref: "#/components/responses/NotFoundError"
    post:
      operationId: addDatasetExample
      tags: [Datasets]
      summary: Add or upsert a dataset example
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/DatasetID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpsertDatasetExampleInput"
      responses:
        "201":
          description: Dataset example
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatasetExample"
        "400":
          $ref: "#/components/responses/ValidationError"

  /v1/workspaces/{workspaceID}/datasets/{datasetID}/examples/{exampleID}:
    patch:
      operationId: patchDatasetExample
      tags: [Datasets]
      summary: Patch a dataset example
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/DatasetID"
        - $ref: "#/components/parameters/DatasetExampleID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PatchDatasetExampleInput"
      responses:
        "200":
          description: Dataset example updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatasetExample"
        "400":
          $ref: "#/components/responses/ValidationError"
    delete:
      operationId: deleteDatasetExample
      tags: [Datasets]
      summary: Archive a dataset example
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/DatasetID"
        - $ref: "#/components/parameters/DatasetExampleID"
      responses:
        "200":
          description: Dataset example archived
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatasetExample"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/workspaces/{workspaceID}/datasets/{datasetID}/versions:
    get:
      operationId: listDatasetVersions
      tags: [Datasets]
      summary: List dataset versions
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/DatasetID"
      responses:
        "200":
          description: Dataset versions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListDatasetVersionsResponse"
        "404":
          $ref: "#/components/responses/NotFoundError"
    post:
      operationId: createDatasetVersion
      tags: [Datasets]
      summary: Snapshot dataset examples
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/DatasetID"
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateDatasetVersionInput"
      responses:
        "201":
          description: Dataset version
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatasetVersion"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/workspaces/{workspaceID}/datasets/{datasetID}/versions/{versionID}:
    get:
      operationId: getDatasetVersion
      tags: [Datasets]
      summary: Get dataset version examples
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/DatasetID"
        - $ref: "#/components/parameters/DatasetVersionID"
      responses:
        "200":
          description: Dataset version detail
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatasetVersionDetail"
        "404":
          $ref: "#/components/responses/NotFoundError"

  # ---------------------------------------------------------------------------
  # Replays
  # ---------------------------------------------------------------------------
  /v1/replays/{runAgentID}:
    get:
      operationId: getReplay
      tags: [Replays]
      summary: Get execution replay
      description: >
        Returns paginated step-by-step execution replay for an agent.
        Returns 202 while replay is being generated, 409 if generation failed.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/RunAgentID"
        - name: cursor
          in: query
          schema:
            type: integer
            default: 0
            minimum: 0
          description: Pagination cursor (step offset)
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
            minimum: 1
            maximum: 200
          description: Maximum steps per page
      responses:
        "200":
          description: Replay ready
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReplayResponse"
        "202":
          description: Replay generation pending
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReplayResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "409":
          description: Replay generation failed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReplayResponse"

  /v1/replays/{runAgentID}/transcript:
    get:
      operationId: getReplayTranscript
      tags: [Replays]
      summary: Get multi-turn conversation transcript
      description: >
        Returns the reconstructed multi-turn conversation transcript for an
        agent — ordered user/assistant turns with phase, actor, and mismatch
        metadata. Single-turn (native) runs return an empty turns array.
        Returns 202 while the transcript is still being recorded.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/RunAgentID"
      responses:
        "200":
          description: Transcript ready
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TranscriptResponse"
        "202":
          description: Transcript recording pending
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TranscriptResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/replays/{runAgentID}/viewer:
    get:
      operationId: getReplayViewer
      tags: [Replays]
      summary: Replay HTML viewer
      description: Server-rendered HTML page for interactive replay visualization.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/RunAgentID"
      responses:
        "200":
          description: HTML replay viewer page
          content:
            text/html:
              schema:
                type: string
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"

  # ---------------------------------------------------------------------------
  # Scorecards
  # ---------------------------------------------------------------------------
  /v1/scorecards/{runAgentID}:
    get:
      operationId: getScorecard
      tags: [Scorecards]
      summary: Get agent scorecard
      description: >
        Returns scoring results across correctness, reliability, latency, and
        cost dimensions. Returns 202 while pending, 409 if generation failed.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/RunAgentID"
      responses:
        "200":
          description: Scorecard ready
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ScorecardResponse"
        "202":
          description: Scorecard generation pending
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ScorecardResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "409":
          description: Scorecard generation failed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ScorecardResponse"

  # ---------------------------------------------------------------------------
  # Compare
  # ---------------------------------------------------------------------------
  /v1/compare:
    get:
      operationId: getRunComparison
      tags: [Compare]
      summary: Compare two runs
      description: Side-by-side comparison of baseline and candidate runs.
      security:
        - bearerJWT: []
      parameters:
        - name: baseline_run_id
          in: query
          required: true
          schema:
            type: string
            format: uuid
        - name: candidate_run_id
          in: query
          required: true
          schema:
            type: string
            format: uuid
        - name: baseline_run_agent_id
          in: query
          schema:
            type: string
            format: uuid
        - name: candidate_run_agent_id
          in: query
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Comparison result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ComparisonResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/compare/viewer:
    get:
      operationId: getComparisonViewer
      tags: [Compare]
      summary: Comparison HTML viewer
      description: Server-rendered HTML page for interactive comparison visualization.
      security:
        - bearerJWT: []
      parameters:
        - name: baseline_run_id
          in: query
          required: true
          schema:
            type: string
            format: uuid
        - name: candidate_run_id
          in: query
          required: true
          schema:
            type: string
            format: uuid
        - name: baseline_run_agent_id
          in: query
          schema:
            type: string
            format: uuid
        - name: candidate_run_agent_id
          in: query
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: HTML comparison viewer page
          content:
            text/html:
              schema:
                type: string
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"

  # ---------------------------------------------------------------------------
  # Release Gates
  # ---------------------------------------------------------------------------
  /v1/release-gates:
    get:
      operationId: listReleaseGates
      tags: [ReleaseGates]
      summary: List release gate evaluations
      description: Lists all release gate evaluations for a run comparison.
      security:
        - bearerJWT: []
      parameters:
        - name: baseline_run_id
          in: query
          required: true
          schema:
            type: string
            format: uuid
        - name: candidate_run_id
          in: query
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Release gate evaluations
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListReleaseGatesResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/release-gates/evaluate:
    post:
      operationId: evaluateReleaseGate
      tags: [ReleaseGates]
      summary: Evaluate a release gate
      description: Evaluates a run comparison against a release gate policy.
      security:
        - bearerJWT: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EvaluateReleaseGateRequest"
      responses:
        "200":
          description: Gate evaluation result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EvaluateReleaseGateResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  # ---------------------------------------------------------------------------
  # Challenge Packs
  # ---------------------------------------------------------------------------
  /v1/workspaces/{workspaceID}/challenge-packs:
    get:
      operationId: listChallengePacks
      tags: [ChallengePacks]
      summary: List challenge packs
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      responses:
        "200":
          description: Challenge packs with versions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListChallengePacksResponse"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
    post:
      operationId: publishChallengePack
      tags: [ChallengePacks]
      summary: Publish a challenge pack
      description: >
        Validates and publishes a challenge pack bundle. The request body is raw
        YAML (max 1 MiB).
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      requestBody:
        required: true
        content:
          application/octet-stream:
            schema:
              type: string
              format: binary
      responses:
        "201":
          description: Challenge pack published
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublishChallengePackResponse"
        "400":
          description: Validation errors
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ValidationResult"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "409":
          description: Challenge pack version or metadata conflict
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"

  /v1/workspaces/{workspaceID}/challenge-packs/validate:
    post:
      operationId: validateChallengePack
      tags: [ChallengePacks]
      summary: Validate a challenge pack bundle
      description: >
        Validates a challenge pack YAML bundle without publishing it. Returns
        200 if valid, 400 with error details if invalid.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      requestBody:
        required: true
        content:
          application/octet-stream:
            schema:
              type: string
              format: binary
      responses:
        "200":
          description: Bundle is valid
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ValidationResult"
        "400":
          description: Bundle has validation errors
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ValidationResult"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"

  /v1/workspaces/{workspaceID}/challenge-pack-versions/{versionID}/input-sets:
    get:
      operationId: listChallengeInputSets
      tags: [ChallengePacks]
      summary: List published input sets for a challenge-pack version
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - name: versionID
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Published input sets for the selected runnable version
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListChallengeInputSetsResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  # ---------------------------------------------------------------------------
  # Challenge Pack Builder (reusable pieces + drafts)
  # ---------------------------------------------------------------------------
  /v1/challenge-piece-library:
    get:
      operationId: listChallengePieceLibrary
      tags: [ChallengePacks]
      summary: List built-in starter pieces
      description: A global, read-only catalog of starter pieces the builder can clone into a pack. Not workspace-scoped.
      security:
        - bearerJWT: []
      parameters:
        - name: kind
          in: query
          required: false
          schema:
            type: string
            enum: [validator, judge, input_set, challenge]
      responses:
        "200":
          description: Starter piece catalog
          content:
            application/json:
              schema:
                type: object
                required: [items]
                properties:
                  items:
                    type: array
                    items:
                      $ref: "#/components/schemas/StarterPiece"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
  /v1/challenge-pack-catalog:
    get:
      operationId: listChallengePackCatalog
      tags: [ChallengePacks]
      summary: List the curated challenge-pack library
      description: >
        A global, read-only catalog of ready-to-run challenge packs ("templates")
        users can clone into a workspace. Not workspace-scoped. The raw YAML body
        is omitted from list items; fetch the detail endpoint for it.
      security:
        - bearerJWT: []
      parameters:
        - name: family
          in: query
          required: false
          schema:
            type: string
        - name: category
          in: query
          required: false
          schema:
            type: string
            enum: [enterprise, agent_capability, safety]
      responses:
        "200":
          description: Curated catalog entries
          content:
            application/json:
              schema:
                type: object
                required: [items]
                properties:
                  items:
                    type: array
                    items:
                      $ref: "#/components/schemas/ChallengePackCatalogSummary"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
  /v1/challenge-pack-catalog/{slug}:
    get:
      operationId: getChallengePackCatalogEntry
      tags: [ChallengePacks]
      summary: Get a catalog entry including its runnable YAML
      security:
        - bearerJWT: []
      parameters:
        - name: slug
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Catalog entry with raw YAML
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChallengePackCatalogEntry"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "404":
          $ref: "#/components/responses/NotFoundError"
  /v1/workspaces/{workspaceID}/challenge-pack-catalog/{slug}/instantiate:
    post:
      operationId: instantiateChallengePackCatalog
      tags: [ChallengePacks]
      summary: Clone a catalog pack into the workspace
      description: >
        Clones a curated library pack into the caller's workspace as a runnable,
        fully-owned pack. Idempotent: adding the same template twice returns the
        existing pack (200, already_existed=true) instead of erroring. Returns 201
        for a fresh clone. The curated library is not gated by the private-packs
        entitlement.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - name: slug
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Workspace already had this pack; returns the existing copy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InstantiateChallengePackCatalogResponse"
        "201":
          description: Catalog pack cloned into the workspace
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InstantiateChallengePackCatalogResponse"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "409":
          description: A different pack already uses this slug in the workspace
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /v1/workspaces/{workspaceID}/challenge-pieces:
    get:
      operationId: listChallengePieces
      tags: [ChallengePacks]
      summary: List reusable challenge pieces
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - name: kind
          in: query
          description: Filter pieces by kind.
          schema:
            type: string
            enum:
              - validator
              - judge
              - input_set
              - challenge
      responses:
        "200":
          description: Reusable challenge pieces
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListChallengePiecesResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
    post:
      operationId: createChallengePiece
      tags: [ChallengePacks]
      summary: Create a reusable challenge piece
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateChallengePieceRequest"
      responses:
        "201":
          description: Challenge piece created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChallengePiece"
        "400":
          description: Validation errors
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ValidationResult"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "409":
          $ref: "#/components/responses/ConflictError"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"

  /v1/workspaces/{workspaceID}/challenge-pieces/{pieceID}:
    get:
      operationId: getChallengePiece
      tags: [ChallengePacks]
      summary: Get a reusable challenge piece
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/ChallengePieceID"
      responses:
        "200":
          description: Challenge piece
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChallengePiece"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
    patch:
      operationId: patchChallengePiece
      tags: [ChallengePacks]
      summary: Patch a reusable challenge piece
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/ChallengePieceID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PatchChallengePieceRequest"
      responses:
        "200":
          description: Challenge piece updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChallengePiece"
        "400":
          description: Validation errors
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ValidationResult"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "409":
          $ref: "#/components/responses/ConflictError"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"
    delete:
      operationId: deleteChallengePiece
      tags: [ChallengePacks]
      summary: Delete a reusable challenge piece
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/ChallengePieceID"
      responses:
        "204":
          description: Challenge piece deleted
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/workspaces/{workspaceID}/challenge-pack-drafts:
    get:
      operationId: listChallengePackDrafts
      tags: [ChallengePacks]
      summary: List challenge pack drafts
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      responses:
        "200":
          description: Challenge pack drafts
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListChallengePackDraftsResponse"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
    post:
      operationId: createChallengePackDraft
      tags: [ChallengePacks]
      summary: Create a challenge pack draft
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateChallengePackDraftRequest"
      responses:
        "201":
          description: Challenge pack draft created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChallengePackDraft"
        "400":
          description: Validation errors
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ValidationResult"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"

  /v1/workspaces/{workspaceID}/challenge-pack-drafts/generate:
    post:
      operationId: generateChallengePackDraft
      tags: [ChallengePacks]
      summary: Generate a challenge pack draft from a description
      description: >
        Authors an editable challenge pack draft from a plain-English
        description of an app, using the selected workspace provider account and
        model. The generated composition is validated through the builder's
        compile path before it is stored, so a draft is only created when it
        compiles. Returns the same draft shape as creating one by hand.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GenerateChallengePackDraftRequest"
      responses:
        "201":
          description: Challenge pack draft created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChallengePackDraft"
        "400":
          description: >
            Invalid request. Request-level problems (blank or over-long
            description, unusable provider account, a model reply that does not
            compile, or a reply the model truncated at its own output limit —
            `generated_pack_truncated`) return an error envelope; a generated
            composition that the builder rejects returns the same field-level
            validation result as creating a draft by hand, because this route
            shares that draft write path.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ErrorEnvelope"
                  - $ref: "#/components/schemas/ValidationResult"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"
        "429":
          description: Pack generation or the selected provider is rate limited
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "502":
          description: Selected provider failed to generate a pack
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "503":
          description: >
            Pack generation is not configured on this deployment, or the
            selected provider timed out or was unavailable.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"

  /v1/workspaces/{workspaceID}/challenge-pack-drafts/{draftID}:
    get:
      operationId: getChallengePackDraft
      tags: [ChallengePacks]
      summary: Get a challenge pack draft
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/ChallengePackDraftID"
      responses:
        "200":
          description: Challenge pack draft
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChallengePackDraft"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
    patch:
      operationId: patchChallengePackDraft
      tags: [ChallengePacks]
      summary: Patch a challenge pack draft
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/ChallengePackDraftID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PatchChallengePackDraftRequest"
      responses:
        "200":
          description: Challenge pack draft updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChallengePackDraft"
        "400":
          description: Validation errors
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ValidationResult"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"
    delete:
      operationId: deleteChallengePackDraft
      tags: [ChallengePacks]
      summary: Delete a challenge pack draft
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/ChallengePackDraftID"
      responses:
        "204":
          description: Challenge pack draft deleted
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/workspaces/{workspaceID}/challenge-pack-drafts/{draftID}/compile:
    post:
      operationId: compileChallengePackDraft
      tags: [ChallengePacks]
      summary: Compile a challenge pack draft
      description: >
        Resolves the draft's referenced pieces and validates the composed
        bundle without publishing it. Returns a readable spec card, the
        equivalent YAML, and a validation verdict. An incomplete draft compiles
        with `valid: false` and the missing pieces reported in `errors`.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/ChallengePackDraftID"
      responses:
        "200":
          description: Draft compiled
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CompileDraftResponse"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/workspaces/{workspaceID}/challenge-pack-drafts/{draftID}/publish:
    post:
      operationId: publishChallengePackDraft
      tags: [ChallengePacks]
      summary: Publish a challenge pack draft
      description: >
        Composes the draft into a runnable bundle, then validates and publishes
        it through the same path as the raw-YAML publish endpoint, snapshotting
        an immutable challenge pack version.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/ChallengePackDraftID"
      responses:
        "201":
          description: Challenge pack published
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublishChallengePackResponse"
        "400":
          description: Validation errors
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ValidationResult"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "409":
          description: Challenge pack version or metadata conflict
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"

  # ---------------------------------------------------------------------------
  # Agent Builds
  # ---------------------------------------------------------------------------
  /v1/workspaces/{workspaceID}/agent-builds:
    post:
      operationId: createAgentBuild
      tags: [AgentBuilds]
      summary: Create an agent build
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateAgentBuildRequest"
      responses:
        "201":
          description: Agent build created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentBuild"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"
    get:
      operationId: listAgentBuilds
      tags: [AgentBuilds]
      summary: List agent builds in a workspace
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      responses:
        "200":
          description: List of agent builds
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListAgentBuildsResponse"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"

  /v1/agent-builds/{agentBuildID}:
    get:
      operationId: getAgentBuild
      tags: [AgentBuilds]
      summary: Get agent build with versions
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/AgentBuildID"
      responses:
        "200":
          description: Agent build with version history
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentBuildDetail"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/agent-builds/{agentBuildID}/versions:
    post:
      operationId: createAgentBuildVersion
      tags: [AgentBuilds]
      summary: Create a new build version
      description: >
        Creates a new draft version with auto-incremented version number.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/AgentBuildID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateAgentBuildVersionRequest"
      responses:
        "201":
          description: Version created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentBuildVersion"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"

  /v1/agent-build-versions/{versionID}:
    get:
      operationId: getAgentBuildVersion
      tags: [AgentBuilds]
      summary: Get a build version
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/VersionID"
      responses:
        "200":
          description: Build version details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentBuildVersion"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
    patch:
      operationId: updateAgentBuildVersion
      tags: [AgentBuilds]
      summary: Update a draft build version
      description: Only draft versions can be updated.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/VersionID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateAgentBuildVersionRequest"
      responses:
        "200":
          description: Version updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentBuildVersion"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"

  /v1/agent-build-versions/{versionID}/validate:
    post:
      operationId: validateAgentBuildVersion
      tags: [AgentBuilds]
      summary: Validate a build version
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/VersionID"
      responses:
        "200":
          description: Validation result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ValidationResult"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/agent-build-versions/{versionID}/ready:
    post:
      operationId: markAgentBuildVersionReady
      tags: [AgentBuilds]
      summary: Mark a version as ready
      description: >
        Validates the version and transitions it from draft to ready. Ready
        versions are immutable and deployable.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/VersionID"
      responses:
        "200":
          description: Version marked ready
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentBuildVersion"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  # ---------------------------------------------------------------------------
  # Agent Deployments
  # ---------------------------------------------------------------------------
  /v1/workspaces/{workspaceID}/agent-deployments:
    get:
      operationId: listAgentDeployments
      tags: [AgentDeployments]
      summary: List agent deployments
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - name: limit
          in: query
          description: Page size (server default 20, capped at 100).
          schema:
            type: integer
            format: int32
            default: 20
            minimum: 1
            maximum: 100
        - name: offset
          in: query
          description: Number of items to skip before the first result.
          schema:
            type: integer
            format: int32
            default: 0
            minimum: 0
      responses:
        "200":
          description: List of agent deployments
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListAgentDeploymentsResponse"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
    post:
      operationId: createAgentDeployment
      tags: [AgentDeployments]
      summary: Create an agent deployment
      description: Only ready build versions can be deployed.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateAgentDeploymentRequest"
      responses:
        "201":
          description: Deployment created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentDeploymentCreateResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"

  /v1/workspaces/{workspaceID}/agent-builds/quick-create:
    post:
      operationId: quickCreateAgent
      tags: [AgentBuilds]
      summary: Quick-create an agent
      description: >
        Collapses the build, first version, ready transition, and deployment
        into a single call. Supply the agent's name and instructions (or a
        template) plus a model on a provider account against a runtime profile;
        the build, version, ready transition, and deployment are derived. The
        advanced per-step endpoints remain available for power users.
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/QuickCreateAgentRequest"
      responses:
        "201":
          description: Agent created (build, version, and deployment)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/QuickCreateAgentResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"

  /v1/agent-tryout-templates:
    get:
      operationId: listAgentTryoutTemplates
      summary: List agent tryout templates
      tags: [AgentTryouts]
      security: []
      responses:
        "200":
          description: Agent tryout templates available to the public tryout UI
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListAgentTryoutTemplatesResponse"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/agent-tryout-input-attachments:
    post:
      operationId: uploadAgentTryoutInputAttachment
      summary: Upload a PDF or image for an anonymous tryout input
      description: >
        Stages a file for inclusion in a subsequent anonymous tryout create request.
        Attachments are bound to the caller fingerprint and referenced by id in
        `input.input_attachments` when creating a tryout.
      tags: [AgentTryouts]
      security: []
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file:
                  type: string
                  format: binary
                  description: PDF or image file (JPEG, PNG, GIF, WebP), up to 15 MB
      responses:
        "201":
          description: Attachment uploaded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentTryoutInputAttachment"
        "400":
          $ref: "#/components/responses/ValidationError"
        "413":
          description: Attachment exceeds maximum upload size
        "503":
          description: Attachment storage unavailable
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/agent-tryouts:
    post:
      operationId: createAnonymousAgentTryout
      summary: Create an anonymous agent tryout
      tags: [AgentTryouts]
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateAgentTryoutRequest"
      responses:
        "201":
          description: Anonymous tryout queued with template, tool, and evaluation snapshots
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicAgentTryoutResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/agent-tryouts/{id}/turns:
    post:
      operationId: submitAnonymousAgentTryoutTurn
      summary: Send a chat turn to a live anonymous agent tryout session
      tags: [AgentTryouts]
      security: []
      parameters:
        - $ref: "#/components/parameters/ResourceID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                message:
                  type: string
                  description: User message for the agent. Required unless end=true.
                end:
                  type: boolean
                  description: Close the interactive session instead of sending a message.
      responses:
        "202":
          description: Turn submitted; the session activity will process it.
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
        "400":
          $ref: "#/components/responses/ValidationError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/agent-tryouts/{id}:
    get:
      operationId: getPublicAgentTryout
      summary: Get an anonymous agent tryout
      tags: [AgentTryouts]
      security: []
      parameters:
        - $ref: "#/components/parameters/ResourceID"
      responses:
        "200":
          description: Anonymous tryout detail
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicAgentTryoutResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/agent-tryouts/{id}/events:
    get:
      operationId: getPublicAgentTryoutEvents
      summary: Get the live evidence timeline for an anonymous agent tryout
      description: >-
        Returns a cursor-paginated timeline of product-level events for an
        anonymous (unclaimed) tryout. Event payloads are redacted to a
        non-sensitive allow-list because this endpoint is unauthenticated. Poll
        with the returned `next_cursor` as `after` to resume; `has_more`
        indicates whether further events are immediately available.
      tags: [AgentTryouts]
      security: []
      parameters:
        - $ref: "#/components/parameters/ResourceID"
        - $ref: "#/components/parameters/TryoutEventsAfter"
        - $ref: "#/components/parameters/TryoutEventsLimit"
      responses:
        "200":
          description: Redacted tryout event timeline page
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentTryoutEventsResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/agent-tryouts/shared/{token}/events:
    get:
      operationId: getSharedAgentTryoutEvents
      summary: Get the redacted evidence timeline for a shared agent tryout
      description: >-
        Returns a cursor-paginated, redacted timeline for a tryout exposed
        behind a public share token. The token is resolved to an active share
        link that must reference an agent tryout whose redaction has cleared.
        Event payloads are always redacted to a non-sensitive allow-list because
        this endpoint is unauthenticated. A token that is missing, revoked,
        expired, points at a non-tryout resource, or references a tryout whose
        redaction has not passed returns 404 so callers cannot probe redaction
        state. Poll with the returned `next_cursor` as `after` to resume.
      tags: [AgentTryouts]
      security: []
      parameters:
        - name: token
          in: path
          required: true
          description: The opaque public share token for the tryout.
          schema:
            type: string
        - $ref: "#/components/parameters/TryoutEventsAfter"
        - $ref: "#/components/parameters/TryoutEventsLimit"
      responses:
        "200":
          description: Redacted tryout event timeline page
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentTryoutEventsResponse"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/workspaces/{workspaceID}/agent-tryouts:
    post:
      operationId: createWorkspaceAgentTryout
      summary: Create a workspace-owned agent tryout
      tags: [AgentTryouts]
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateAgentTryoutRequest"
      responses:
        "201":
          description: Workspace tryout queued
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentTryoutResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
    get:
      operationId: listWorkspaceAgentTryouts
      summary: List workspace agent tryouts
      tags: [AgentTryouts]
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - name: limit
          in: query
          schema:
            type: integer
            format: int32
            default: 50
            minimum: 1
            maximum: 100
        - name: offset
          in: query
          schema:
            type: integer
            format: int32
            default: 0
            minimum: 0
      responses:
        "200":
          description: Workspace tryouts
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListAgentTryoutsResponse"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"

  /v1/workspaces/{workspaceID}/agent-tryouts/{id}:
    get:
      operationId: getWorkspaceAgentTryout
      summary: Get a workspace agent tryout
      tags: [AgentTryouts]
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/ResourceID"
      responses:
        "200":
          description: Workspace tryout detail
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentTryoutResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/workspaces/{workspaceID}/agent-tryouts/{id}/events:
    get:
      operationId: getWorkspaceAgentTryoutEvents
      summary: Get the live evidence timeline for a workspace agent tryout
      description: >-
        Returns a cursor-paginated timeline of product-level events for a
        workspace-owned tryout. Workspace members receive the full (unredacted)
        event payloads. Poll with the returned `next_cursor` as `after` to
        resume.
      tags: [AgentTryouts]
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/ResourceID"
        - $ref: "#/components/parameters/TryoutEventsAfter"
        - $ref: "#/components/parameters/TryoutEventsLimit"
      responses:
        "200":
          description: Workspace tryout event timeline page
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentTryoutEventsResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/workspaces/{workspaceID}/agent-tryouts/{id}/artifacts:
    get:
      operationId: listWorkspaceAgentTryoutArtifacts
      summary: List the captured output artifacts of a workspace agent tryout
      description: >-
        Returns the durable output files the agent produced during a tryout
        (e.g. a slide deck or spreadsheet), each with a signed, time-limited
        download URL. Workspace members see every captured artifact (no public
        template allowlist redaction). Empty when the tryout has no linked run
        or produced no files.
      tags: [AgentTryouts]
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
        - $ref: "#/components/parameters/ResourceID"
      responses:
        "200":
          description: Captured tryout artifacts
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListAgentTryoutArtifactsResponse"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"

  /v1/agent-tryouts/{id}/claim:
    post:
      operationId: claimAgentTryout
      summary: Claim an anonymous tryout into a workspace
      tags: [AgentTryouts]
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/ResourceID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ClaimAgentTryoutRequest"
      responses:
        "200":
          description: Claimed tryout
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentTryoutResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "409":
          $ref: "#/components/responses/ConflictError"

  /v1/agent-tryouts/{id}/share:
    post:
      operationId: createAgentTryoutShare
      summary: Create a noindex share link for a workspace tryout
      description: >-
        Creates a private (noindex) share link for a completed workspace tryout.
        Share creation is gated on redaction: a tryout whose `redaction_status`
        is `pending` or `failed` returns 409 so unredacted evidence is never
        published. Only `passed`/`not_required` tryouts can be shared.
      tags: [AgentTryouts]
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/ResourceID"
      responses:
        "201":
          description: Private share link created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentTryoutShareResponse"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "409":
          $ref: "#/components/responses/ConflictError"

  /v1/agent-tryouts/{id}/rerun:
    post:
      operationId: rerunAgentTryout
      summary: Rerun a workspace tryout with a different model policy
      description: >-
        Creates a new tryout that reuses the source tryout's template and input
        but runs under a different model policy, linked to the source via
        `parent_tryout_id`. The source is never mutated. Anonymous/unclaimed
        source tryouts return a sign-in-required error; an invalid model policy
        or an unavailable provider/model returns a product error.
      tags: [AgentTryouts]
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/ResourceID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RerunAgentTryoutRequest"
      responses:
        "201":
          description: Rerun tryout queued
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentTryoutResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "402":
          description: Provider key required or insufficient credits to rerun.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "422":
          description: The requested model/provider is unavailable.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"

  /v1/agent-tryouts/{id}/promote-to-eval:
    post:
      operationId: promoteAgentTryoutToEval
      summary: Promote a workspace tryout into an editable challenge pack draft
      description: >-
        Converts a completed workspace tryout into a `challenge_pack_drafts` row
        the visual pack builder can edit, compile, publish, and run. The tryout's
        template instructions, input, tool policy, expected artifacts, LLM
        judges, and cost/duration budget are mapped onto a pack composition. v1
        supports only the `vibe_eval` target; any other target returns 400. The
        request body is optional (an empty POST uses the default target).
        Requires the `publish_challenge_pack` permission on the tryout's
        workspace, because the result is a publishable pack.
      tags: [AgentTryouts]
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/ResourceID"
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PromoteAgentTryoutRequest"
      responses:
        "201":
          description: Promotion draft created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentTryoutPromotionResult"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"
        "503":
          description: The challenge pack builder is not configured on this deployment
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"

  /v1/workspaces/{workspaceID}/agent-tryouts/compare:
    post:
      operationId: compareAgentTryouts
      summary: Compare 2-4 workspace tryouts side by side
      description: >-
        Returns a read-only side-by-side comparison of 2–4 tryouts that all
        belong to the workspace: per-tryout model policy, status, cost, latency,
        summary, and a link to the full event evidence. Any id outside the
        workspace fails closed as not-found.
      tags: [AgentTryouts]
      security:
        - bearerJWT: []
      parameters:
        - $ref: "#/components/parameters/WorkspaceID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CompareAgentTryoutsRequest"
      responses:
        "200":
          description: Side-by-side comparison
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentTryoutCompareResult"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"

# =============================================================================
# Components
# =============================================================================
components:
  # ---------------------------------------------------------------------------
  # Security Schemes
  # ---------------------------------------------------------------------------
  securitySchemes:
    bearerJWT:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: >
        Bearer token for authenticated API requests. Web sessions use a WorkOS JWT
        and CLI sessions use an AgentClash token prefixed with `clitok_`.
        In development mode, use custom headers instead:
        X-Agentclash-User-Id, X-Agentclash-WorkOS-User-Id,
        X-Agentclash-User-Email, X-Agentclash-User-Display-Name,
        X-Agentclash-Workspace-Memberships.
    hostedRunToken:
      type: http
      scheme: bearer
      description: >
        HMAC-SHA256 callback token issued when a hosted run is started.
        Not a JWT — the token encodes run_id and run_agent_id claims.
    artifactSignature:
      type: apiKey
      in: query
      name: signature
      description: >
        HMAC-SHA256 signature for artifact content URLs. Must be paired with
        an "expires" query parameter (unix timestamp).

  # ---------------------------------------------------------------------------
  # Parameters
  # ---------------------------------------------------------------------------
  parameters:
    WorkspaceID:
      name: workspaceID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    RunID:
      name: runID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    RunAgentID:
      name: runAgentID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    ExecutionID:
      name: executionID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    AgentHarnessSuiteID:
      name: suiteID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    ArtifactID:
      name: artifactID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    AgentBuildID:
      name: agentBuildID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    VersionID:
      name: versionID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    ResourceID:
      name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    TryoutEventsAfter:
      name: after
      in: query
      description: Return only events whose cursor is strictly greater than this value. Use the previous response's `next_cursor` to resume. Defaults to 0 (from the beginning).
      schema:
        type: integer
        format: int64
        default: 0
        minimum: 0
    TryoutEventsLimit:
      name: limit
      in: query
      description: Maximum number of events to scan for this page.
      schema:
        type: integer
        format: int32
        default: 100
        minimum: 1
        maximum: 200
    RegressionSuiteID:
      name: suiteID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    RegressionCaseID:
      name: caseID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    ChallengePieceID:
      name: pieceID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    ChallengePackDraftID:
      name: draftID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    DatasetID:
      name: datasetID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    DatasetExampleID:
      name: exampleID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    DatasetVersionID:
      name: versionID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    OrganizationID:
      name: organizationID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    MembershipID:
      name: membershipID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    ProfileID:
      name: profileID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    AccountID:
      name: accountID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    ToolID:
      name: toolID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    SecretKey:
      name: secretKey
      in: path
      required: true
      schema:
        type: string
        pattern: "^[A-Za-z_][A-Za-z0-9_]*$"
        minLength: 1
        maxLength: 128

  # ---------------------------------------------------------------------------
  # Responses
  # ---------------------------------------------------------------------------
  responses:
    ValidationError:
      description: Invalid request
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
    UnauthorizedError:
      description: Authentication required
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
    ForbiddenError:
      description: Insufficient permissions
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
    NotFoundError:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
    UnsupportedMediaType:
      description: Content-Type must be application/json
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
    ConflictError:
      description: Resource conflict
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
    TooManyRequests:
      description: Rate or concurrency limit exceeded
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
    GoneError:
      description: Resource expired or removed
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
    InternalError:
      description: Internal server error
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"

  # ---------------------------------------------------------------------------
  # Schemas
  # ---------------------------------------------------------------------------
  schemas:
    PublicPublication:
      type: object
      required: [id, resource_type, created_at, updated_at, canonical_path]
      properties:
        id:
          type: string
          format: uuid
        resource_type:
          type: string
          enum:
            - challenge_pack_version
            - run_scorecard
            - run_agent_scorecard
            - run_agent_replay
            - agent_tryout
        expires_at:
          type: string
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        canonical_path:
          type: string
          pattern: ^/publications/[0-9a-f-]+$

    PublicPublicationPayload:
      type: object
      required: [publication, resource]
      properties:
        publication:
          $ref: "#/components/schemas/PublicPublication"
        resource:
          type: object
          description: Type-specific, explicitly allowlisted redacted public resource.
          required: [type]
          properties:
            type:
              type: string
          additionalProperties: true

    PublicPublicationList:
      type: object
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/PublicPublicationPayload"
        next_cursor:
          type: string
          format: uuid

    # --- Shared ---

    Dataset:
      type: object
      required: [id, organization_id, workspace_id, slug, name, description, input_schema_enforced, active_example_count, version_count, created_by, created_at, updated_at]
      properties:
        id: { type: string, format: uuid }
        organization_id: { type: string, format: uuid }
        workspace_id: { type: string, format: uuid }
        slug: { type: string }
        name: { type: string }
        description: { type: string }
        input_schema:
          type: object
          additionalProperties: true
        input_schema_enforced: { type: boolean }
        default_challenge_pack_version_id: { type: string, format: uuid }
        active_example_count: { type: integer }
        version_count: { type: integer }
        created_by: { type: string, format: uuid }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        archived_at: { type: string, format: date-time }

    DatasetExample:
      type: object
      required: [id, dataset_id, input, metadata, tags, status, source, created_by, created_at, updated_at]
      properties:
        id: { type: string, format: uuid }
        dataset_id: { type: string, format: uuid }
        external_id: { type: string }
        input: {}
        expected: {}
        metadata:
          type: object
          additionalProperties: true
        tags:
          type: array
          items: { type: string }
        status:
          type: string
          enum: [active, archived, muted]
        source:
          type: string
          enum: [manual, import, trace, synthetic, promotion]
        source_run_id: { type: string, format: uuid }
        source_trace_id: { type: string }
        source_platform: { type: string }
        artifact_id: { type: string, format: uuid }
        created_by: { type: string, format: uuid }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    DatasetVersion:
      type: object
      required: [id, dataset_id, version_number, example_count, manifest_checksum, created_by, created_at]
      properties:
        id: { type: string, format: uuid }
        dataset_id: { type: string, format: uuid }
        version_number: { type: integer }
        label: { type: string }
        example_count: { type: integer }
        manifest_checksum: { type: string }
        created_by: { type: string, format: uuid }
        created_at: { type: string, format: date-time }

    CreateDatasetInput:
      type: object
      required: [slug, name]
      properties:
        slug: { type: string }
        name: { type: string }
        description: { type: string }
        input_schema:
          type: object
          additionalProperties: true
        input_schema_enforced: { type: boolean }
        default_challenge_pack_version_id: { type: string, format: uuid }

    PatchDatasetInput:
      type: object
      properties:
        slug: { type: string }
        name: { type: string }
        description: { type: string }
        input_schema:
          type: object
          additionalProperties: true
        input_schema_enforced: { type: boolean }
        default_challenge_pack_version_id: { type: string, format: uuid }

    UpsertDatasetExampleInput:
      type: object
      required: [input]
      properties:
        external_id: { type: string }
        input: {}
        expected: {}
        metadata:
          type: object
          additionalProperties: true
        tags:
          type: array
          items: { type: string }
        status:
          type: string
          enum: [active, archived, muted]
        source:
          type: string
          enum: [manual, import, trace, synthetic, promotion]
        source_run_id: { type: string, format: uuid }
        source_trace_id: { type: string }
        source_platform: { type: string }
        artifact_id: { type: string, format: uuid }

    PatchDatasetExampleInput:
      type: object
      properties:
        input: {}
        expected: {}
        metadata:
          type: object
          additionalProperties: true
        tags:
          type: array
          items: { type: string }
        status:
          type: string
          enum: [active, archived, muted]
        source:
          type: string
          enum: [manual, import, trace, synthetic, promotion]
        source_run_id: { type: string, format: uuid }
        source_trace_id: { type: string }
        source_platform: { type: string }
        artifact_id: { type: string, format: uuid }

    CreateDatasetVersionInput:
      type: object
      properties:
        label: { type: string }

    DatasetInteropFormat:
      type: string
      enum: [openai, braintrust, langsmith, phoenix, jsonl, csv]

    DatasetImportMultipartRequest:
      type: object
      required: [file]
      properties:
        file:
          type: string
          format: binary
        format:
          $ref: "#/components/schemas/DatasetInteropFormat"
        mode:
          type: string
          enum: [add, replace]
          default: add
        dry_run:
          type: boolean
          default: false
        mapping:
          description: JSON-encoded DatasetImportMapping.
          type: string
        map:
          description: Alias for mapping.
          type: string

    DatasetImportPreviewExample:
      type: object
      required: [input, metadata]
      properties:
        external_id: { type: string }
        input: {}
        expected: {}
        metadata:
          type: object
          additionalProperties: true
        tags:
          type: array
          items: { type: string }

    DatasetImportRowError:
      type: object
      required: [row, message]
      properties:
        row: { type: integer }
        field: { type: string }
        message: { type: string }

    DatasetImportResponse:
      type: object
      required: [format, dry_run, mode, imported_count]
      properties:
        format:
          $ref: "#/components/schemas/DatasetInteropFormat"
        dry_run: { type: boolean }
        mode:
          type: string
          enum: [add, replace]
        preview:
          type: array
          items:
            $ref: "#/components/schemas/DatasetImportPreviewExample"
        errors:
          type: array
          items:
            $ref: "#/components/schemas/DatasetImportRowError"
        imported_count: { type: integer }
        version:
          $ref: "#/components/schemas/DatasetVersion"
        examples:
          type: array
          items:
            $ref: "#/components/schemas/DatasetExample"

    StartDatasetEvalInput:
      type: object
      required: [version_id, challenge_pack_version_id, agent_deployment_ids]
      properties:
        version_id: { type: string, format: uuid }
        challenge_pack_version_id: { type: string, format: uuid }
        challenge_id:
          type: string
          description: Challenge key to bind dataset examples to.
        challenge_key:
          type: string
          description: Alias for challenge_id.
        mapping:
          type: object
          additionalProperties: true
        agent_deployment_ids:
          type: array
          minItems: 1
          items: { type: string, format: uuid }
        name: { type: string }

    DatasetVersionInputSet:
      type: object
      required: [id, dataset_id, dataset_version_id, challenge_pack_version_id, challenge_identity_id, challenge_key, challenge_input_set_id, input_key, input_checksum, mapping, created_at, updated_at]
      properties:
        id: { type: string, format: uuid }
        dataset_id: { type: string, format: uuid }
        dataset_version_id: { type: string, format: uuid }
        challenge_pack_version_id: { type: string, format: uuid }
        challenge_identity_id: { type: string, format: uuid }
        challenge_key: { type: string }
        challenge_input_set_id: { type: string, format: uuid }
        input_key: { type: string }
        input_checksum: { type: string }
        mapping:
          type: object
          additionalProperties: true
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    StartDatasetEvalResponse:
      type: object
      required: [run, dataset_version_input_set]
      properties:
        run:
          $ref: "#/components/schemas/CreateRunResponse"
        dataset_version_input_set:
          $ref: "#/components/schemas/DatasetVersionInputSet"

    DatasetEvalResult:
      type: object
      required: [dataset_example_id, dataset_version_id, challenge_input_set_id]
      properties:
        dataset_example_id: { type: string, format: uuid }
        dataset_version_id: { type: string, format: uuid }
        challenge_input_set_id: { type: string, format: uuid }
        run_id: { type: string, format: uuid }
        run_agent_id: { type: string, format: uuid }
        verdict: { type: string }
        normalized_score: { type: number }
        judged_at: { type: string, format: date-time }

    ListDatasetResultsResponse:
      type: object
      required: [items, total, limit, offset]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/DatasetEvalResult"
        total: { type: integer, format: int64 }
        limit: { type: integer, format: int32 }
        offset: { type: integer, format: int32 }

    CreateDatasetBaselineInput:
      type: object
      required: [run_id]
      properties:
        run_id: { type: string, format: uuid }
        agent_deployment_id: { type: string, format: uuid }
        label: { type: string }

    DatasetBaseline:
      type: object
      required:
        [id, dataset_id, dataset_version_id, challenge_pack_version_id, challenge_key, run_id, metrics, example_outcomes, created_by, created_at]
      properties:
        id: { type: string, format: uuid }
        dataset_id: { type: string, format: uuid }
        dataset_version_id: { type: string, format: uuid }
        dataset_version_input_set_id: { type: string, format: uuid }
        challenge_pack_version_id: { type: string, format: uuid }
        challenge_key: { type: string }
        agent_deployment_id: { type: string, format: uuid }
        run_id: { type: string, format: uuid }
        pass_rate: { type: number }
        metrics:
          type: object
          additionalProperties: true
        example_outcomes:
          type: array
          items:
            type: object
            additionalProperties: true
        label: { type: string }
        created_by: { type: string, format: uuid }
        created_at: { type: string, format: date-time }

    ListDatasetBaselinesResponse:
      type: object
      required: [items, total, limit, offset]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/DatasetBaseline"
        total: { type: integer, format: int64 }
        limit: { type: integer, format: int32 }
        offset: { type: integer, format: int32 }

    DatasetRegressionSuiteLink:
      type: object
      required: [dataset_id, regression_suite_id, created_at, updated_at]
      properties:
        dataset_id: { type: string, format: uuid }
        regression_suite_id: { type: string, format: uuid }
        synced_version_id: { type: string, format: uuid }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    SyncDatasetRegressionSuiteInput:
      type: object
      required: [version_id, challenge_pack_version_id, challenge_key]
      properties:
        version_id: { type: string, format: uuid }
        challenge_pack_version_id: { type: string, format: uuid }
        challenge_key: { type: string }
        regression_suite_id: { type: string, format: uuid }
        suite_name: { type: string }

    SyncDatasetRegressionSuiteResult:
      type: object
      required: [link, suite, created_cases, skipped_cases, total_examples]
      properties:
        link:
          $ref: "#/components/schemas/DatasetRegressionSuiteLink"
        suite:
          $ref: "#/components/schemas/RegressionSuite"
        created_cases: { type: integer }
        skipped_cases: { type: integer }
        total_examples: { type: integer }

    StartDatasetGenerationInput:
      type: object
      required: [strategy, target_count, provider_account_id, model]
      properties:
        strategy:
          type: string
          enum:
            [
              self_instruct,
              self-instruct,
              agentic_self_instruct,
              agentic-self-instruct,
            ]
        target_count:
          type: integer
          minimum: 1
          maximum: 100
        provider_account_id: { type: string, format: uuid }
        model: { type: string }
        seeds_tag: { type: string }
        create_version: { type: boolean }
        version_label: { type: string }
        judge_provider_account_id:
          type: string
          format: uuid
          description: Required for agentic_self_instruct.
        judge_model:
          type: string
          description: Required for agentic_self_instruct.
        max_rounds_per_example:
          type: integer
          minimum: 0
          maximum: 15
        acceptance_mode:
          type: string
          enum: [judge, threshold]
        min_gap:
          type: number
          minimum: 0
          maximum: 1
        max_weak_score:
          type: number
          minimum: 0
          maximum: 1
        min_strong_score:
          type: number
          minimum: 0
          maximum: 1
        solver_mode:
          type: string
          enum: [judge_only, direct_provider]
        weak_provider_account_id:
          type: string
          format: uuid
          description: Required when solver_mode is direct_provider.
        weak_model:
          type: string
          description: Required when solver_mode is direct_provider.
        strong_provider_account_id:
          type: string
          format: uuid
          description: Required when solver_mode is direct_provider.
        strong_model:
          type: string
          description: Required when solver_mode is direct_provider.
        weak_rollouts:
          type: integer
          minimum: 1
          maximum: 5
        strong_rollouts:
          type: integer
          minimum: 1
          maximum: 5
        weak_deployment_id:
          type: string
          format: uuid
        strong_deployment_id:
          type: string
          format: uuid
        challenge_pack_version_id:
          type: string
          format: uuid
        challenge_key:
          type: string
        field_mapping:
          type: object
          additionalProperties: true

    DatasetGenerationJob:
      type: object
      required:
        - id
        - dataset_id
        - workspace_id
        - strategy
        - status
        - config
        - summary
        - target_count
        - generated_count
        - accepted_count
        - rejected_count
        - total_input_tokens
        - total_output_tokens
        - total_cost_usd
        - created_by
        - queued_at
        - created_at
        - updated_at
      properties:
        id: { type: string, format: uuid }
        dataset_id: { type: string, format: uuid }
        workspace_id: { type: string, format: uuid }
        strategy: { type: string }
        status:
          type: string
          enum: [queued, running, completed, failed]
        config: { type: object, additionalProperties: true }
        summary: { type: object, additionalProperties: true }
        target_count: { type: integer }
        generated_count: { type: integer }
        accepted_count: { type: integer }
        rejected_count: { type: integer }
        total_input_tokens: { type: integer, format: int64 }
        total_output_tokens: { type: integer, format: int64 }
        total_cost_usd: { type: number, format: double }
        version_id: { type: string, format: uuid }
        temporal_workflow_id: { type: string }
        temporal_run_id: { type: string }
        error_message: { type: string }
        created_by: { type: string, format: uuid }
        queued_at: { type: string, format: date-time }
        started_at: { type: string, format: date-time }
        finished_at: { type: string, format: date-time }
        failed_at: { type: string, format: date-time }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    DatasetGenerationRejection:
      type: object
      required: [id, job_id, reason_code, metadata, created_at]
      properties:
        id: { type: string, format: uuid }
        job_id: { type: string, format: uuid }
        reason_code: { type: string }
        reason_detail: { type: string }
        candidate_input: {}
        candidate_expected: {}
        metadata: { type: object, additionalProperties: true }
        created_at: { type: string, format: date-time }

    DatasetGenerationRejectionsResult:
      type: object
      required: [items, total, limit, offset]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/DatasetGenerationRejection"
        total: { type: integer, format: int64 }
        limit: { type: integer }
        offset: { type: integer }

    EvaluateDatasetGateInput:
      type: object
      required: [baseline_id, run_id]
      properties:
        baseline_id: { type: string, format: uuid }
        run_id: { type: string, format: uuid }
        agent_deployment_id: { type: string, format: uuid }
        min_pass_rate: { type: number, minimum: 0, maximum: 1 }
        max_regressions: { type: integer, minimum: 0 }

    DatasetGateRegression:
      type: object
      required: [dataset_example_id, reason]
      properties:
        dataset_example_id: { type: string, format: uuid }
        reason: { type: string }
        baseline_verdict: { type: string }
        candidate_verdict: { type: string }
        baseline_score: { type: number }
        candidate_score: { type: number }

    DatasetGateResult:
      type: object
      required: [pass, pass_rate, baseline_pass_rate, regression_count, regressions, evaluated_examples]
      properties:
        pass: { type: boolean }
        pass_rate: { type: number }
        baseline_pass_rate: { type: number }
        regression_count: { type: integer }
        regressions:
          type: array
          items:
            $ref: "#/components/schemas/DatasetGateRegression"
        evaluated_examples: { type: integer }
        failed_thresholds:
          type: array
          items: { type: string }

    EvaluateDatasetGateResponse:
      type: object
      required: [baseline, candidate_run_id, gate]
      properties:
        baseline:
          $ref: "#/components/schemas/DatasetBaseline"
        candidate_run_id: { type: string, format: uuid }
        gate:
          $ref: "#/components/schemas/DatasetGateResult"

    DatasetTraceImport:
      type: object
      required: [id, dataset_id, source_platform, candidate_count, status, created_by, created_at]
      properties:
        id: { type: string, format: uuid }
        dataset_id: { type: string, format: uuid }
        source_platform: { type: string }
        artifact_id: { type: string, format: uuid }
        candidate_count: { type: integer, format: int32 }
        status: { type: string }
        created_by: { type: string, format: uuid }
        created_at: { type: string, format: date-time }

    DatasetTraceCandidate:
      type: object
      required: [id, dataset_id, import_id, source_platform, input, metadata, tags, status, created_at, updated_at]
      properties:
        id: { type: string, format: uuid }
        dataset_id: { type: string, format: uuid }
        import_id: { type: string, format: uuid }
        source_platform: { type: string }
        source_trace_id: { type: string }
        source_run_id: { type: string, format: uuid }
        source_run_agent_id: { type: string, format: uuid }
        external_id: { type: string }
        input: {}
        output: {}
        expected: {}
        metadata:
          type: object
          additionalProperties: true
        tags:
          type: array
          items: { type: string }
        status:
          type: string
          enum: [pending, promoted, rejected]
        promoted_example_id: { type: string, format: uuid }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    DatasetTraceRedactionConfig:
      type: object
      properties:
        drop_metadata_keys:
          type: array
          items: { type: string }
        hash_metadata_keys:
          type: array
          items: { type: string }
        drop_metadata_paths:
          type: array
          items: { type: string }

    ImportDatasetTracesInput:
      type: object
      required: [source_platform]
      properties:
        source_platform:
          type: string
          enum: [otel, braintrust, langsmith, phoenix, agentclash]
        payload: {}
        run_id: { type: string, format: uuid }
        run_agent_id: { type: string, format: uuid }
        artifact_id: { type: string, format: uuid }
        redaction:
          $ref: "#/components/schemas/DatasetTraceRedactionConfig"

    ImportDatasetTracesResponse:
      type: object
      required: [import, candidates]
      properties:
        import:
          $ref: "#/components/schemas/DatasetTraceImport"
        candidates:
          type: array
          items:
            $ref: "#/components/schemas/DatasetTraceCandidate"

    ListDatasetTraceCandidatesResponse:
      type: object
      required: [candidates, total]
      properties:
        candidates:
          type: array
          items:
            $ref: "#/components/schemas/DatasetTraceCandidate"
        total: { type: integer, format: int64 }

    PromoteDatasetTraceCandidateInput:
      type: object
      properties:
        expected: {}
        tags:
          type: array
          items: { type: string }

    PromoteDatasetTraceCandidateResponse:
      type: object
      required: [candidate, example, version, created]
      properties:
        candidate:
          $ref: "#/components/schemas/DatasetTraceCandidate"
        example:
          $ref: "#/components/schemas/DatasetExample"
        version:
          $ref: "#/components/schemas/DatasetVersion"
        created: { type: boolean }

    ListDatasetsResponse:
      type: object
      required: [items, total, limit, offset]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/Dataset"
        total: { type: integer, format: int64 }
        limit: { type: integer, format: int32 }
        offset: { type: integer, format: int32 }

    ListDatasetExamplesResponse:
      type: object
      required: [items, total, limit, offset]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/DatasetExample"
        total: { type: integer, format: int64 }
        limit: { type: integer, format: int32 }
        offset: { type: integer, format: int32 }

    ListDatasetVersionsResponse:
      type: object
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/DatasetVersion"

    DatasetVersionDetail:
      type: object
      required: [version, examples]
      properties:
        version:
          $ref: "#/components/schemas/DatasetVersion"
        examples:
          type: array
          items:
            $ref: "#/components/schemas/DatasetExample"

    ErrorEnvelope:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message]
          properties:
            code:
              type: string
            message:
              type: string
            plan_key:
              type: string
              description: Present on billing entitlement gate failures.
            upgrade_target:
              type: string
              description: Suggested plan key for resolving a billing gate failure.

    RetryAgentHarnessExecutionRequest:
      type: object
      required: [idempotency_key]
      properties:
        idempotency_key:
          type: string
          minLength: 1
          description: Idempotency key scoped to the source execution retry.

    AgentHarnessFailureClass:
      type: string
      enum:
        - setup
        - auth
        - tool_misuse
        - incomplete_implementation
        - no_op_diff
        - test_failure
        - overbroad_diff
        - no_pr
        - judge_failure
        - timeout
        - policy_privacy
        - infrastructure
        - none
        - unknown

    AgentHarnessFailureEventRef:
      type: object
      required: [sequence_number, event_type, actor_type]
      properties:
        sequence_number:
          type: integer
          format: int64
        event_type:
          type: string
        actor_type:
          type: string
        artifact_id:
          type: string
          format: uuid
        payload:
          type: object
          additionalProperties: true

    AgentHarnessFailureReview:
      type: object
      required:
        - execution_id
        - agent_harness_id
        - workspace_id
        - status
        - suggested_class
        - suggested_source
        - suggested_confidence
        - effective_class
        - taxonomy
        - created_at
        - updated_at
      properties:
        execution_id:
          type: string
          format: uuid
        agent_harness_id:
          type: string
          format: uuid
        workspace_id:
          type: string
          format: uuid
        run_id:
          type: string
          format: uuid
        run_agent_id:
          type: string
          format: uuid
        status:
          type: string
        suggested_class:
          $ref: "#/components/schemas/AgentHarnessFailureClass"
        suggested_summary:
          type: string
        suggested_source:
          type: string
          enum: [rules, llm]
        suggested_confidence:
          type: number
        human_class:
          $ref: "#/components/schemas/AgentHarnessFailureClass"
        human_summary:
          type: string
        effective_class:
          $ref: "#/components/schemas/AgentHarnessFailureClass"
        effective_summary:
          type: string
        taxonomy:
          $ref: "#/components/schemas/FailureReviewTaxonomy"
        repository_url:
          type: string
        base_branch:
          type: string
        task_type:
          type: string
        harness_name:
          type: string
        harness_kind:
          type: string
        model:
          type: string
        template:
          type: string
        suite_id:
          type: string
          format: uuid
        suite_version_id:
          type: string
          format: uuid
        suite_task_id:
          type: string
          format: uuid
        scorecard_passed:
          type: boolean
        overall_score:
          type: number
        event_refs:
          type: array
          items:
            $ref: "#/components/schemas/AgentHarnessFailureEventRef"
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    UpdateAgentHarnessFailureReviewRequest:
      type: object
      properties:
        suggested_class:
          $ref: "#/components/schemas/AgentHarnessFailureClass"
        suggested_summary:
          type: string
        suggested_source:
          type: string
          enum: [rules, llm]
        suggested_confidence:
          type: number
        suggested_payload:
          type: object
          additionalProperties: true
        human_class:
          $ref: "#/components/schemas/AgentHarnessFailureClass"
        human_summary:
          type: string
        human_payload:
          type: object
          additionalProperties: true

    AgentHarnessFailureSummaryGroup:
      type: object
      required: [group_by, key, label, failure_class, count, latest_execution_id, latest_at]
      properties:
        group_by:
          type: string
          enum: [repository, task_type, harness, model, template, suite]
        key:
          type: string
        label:
          type: string
        failure_class:
          $ref: "#/components/schemas/AgentHarnessFailureClass"
        count:
          type: integer
        latest_execution_id:
          type: string
          format: uuid
        latest_at:
          type: string
          format: date-time
        suite_id:
          type: string
          format: uuid

    ListAgentHarnessFailureSummaryResponse:
      type: object
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/AgentHarnessFailureSummaryGroup"

    PromoteAgentHarnessExecutionRequest:
      type: object
      required: [suite_id, title]
      properties:
        suite_id:
          type: string
          format: uuid
        title:
          type: string
          minLength: 1
        public_prompt:
          type: string
        failure_class:
          $ref: "#/components/schemas/AgentHarnessFailureClass"
        failure_summary:
          type: string
        metadata:
          type: object
          additionalProperties: true

    AgentHarnessSuiteTask:
      type: object
      required: [id, suite_version_id, task_order, title, public_prompt, source_type, created_at, updated_at]
      properties:
        id:
          type: string
          format: uuid
        suite_version_id:
          type: string
          format: uuid
        task_order:
          type: integer
        title:
          type: string
        public_prompt:
          type: string
        source_type:
          type: string
        repository_url:
          type: string
        base_branch:
          type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    PromoteAgentHarnessExecutionResponse:
      type: object
      required: [suite, task]
      properties:
        suite:
          type: object
          additionalProperties: true
        task:
          $ref: "#/components/schemas/AgentHarnessSuiteTask"

    AgentHarnessSuiteRankingEnvelope:
      type: object
      required: [ranking]
      properties:
        ranking:
          $ref: "#/components/schemas/AgentHarnessSuiteRanking"

    AgentHarnessSuiteRanking:
      type: object
      required: [schema_version, suite_id, suite_version, k, computed_at, rankings, pairwise, evidence]
      additionalProperties: true
      properties:
        schema_version:
          type: string
        suite_id:
          type: string
          format: uuid
        suite_version:
          type: object
          additionalProperties: true
        k:
          type: integer
          minimum: 1
        computed_at:
          type: string
          format: date-time
        rankings:
          type: array
          items:
            type: object
            additionalProperties: true
        pairwise:
          type: array
          items:
            type: object
            additionalProperties: true
        evidence:
          type: object
          additionalProperties: true

    AgentHarnessExecution:
      type: object
      required: [id, organization_id, workspace_id, agent_harness_id, status, harness_snapshot, execution_config_snapshot, evaluation_config_snapshot, created_at, updated_at]
      properties:
        id:
          type: string
          format: uuid
        organization_id:
          type: string
          format: uuid
        workspace_id:
          type: string
          format: uuid
        agent_harness_id:
          type: string
          format: uuid
        run_id:
          type: string
          format: uuid
        run_agent_id:
          type: string
          format: uuid
        evaluation_spec_id:
          type: string
          format: uuid
        temporal_workflow_id:
          type: string
        temporal_run_id:
          type: string
        retry_of_execution_id:
          type: string
          format: uuid
        created_by_user_id:
          type: string
          format: uuid
        status:
          type: string
          enum: [queued, provisioning, running, scoring, completed, failed, cancelled]
        harness_snapshot:
          type: object
          additionalProperties: true
        execution_config_snapshot:
          type: object
          additionalProperties: true
        evaluation_config_snapshot:
          type: object
          additionalProperties: true
        error_message:
          type: string
        failure_stage:
          type: string
        started_at:
          type: string
          format: date-time
        completed_at:
          type: string
          format: date-time
        cancelled_at:
          type: string
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    EmptyObject:
      type: object
      additionalProperties: false

    CIProfilesResponse:
      type: object
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/CIProfile"

    CIProfile:
      type: object
      required:
        - id
        - workspace_id
        - name
        - repository_full_name
        - default_branch
        - manifest_path
        - workflow_path
        - config
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        workspace_id:
          type: string
          format: uuid
        name:
          type: string
        repository_full_name:
          type: string
          example: acme/support-agent
        github_repository_id:
          type: integer
          format: int64
        github_installation_id:
          type: integer
          format: int64
        default_branch:
          type: string
          example: main
        manifest_path:
          type: string
          example: .agentclash/ci.yaml
        workflow_path:
          type: string
          example: .github/workflows/agentclash.yml
        config:
          type: object
          additionalProperties: true
          description: UI profile snapshot. Includes schemaVersion for forward-compatible migrations.
        created_by_user_id:
          type: string
          format: uuid
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    SaveCIProfileRequest:
      type: object
      required: [name, repository_full_name, default_branch, manifest_path, workflow_path, config]
      properties:
        name:
          type: string
          maxLength: 120
        repository_full_name:
          type: string
          example: acme/support-agent
        github_repository_id:
          type: integer
          format: int64
        github_installation_id:
          type: integer
          format: int64
        default_branch:
          type: string
          example: main
        manifest_path:
          type: string
          example: .agentclash/ci.yaml
        workflow_path:
          type: string
          example: .github/workflows/agentclash.yml
        config:
          type: object
          additionalProperties: true

    CreateCISetupPullRequestRequest:
      type: object
      required: [github_repository_id, base_branch, files]
      properties:
        github_repository_id:
          type: integer
          format: int64
        github_installation_id:
          type: integer
          format: int64
        base_branch:
          type: string
          example: main
        title:
          type: string
        body:
          type: string
        draft:
          type: boolean
        check_only:
          type: boolean
          description: Return target-file conflicts without creating a PR.
        overwrite_existing:
          type: boolean
          description: Allow generated files to replace existing target paths in the setup PR branch.
        files:
          type: array
          minItems: 1
          items:
            $ref: "#/components/schemas/CISetupPullRequestFile"

    CreateCISetupPullRequestResponse:
      type: object
      required: [base_branch, files]
      properties:
        pull_request:
          $ref: "#/components/schemas/GitHubPullRequest"
        branch:
          type: string
          description: Created branch name. Empty when no pull request was created.
        base_branch:
          type: string
        files:
          type: array
          items:
            $ref: "#/components/schemas/CISetupPullRequestFileSummary"
        conflicts:
          type: array
          items:
            $ref: "#/components/schemas/CISetupFileConflict"

    CISetupPullRequestFile:
      type: object
      required: [path, content]
      properties:
        path:
          type: string
          example: .agentclash/ci.yaml
        content:
          type: string

    CISetupPullRequestFileSummary:
      type: object
      required: [path]
      properties:
        path:
          type: string

    CISetupFileConflict:
      type: object
      required: [path, exists]
      properties:
        path:
          type: string
        exists:
          type: boolean
        sha:
          type: string

    GitHubPullRequest:
      type: object
      required: [number, html_url, state, draft]
      properties:
        number:
          type: integer
        html_url:
          type: string
          format: uri
        state:
          type: string
        draft:
          type: boolean

    ValidationResult:
      type: object
      required: [valid, errors]
      properties:
        valid:
          type: boolean
        errors:
          type: array
          items:
            $ref: "#/components/schemas/ValidationErrorDetail"

    ValidationErrorDetail:
      type: object
      required: [field, message]
      properties:
        field:
          type: string
        message:
          type: string

    RunStatus:
      type: string
      enum:
        - draft
        - queued
        - provisioning
        - running
        - scoring
        - completed
        - failed
        - cancelled

    EvalSessionStatus:
      type: string
      enum:
        - queued
        - running
        - aggregating
        - completed
        - failed
        - cancelled

    ExpandEvalSetRequest:
      type: object
      required: [workspace_id, manifest]
      properties:
        workspace_id:
          type: string
          format: uuid
        manifest:
          type: object
          description: evalset/v1 manifest document
          additionalProperties: true
        max_combinations:
          type: integer
          minimum: 1
          description: Optional override of the 2000 combination safety cap

    ScanEvalSetRequest:
      type: object
      properties:
        scanners:
          type: array
          items: { type: string }
          description: Built-in scanner names (e.g. reward-hacking)

    ScanEvalSetResponse:
      type: object
      required: [eval_set_id, status]
      properties:
        eval_set_id:
          type: string
          format: uuid
        status:
          type: string
          example: scan_started

    ScanFinding:
      type: object
      required: [id, workspace_id, eval_set_id, case_key, scanner, scanner_version, severity, status]
      properties:
        id: { type: string, format: uuid }
        workspace_id: { type: string, format: uuid }
        organization_id: { type: string, format: uuid }
        eval_set_id: { type: string, format: uuid }
        case_result_id: { type: string, format: uuid }
        matrix_key: { type: string }
        case_key: { type: string }
        scanner: { type: string }
        scanner_version: { type: string }
        severity: { type: string, enum: [low, medium, high, critical] }
        category: { type: string }
        evidence: { type: string }
        confidence: { type: number }
        status: { type: string, enum: [open, triaged, dismissed] }
        status_updated_by: { type: string, format: uuid }
        status_updated_at: { type: string, format: date-time }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    ListScanFindingsResponse:
      type: object
      required: [findings]
      properties:
        findings:
          type: array
          items:
            $ref: "#/components/schemas/ScanFinding"

    UpdateScanFindingStatusRequest:
      type: object
      required: [status]
      properties:
        status:
          type: string
          enum: [open, triaged, dismissed]

    EvalSetCombination:
      type: object
      required: [matrix_key, pack_ref, agent_ref, repeat]
      properties:
        matrix_key:
          type: string
        pack_ref:
          type: string
          description: challenge_pack_version UUID
        agent_ref:
          type: string
          description: agent_deployment UUID (stable identity; never the display label)
        agent_label:
          type: string
          description: Optional human-readable agent axis label from the manifest
        model_ref:
          type: string
        repeat:
          type: integer
        seed:
          type: integer
          format: int64
        case_fanout:
          type: boolean

    EvalSetExpansionReport:
      type: object
      required: [name, combinations, count, pack_count, agent_count, repeats]
      properties:
        name:
          type: string
        combinations:
          type: array
          items:
            $ref: "#/components/schemas/EvalSetCombination"
        count:
          type: integer
        pack_count:
          type: integer
        agent_count:
          type: integer
        model_count:
          type: integer
        repeats:
          type: integer
        max_concurrent_runs:
          type: integer
        budget_usd:
          type: number
        case_fanout:
          type: boolean
        estimate:
          $ref: "#/components/schemas/EvalSetCostEstimate"
        warning:
          type: string
          description: Present when estimate.exceeds_budget is true.

    EvalSetStatus:
      type: string
      enum:
        - queued
        - expanding
        - running
        - aggregating
        - completed
        - failed
        - cancelled
        - budget_exceeded

    EvalSetCostEstimate:
      type: object
      properties:
        estimated_usd:
          type: number
        combinations:
          type: integer
        assumed_cases_per_pack:
          type: integer
        per_case_usd:
          type: number
        heuristic:
          type: string
        tolerance_pct:
          type: integer
        budget_usd:
          type: ["number", "null"]
        exceeds_budget:
          type: boolean

    EvalSet:
      type: object
      required: [id, workspace_id, organization_id, name, status, combination_count]
      properties:
        id:
          type: string
          format: uuid
        workspace_id:
          type: string
          format: uuid
        organization_id:
          type: string
          format: uuid
        name:
          type: string
        status:
          $ref: "#/components/schemas/EvalSetStatus"
        combination_count:
          type: integer
        max_concurrent_runs:
          type: integer
        budget_usd:
          type: ["number", "null"]
        spent_usd:
          type: number
        estimated_cost_usd:
          type: ["number", "null"]
        case_fanout:
          type: boolean
        failure_reason:
          type: ["string", "null"]
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    CreateEvalSetRequest:
      type: object
      required: [workspace_id, manifest]
      properties:
        workspace_id:
          type: string
          format: uuid
        manifest:
          type: object
          additionalProperties: true
        max_combinations:
          type: integer
          minimum: 1

    CreateEvalSetResponse:
      type: object
      required: [eval_set, eval_session_ids, combination_count]
      properties:
        eval_set:
          $ref: "#/components/schemas/EvalSet"
        eval_session_ids:
          type: array
          items:
            type: string
            format: uuid
        combination_count:
          type: integer
        workflow:
          type: string

    ListEvalSetsResponse:
      type: object
      required: [eval_sets, total]
      properties:
        eval_sets:
          type: array
          items:
            $ref: "#/components/schemas/EvalSet"
        total:
          type: integer
          format: int64

    EvalSetResult:
      type: object
      properties:
        eval_set_id:
          type: string
          format: uuid
        aggregate:
          type: object
          additionalProperties: true
        evidence:
          type: object
          additionalProperties: true
        session_count:
          type: integer
        run_count:
          type: integer

    GetEvalSetResponse:
      type: object
      required: [eval_set]
      properties:
        eval_set:
          $ref: "#/components/schemas/EvalSet"
        eval_session_ids:
          type: array
          items:
            type: string
            format: uuid
        result:
          $ref: "#/components/schemas/EvalSetResult"

    RunAgentStatus:
      type: string
      enum:
        - queued
        - ready
        - executing
        - evaluating
        - completed
        - failed

    ReplayState:
      type: string
      enum: [ready, pending, errored]

    RankingReadState:
      type: string
      enum: [ready, pending, errored]

    ComparisonReadState:
      type: string
      enum: [comparable, partial_evidence, not_comparable]

    # --- Health ---

    HealthResponse:
      type: object
      required: [ok, service]
      properties:
        ok:
          type: boolean
        service:
          type: string
          example: api-server

    ReadyResponse:
      type: object
      required: [ok, service, checks]
      properties:
        ok:
          type: boolean
        service:
          type: string
          example: api-server
        checks:
          type: object
          description: >-
            Per-dependency status keyed by dependency name. Values are "ok",
            "not configured", or "unreachable: <error>".
          additionalProperties:
            type: string
          example:
            postgres: ok
            temporal: ok

    # --- Session ---

    SessionResponse:
      type: object
      required: [user_id, organization_memberships, workspace_memberships]
      properties:
        user_id:
          type: string
          format: uuid
        workos_user_id:
          type: string
        email:
          type: string
        display_name:
          type: string
        organization_memberships:
          type: array
          items:
            $ref: "#/components/schemas/SessionOrganizationMembership"
        workspace_memberships:
          type: array
          items:
            $ref: "#/components/schemas/SessionWorkspaceMembership"

    SessionOrganizationMembership:
      type: object
      required: [organization_id, role]
      properties:
        organization_id:
          type: string
          format: uuid
        role:
          type: string

    SessionWorkspaceMembership:
      type: object
      required: [workspace_id, role]
      properties:
        workspace_id:
          type: string
          format: uuid
        role:
          type: string

    WorkspaceAccessCheckResponse:
      type: object
      required: [ok, workspace_id]
      properties:
        ok:
          type: boolean
        workspace_id:
          type: string
          format: uuid

    # --- CLI Auth ---

    CreateDeviceCodeResponse:
      type: object
      required:
        [
          device_code,
          user_code,
          verification_uri,
          verification_uri_complete,
          expires_in,
          interval,
        ]
      properties:
        device_code:
          type: string
        user_code:
          type: string
        verification_uri:
          type: string
          format: uri
          example: https://app.agentclash.dev/auth/device
        verification_uri_complete:
          type: string
          format: uri
          example: https://app.agentclash.dev/auth/device?user_code=ABCD-EFGH
        expires_in:
          type: integer
          format: int32
        interval:
          type: integer
          format: int32

    PollDeviceTokenRequest:
      type: object
      required: [device_code]
      properties:
        device_code:
          type: string

    PollDeviceTokenResponse:
      type: object
      required: [token]
      properties:
        token:
          type: string

    ApproveDeviceCodeRequest:
      type: object
      required: [user_code]
      properties:
        user_code:
          type: string

    DenyDeviceCodeRequest:
      type: object
      required: [user_code]
      properties:
        user_code:
          type: string

    DeviceApprovalResponse:
      type: object
      required: [ok]
      properties:
        ok:
          type: boolean

    CreateCLITokenRequest:
      type: object
      properties:
        name:
          type: string

    CreateCLITokenResponse:
      type: object
      required: [id, token, name, created_at]
      properties:
        id:
          type: string
          format: uuid
        token:
          type: string
        name:
          type: string
        created_at:
          type: string
          format: date-time
        expires_at:
          type: string
          format: date-time

    CLITokenSummary:
      type: object
      required: [id, name, created_at]
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        last_used_at:
          type: string
          format: date-time
        expires_at:
          type: string
          format: date-time
        created_at:
          type: string
          format: date-time

    ListCLITokensResponse:
      type: object
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/CLITokenSummary"

    # --- User Profile ---

    UserProfileResponse:
      type: object
      required: [user_id, organizations]
      properties:
        user_id:
          type: string
          format: uuid
        workos_user_id:
          type: string
        email:
          type: string
        display_name:
          type: string
        organizations:
          type: array
          items:
            $ref: "#/components/schemas/UserMeOrganization"

    UserMeOrganization:
      type: object
      required: [id, name, slug, role, workspaces]
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        slug:
          type: string
        role:
          type: string
        workspaces:
          type: array
          items:
            $ref: "#/components/schemas/UserMeWorkspace"

    UserMeWorkspace:
      type: object
      required: [id, name, slug, role]
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        slug:
          type: string
        role:
          type: string

    # --- Onboarding ---

    OnboardRequest:
      type: object
      required: [organization_name, workspace_name]
      properties:
        organization_name:
          type: string
        organization_slug:
          type: string
        workspace_name:
          type: string
        workspace_slug:
          type: string

    OnboardResponse:
      type: object
      required: [organization, workspace]
      properties:
        organization:
          $ref: "#/components/schemas/OrganizationResponse"
        workspace:
          $ref: "#/components/schemas/WorkspaceDetailResponse"

    # --- Organizations ---

    CreateOrganizationRequest:
      type: object
      required: [name]
      properties:
        name:
          type: string
        slug:
          type: string

    UpdateOrganizationRequest:
      type: object
      properties:
        name:
          type: string
        status:
          type: string
          enum: [active, archived]

    OrganizationResponse:
      type: object
      required: [id, name, slug, status, created_at, updated_at]
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        slug:
          type: string
        status:
          type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    ListOrganizationsResponse:
      type: object
      required: [items, total, limit, offset]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/OrganizationResponse"
        total:
          type: integer
          format: int64
        limit:
          type: integer
          format: int32
        offset:
          type: integer
          format: int32

    # --- Workspaces ---

    CreateWorkspaceRequest:
      type: object
      required: [name]
      properties:
        name:
          type: string
        slug:
          type: string

    UpdateWorkspaceRequest:
      type: object
      properties:
        name:
          type: string
        status:
          type: string
          enum: [active, archived]
        public_packs:
          type: boolean

    WorkspaceDetailResponse:
      type: object
      required: [id, organization_id, name, slug, status, public_packs, created_at, updated_at]
      properties:
        id:
          type: string
          format: uuid
        organization_id:
          type: string
          format: uuid
        name:
          type: string
        slug:
          type: string
        status:
          type: string
        public_packs:
          type: boolean
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    ListWorkspacesResponse:
      type: object
      required: [items, total, limit, offset]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/WorkspaceDetailResponse"
        total:
          type: integer
          format: int64
        limit:
          type: integer
          format: int32
        offset:
          type: integer
          format: int32

    # --- Billing ---

    BillingPlanKey:
      type: string
      enum: [free, pro, team, enterprise]

    BillingPeriod:
      type: string
      enum: [monthly, yearly, custom]

    BillingLimit:
      type: object
      properties:
        value:
          anyOf:
            - type: integer
            - type: "null"
        per_seat:
          type: boolean
        unlimited:
          type: boolean
        custom:
          type: boolean

    BillingPlanLimits:
      type: object
      required:
        [
          seats,
          workspaces,
          races_per_workspace_month,
          max_models_per_race,
          replay_retention_days,
          concurrent_races,
        ]
      properties:
        seats:
          $ref: "#/components/schemas/BillingLimit"
        workspaces:
          $ref: "#/components/schemas/BillingLimit"
        races_per_workspace_month:
          $ref: "#/components/schemas/BillingLimit"
        max_models_per_race:
          $ref: "#/components/schemas/BillingLimit"
        replay_retention_days:
          $ref: "#/components/schemas/BillingLimit"
        concurrent_races:
          $ref: "#/components/schemas/BillingLimit"

    BillingPlan:
      type: object
      required:
        [
          key,
          display_name,
          minimum_seats,
          default_seats,
          billing_periods,
          limits,
          feature_flags,
        ]
      properties:
        key:
          $ref: "#/components/schemas/BillingPlanKey"
        display_name:
          type: string
        minimum_seats:
          type: integer
          minimum: 1
        default_seats:
          type: integer
          minimum: 1
        billing_periods:
          type: array
          items:
            $ref: "#/components/schemas/BillingPeriod"
        limits:
          $ref: "#/components/schemas/BillingPlanLimits"
        feature_flags:
          type: object
          additionalProperties:
            type: boolean
        upgrade_target:
          $ref: "#/components/schemas/BillingPlanKey"
        dodo_product_ids:
          type: object
          additionalProperties:
            type: string

    ListBillingPlansResponse:
      type: object
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/BillingPlan"

    BillingEntitlements:
      type: object
      required:
        [
          plan_key,
          billing_period,
          status,
          seat_quantity,
          seats_limit,
          workspaces_limit,
          races_per_workspace_month,
          max_models_per_race,
          replay_retention_days,
          concurrent_races,
          feature_flags,
        ]
      properties:
        plan_key:
          $ref: "#/components/schemas/BillingPlanKey"
        billing_period:
          $ref: "#/components/schemas/BillingPeriod"
        status:
          type: string
          enum: [active, trialing, expired, inactive, renewed]
        seat_quantity:
          type: integer
          minimum: 1
        seats_limit:
          anyOf:
            - type: integer
            - type: "null"
        workspaces_limit:
          anyOf:
            - type: integer
            - type: "null"
        races_per_workspace_month:
          anyOf:
            - type: integer
            - type: "null"
        max_models_per_race:
          anyOf:
            - type: integer
            - type: "null"
        replay_retention_days:
          anyOf:
            - type: integer
            - type: "null"
        concurrent_races:
          anyOf:
            - type: integer
            - type: "null"
        feature_flags:
          type: object
          additionalProperties:
            type: boolean
        upgrade_target:
          $ref: "#/components/schemas/BillingPlanKey"
        expires_at:
          type: string
          format: date-time

    BillingSubscriptionResponse:
      type: object
      required:
        [
          id,
          organization_id,
          dodo_subscription_id,
          dodo_product_id,
          plan_key,
          billing_period,
          status,
          cancel_at_next_billing_date,
          seat_quantity,
          addon_quantities,
          created_at,
          updated_at,
        ]
      properties:
        id:
          type: string
          format: uuid
        organization_id:
          type: string
          format: uuid
        dodo_subscription_id:
          type: string
        dodo_customer_id:
          type: string
        dodo_product_id:
          type: string
        plan_key:
          $ref: "#/components/schemas/BillingPlanKey"
        billing_period:
          $ref: "#/components/schemas/BillingPeriod"
        status:
          type: string
        next_billing_date:
          type: string
          format: date-time
        cancel_at_next_billing_date:
          type: boolean
        cancelled_at:
          type: string
          format: date-time
        expires_at:
          type: string
          format: date-time
        trial_period_days:
          type: integer
        seat_quantity:
          type: integer
          minimum: 1
        addon_quantities:
          type: object
          additionalProperties: true
        latest_dodo_event_at:
          type: string
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    BillingAccountResponse:
      type: object
      required: [id, organization_id, status, created_at, updated_at]
      properties:
        id:
          type: string
          format: uuid
        organization_id:
          type: string
          format: uuid
        dodo_customer_id:
          type: string
        billing_email:
          type: string
          format: email
        status:
          type: string
          enum: [active, inactive]
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    BillingCheckoutIntentResponse:
      type: object
      required:
        [
          id,
          organization_id,
          requested_plan_key,
          billing_period,
          seat_quantity,
          return_url,
          checkout_url,
          status,
          metadata,
          created_at,
          updated_at,
        ]
      properties:
        id:
          type: string
          format: uuid
        organization_id:
          type: string
          format: uuid
        requested_plan_key:
          type: string
          enum: [pro, team]
        billing_period:
          type: string
          enum: [monthly, yearly]
        seat_quantity:
          type: integer
          minimum: 1
        return_url:
          type: string
          format: uri
        checkout_url:
          type: string
          format: uri
        dodo_checkout_session_id:
          type: string
        status:
          type: string
          enum: [created, pending, completed, cancelled, expired]
        metadata:
          type: object
          additionalProperties: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    BillingOverviewResponse:
      type: object
      required: [entitlements]
      properties:
        entitlements:
          $ref: "#/components/schemas/BillingEntitlements"
        account:
          $ref: "#/components/schemas/BillingAccountResponse"
        subscription:
          $ref: "#/components/schemas/BillingSubscriptionResponse"
        latest_checkout_intent:
          $ref: "#/components/schemas/BillingCheckoutIntentResponse"

    StartBillingTrialRequest:
      type: object
      required: [plan_key]
      properties:
        plan_key:
          type: string
          enum: [pro, team]
        billing_period:
          type: string
          enum: [monthly, yearly]
          default: monthly

    CreateBillingCheckoutRequest:
      type: object
      required: [plan_key, billing_period, seat_quantity, return_url]
      properties:
        plan_key:
          type: string
          enum: [pro, team]
        billing_period:
          type: string
          enum: [monthly, yearly]
        seat_quantity:
          type: integer
          minimum: 1
        return_url:
          type: string
          format: uri

    CreateBillingCheckoutResponse:
      type: object
      required:
        [
          checkout_intent_id,
          checkout_url,
          plan_key,
          billing_period,
          seat_quantity,
        ]
      properties:
        checkout_intent_id:
          type: string
          format: uuid
        checkout_url:
          type: string
          format: uri
        plan_key:
          type: string
          enum: [pro, team]
        billing_period:
          type: string
          enum: [monthly, yearly]
        seat_quantity:
          type: integer
          minimum: 1

    CreateBillingPortalResponse:
      type: object
      required: [portal_url]
      properties:
        portal_url:
          type: string
          format: uri

    WorkspaceUsageSnapshot:
      type: object
      required: [workspace_id, race_count, active_runs, window_start, window_end]
      properties:
        workspace_id:
          type: string
          format: uuid
        race_count:
          type: integer
        active_runs:
          type: integer
        window_start:
          type: string
          format: date-time
        window_end:
          type: string
          format: date-time

    BillingGateDecision:
      type: object
      required: [allowed, plan_key]
      properties:
        allowed:
          type: boolean
        code:
          type: string
          enum:
            [
              plan_limit_exceeded,
              quota_exceeded,
              concurrency_limit_exceeded,
              seat_limit_exceeded,
              feature_not_entitled,
              entitlement_expired,
              entitlement_inactive,
            ]
        message:
          type: string
        plan_key:
          $ref: "#/components/schemas/BillingPlanKey"
        upgrade_target:
          $ref: "#/components/schemas/BillingPlanKey"
        limit:
          anyOf:
            - type: integer
            - type: "null"
        used:
          type: integer
        remaining:
          anyOf:
            - type: integer
            - type: "null"
        reset_at:
          type: string
          format: date-time
        expires_at:
          type: string
          format: date-time

    WorkspaceGateSummary:
      type: object
      required: [run]
      properties:
        run:
          $ref: "#/components/schemas/BillingGateDecision"

    WorkspaceEntitlementsResponse:
      type: object
      required: [organization_id, workspace_id, entitlements, usage, gates]
      properties:
        organization_id:
          type: string
          format: uuid
        workspace_id:
          type: string
          format: uuid
        entitlements:
          $ref: "#/components/schemas/BillingEntitlements"
        usage:
          $ref: "#/components/schemas/WorkspaceUsageSnapshot"
        gates:
          $ref: "#/components/schemas/WorkspaceGateSummary"

    QuotaCounter:
      type: object
      required: [used]
      properties:
        used:
          type: integer
        limit:
          type: integer
        remaining:
          type: integer
        reset_at:
          type: string
          format: date-time

    WorkspaceQuotaResponse:
      type: object
      required:
        [
          organization_id,
          workspace_id,
          plan_key,
          billing_period,
          status,
          monthly_races,
          concurrent_races,
          seats,
          window_start,
          window_end,
        ]
      properties:
        organization_id:
          type: string
          format: uuid
        workspace_id:
          type: string
          format: uuid
        plan_key:
          $ref: "#/components/schemas/BillingPlanKey"
        billing_period:
          type: string
        status:
          type: string
        monthly_races:
          $ref: "#/components/schemas/QuotaCounter"
        concurrent_races:
          $ref: "#/components/schemas/QuotaCounter"
        seats:
          $ref: "#/components/schemas/QuotaCounter"
        window_start:
          type: string
          format: date-time
        window_end:
          type: string
          format: date-time

    ProcessDodoWebhookResponse:
      type: object
      required: [duplicate, event_type]
      properties:
        duplicate:
          type: boolean
        event_type:
          type: string

    # --- Organization Memberships ---

    InviteOrgMemberRequest:
      type: object
      required: [email, role]
      properties:
        email:
          type: string
        role:
          type: string
          enum: [org_admin, org_member]

    UpdateOrgMembershipRequest:
      type: object
      properties:
        role:
          type: string
          enum: [org_admin, org_member]
        status:
          type: string

    OrgMembershipResponse:
      type: object
      required: [id, organization_id, user_id, email, display_name, role, membership_status, created_at]
      properties:
        id:
          type: string
          format: uuid
        organization_id:
          type: string
          format: uuid
        user_id:
          type: string
          format: uuid
        email:
          type: string
        display_name:
          type: string
        role:
          type: string
        membership_status:
          type: string
        created_at:
          type: string
          format: date-time
        accept_url:
          type: string
          format: uri
          description: Present while the membership is invited.

    ListOrgMembershipsResponse:
      type: object
      required: [items, total, limit, offset]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/OrgMembershipResponse"
        total:
          type: integer
          format: int64
        limit:
          type: integer
          format: int32
        offset:
          type: integer
          format: int32

    # --- Workspace Memberships ---

    InviteWorkspaceMemberRequest:
      type: object
      required: [email, role]
      properties:
        email:
          type: string
        role:
          type: string
          enum: [workspace_admin, workspace_member, workspace_viewer]

    UpdateWorkspaceMembershipRequest:
      type: object
      properties:
        role:
          type: string
          enum: [workspace_admin, workspace_member, workspace_viewer]
        status:
          type: string

    WorkspaceMembershipDetailResponse:
      type: object
      required: [id, workspace_id, organization_id, user_id, email, display_name, role, membership_status, created_at]
      properties:
        id:
          type: string
          format: uuid
        workspace_id:
          type: string
          format: uuid
        organization_id:
          type: string
          format: uuid
        user_id:
          type: string
          format: uuid
        email:
          type: string
        display_name:
          type: string
        role:
          type: string
        membership_status:
          type: string
        created_at:
          type: string
          format: date-time
        accept_url:
          type: string
          format: uri
          description: Present while the membership is invited.

    ListWorkspaceMembershipsResponse:
      type: object
      required: [items, total, limit, offset]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/WorkspaceMembershipDetailResponse"
        total:
          type: integer
          format: int64
        limit:
          type: integer
          format: int32
        offset:
          type: integer
          format: int32

    # --- Workspace Secrets ---

    WorkspaceSecretSummary:
      type: object
      required: [key, created_at, updated_at]
      properties:
        key:
          type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        created_by:
          type: string
          format: uuid
        updated_by:
          type: string
          format: uuid

    ListWorkspaceSecretsResponse:
      type: object
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/WorkspaceSecretSummary"

    UpsertWorkspaceSecretRequest:
      type: object
      required: [value]
      properties:
        value:
          type: string

    # --- Runtime Profiles ---

    CreateRuntimeProfileRequest:
      type: object
      required: [name, execution_target]
      properties:
        name:
          type: string
        execution_target:
          type: string
          enum: [native, hosted_external, prompt_eval, responses, multi_turn]
        trace_mode:
          type: string
        max_iterations:
          type: integer
          format: int32
        max_tool_calls:
          type: integer
          format: int32
        step_timeout_seconds:
          type: integer
          format: int32
        run_timeout_seconds:
          type: integer
          format: int32
        profile_config:
          type: object
          additionalProperties: true

    RuntimeProfileResponse:
      type: object
      required: [id, name, slug, execution_target, trace_mode, max_iterations, max_tool_calls, step_timeout_seconds, run_timeout_seconds, profile_config, created_at, updated_at]
      properties:
        id:
          type: string
          format: uuid
        workspace_id:
          type: string
          format: uuid
        name:
          type: string
        slug:
          type: string
        execution_target:
          type: string
          enum: [native, hosted_external, prompt_eval, responses, multi_turn]
        trace_mode:
          type: string
        max_iterations:
          type: integer
          format: int32
        max_tool_calls:
          type: integer
          format: int32
        step_timeout_seconds:
          type: integer
          format: int32
        run_timeout_seconds:
          type: integer
          format: int32
        profile_config:
          type: object
          additionalProperties: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    ListRuntimeProfilesResponse:
      type: object
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/RuntimeProfileResponse"

    # --- Provider Accounts ---

    CreateProviderAccountRequest:
      type: object
      required: [provider_key, name]
      allOf:
        - if:
            properties:
              provider_key:
                const: custom
            required: [provider_key]
          then:
            required: [base_url]
      properties:
        provider_key:
          type: string
          enum: [openai, anthropic, gemini, xai, openrouter, mistral, custom]
        name:
          type: string
        credential_reference:
          type: string
        api_key:
          type: string
          writeOnly: true
        base_url:
          type: string
          format: uri
          description: >
            HTTPS base URL for the provider API. Required when provider_key is
            custom and optional for supported providers whose default endpoint
            should be overridden.
        limits_config:
          type: object
          additionalProperties: true

    ProviderAccountResponse:
      type: object
      required: [id, provider_key, name, credential_reference, base_url, status, limits_config, created_at, updated_at]
      properties:
        id:
          type: string
          format: uuid
        workspace_id:
          type: string
          format: uuid
        provider_key:
          type: string
          enum: [openai, anthropic, gemini, xai, openrouter, mistral, custom]
        name:
          type: string
        credential_reference:
          type: string
        base_url:
          type: string
          description: >
            Normalized provider API base URL, or an empty string when the
            provider's default endpoint is used.
        status:
          type: string
        limits_config:
          type: object
          additionalProperties: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    ListProviderAccountsResponse:
      type: object
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/ProviderAccountResponse"

    ProviderAccountTestRequest:
      type: object
      properties:
        model:
          type: string
          description: Required for custom providers; optional otherwise.
        step_timeout_seconds:
          type: integer
          format: int32
          description: Uses the default when non-positive and is capped at 30 seconds.

    ProviderAccountTestResponse:
      type: object
      required: [account_id, provider_key, model, passed, status, duration_ms]
      properties:
        account_id:
          type: string
          format: uuid
        provider_key:
          type: string
          enum: [openai, anthropic, gemini, xai, openrouter, mistral, custom]
        model:
          type: string
        provider_model_id:
          type: string
        passed:
          type: boolean
        status:
          type: string
          enum: [passed, failed]
        code:
          type: string
        message:
          type: string
        retryable:
          type: boolean
        duration_ms:
          type: integer
          format: int64

    ProviderConnectionModel:
      type: object
      required: [id, display_name, input_cost_per_mtok, output_cost_per_mtok, pricing_source]
      properties:
        id:
          type: string
        display_name:
          type: string
        input_cost_per_mtok:
          type: number
          format: double
          description: USD per million input tokens. Picker display only; not used for scoring.
        output_cost_per_mtok:
          type: number
          format: double
          description: USD per million output tokens. Picker display only; not used for scoring.
        pricing_source:
          type: string
          enum: [live, static, unknown]

    ProviderConnectionModelsResponse:
      type: object
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/ProviderConnectionModel"

    # --- Tools ---

    CreateToolRequest:
      type: object
      required: [name, tool_kind]
      properties:
        name:
          type: string
        tool_kind:
          type: string
          enum: [primitive, composed]
          description: Whether the tool is a single-operation primitive or a multi-step composed tool.
        capability_key:
          type: string
          description: Optional stable capability identifier. Defaults to the generated slug when omitted.
        definition:
          type: object
          additionalProperties: true
          description: Canonical tool definition (see toolspec). Validated before persistence.

    UpdateToolRequest:
      type: object
      description: >-
        Mutable fields of a tool. tool_kind and slug are immutable. Omitted name,
        capability_key and lifecycle_status keep their existing values.
      properties:
        name:
          type: string
        capability_key:
          type: string
        definition:
          type: object
          additionalProperties: true
        lifecycle_status:
          type: string
          enum: [active, disabled]

    ToolResponse:
      type: object
      required: [id, name, slug, tool_kind, capability_key, definition, lifecycle_status, created_at, updated_at]
      properties:
        id:
          type: string
          format: uuid
        workspace_id:
          type: string
          format: uuid
        name:
          type: string
        slug:
          type: string
        tool_kind:
          type: string
        capability_key:
          type: string
        definition:
          type: object
          additionalProperties: true
        lifecycle_status:
          type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    ListToolsResponse:
      type: object
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/ToolResponse"

    ToolPrimitive:
      type: object
      required: [name, description, kind, parameters, delegatable]
      properties:
        name:
          type: string
        description:
          type: string
        kind:
          type: string
          enum: [core, file, data, network, build, shell]
        parameters:
          type: object
          additionalProperties: true
          description: JSON Schema describing the primitive's arguments.
        delegatable:
          type: boolean
          description: Whether a custom tool may delegate to or compose this primitive.

    ListToolPrimitivesResponse:
      type: object
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/ToolPrimitive"

    ToolLibraryEntry:
      type: object
      required: [slug, name, category, description, tags, tool_kind, delivery, has_live, definition]
      properties:
        slug:
          type: string
          description: Stable catalog slug used to instantiate this entry.
        name:
          type: string
        category:
          type: string
        description:
          type: string
        tags:
          type: array
          items:
            type: string
        tool_kind:
          type: string
          enum: [primitive, composed]
        delivery:
          type: string
          enum: [live, mock]
          description: >-
            "live" means the default definition runs for real; "mock" returns a
            canned response (a real call may be available via the live variant).
        requires_secret:
          type: string
          description: Workspace secret the live variant needs, if any.
        has_live:
          type: boolean
          description: Whether a real (live) HTTP variant is bundled.
        definition:
          type: object
          additionalProperties: true
          description: The canonical, instantly-usable tool definition.

    ListToolLibraryResponse:
      type: object
      required: [items, categories]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/ToolLibraryEntry"
        categories:
          type: array
          items:
            type: string

    CreateToolsFromLibraryRequest:
      type: object
      required: [entries]
      properties:
        entries:
          type: array
          minItems: 1
          maxItems: 100
          items:
            type: object
            required: [slug]
            properties:
              slug:
                type: string
              variant:
                type: string
                enum: [default, live]
                description: Which variant to instantiate (defaults to "default").
              conflict:
                type: string
                enum: [skip, suffix]
                description: How to handle an existing tool with the same name (defaults to "skip").

    LibrarySkip:
      type: object
      required: [slug, reason]
      properties:
        slug:
          type: string
        reason:
          type: string

    CreateToolsFromLibraryResponse:
      type: object
      required: [items, skipped]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/ToolResponse"
        skipped:
          type: array
          items:
            $ref: "#/components/schemas/LibrarySkip"

    # --- Routing Policies ---

    CreateRoutingPolicyRequest:
      type: object
      required: [name, policy_kind]
      properties:
        name:
          type: string
        policy_kind:
          type: string
        config:
          type: object
          additionalProperties: true

    RoutingPolicyResponse:
      type: object
      required: [id, name, policy_kind, config, created_at, updated_at]
      properties:
        id:
          type: string
          format: uuid
        workspace_id:
          type: string
          format: uuid
        name:
          type: string
        policy_kind:
          type: string
        config:
          type: object
          additionalProperties: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    ListRoutingPoliciesResponse:
      type: object
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/RoutingPolicyResponse"

    # --- Spend Policies ---

    CreateSpendPolicyRequest:
      type: object
      required: [name, window_kind]
      properties:
        name:
          type: string
        currency_code:
          type: string
        window_kind:
          type: string
        soft_limit:
          type: number
          format: double
        hard_limit:
          type: number
          format: double
        config:
          type: object
          additionalProperties: true

    SpendPolicyResponse:
      type: object
      required: [id, name, currency_code, window_kind, config, created_at, updated_at]
      properties:
        id:
          type: string
          format: uuid
        workspace_id:
          type: string
          format: uuid
        name:
          type: string
        currency_code:
          type: string
        window_kind:
          type: string
        soft_limit:
          type: number
          format: double
        hard_limit:
          type: number
          format: double
        config:
          type: object
          additionalProperties: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    ListSpendPoliciesResponse:
      type: object
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/SpendPolicyResponse"

    # --- Runs ---

    RunLinks:
      type: object
      required: [self, agents]
      properties:
        self:
          type: string
        agents:
          type: string

    RunCIMetadata:
      type: object
      description: Source control and CI metadata attached to automation-created runs.
      properties:
        provider:
          type: string
          example: github_actions
        repository:
          type: string
          example: acme/agent
        pull_request_number:
          type: integer
          minimum: 1
        branch:
          type: string
        ref:
          type: string
        commit_sha:
          type: string
        workflow:
          type: string
        workflow_run_id:
          type: string
        workflow_run_attempt:
          type: string
        workflow_run_url:
          type: string
          format: uri
        event_name:
          type: string
        default_branch:
          type: string

    CreateRunRequest:
      type: object
      required: [workspace_id, challenge_pack_version_id, agent_deployment_ids]
      properties:
        workspace_id:
          type: string
          format: uuid
        challenge_pack_version_id:
          type: string
          format: uuid
        challenge_input_set_id:
          type: string
          format: uuid
          description: >
            Optional. If omitted and the challenge pack has exactly one input
            set, it is auto-selected. If omitted and the pack has multiple
            input sets, the request is rejected with code
            missing_challenge_input_set_id.
        name:
          type: string
        agent_deployment_ids:
          type: array
          minItems: 1
          items:
            type: string
            format: uuid
        regression_suite_ids:
          type: array
          items:
            type: string
            format: uuid
        regression_case_ids:
          type: array
          items:
            type: string
            format: uuid
        official_pack_mode:
          type: string
          enum: [full, suite_only]
          description: >
            Optional. Defaults to `full`. Use `suite_only` to execute only the
            resolved regression selection instead of unioning it with the full
            official pack case set.
        include_proposed_regressions:
          type: boolean
          default: false
          description: >
            Optional. When true, explicitly selected regression suites and cases
            may include proposed regression cases for validation runs. Release
            gates still enforce only active regression cases.
        race_context:
          type: boolean
          default: false
          description: >
            Optional. When true, the executor injects live peer standings into
            each agent's message stream during the run. Requires at least two
            agents; `true` with a single agent is rejected with code
            `invalid_race_context`. See issue #400.
        race_context_min_step_gap:
          type: integer
          minimum: 1
          maximum: 10
          description: >
            Optional. Overrides the default cadence (minimum steps between
            standings injections). Null / omitted uses the backend default.
            Only meaningful when `race_context` is true.
        max_iterations:
          type: integer
          minimum: 1
          maximum: 1000
          description: >
            Optional. Overrides the max native-agent iteration budget for this
            run. If omitted, AgentClash uses the challenge pack
            `evaluation_spec.runtime_limits.max_iterations` default when
            present, otherwise the selected runtime profile default.
        ci_metadata:
          $ref: "#/components/schemas/RunCIMetadata"
          description: >
            Optional CI source metadata attached by AgentClash CI integrations.

    CreateEvalSessionRequest:
      type: object
      required: [workspace_id, challenge_pack_version_id, eval_session]
      properties:
        workspace_id:
          type: string
          format: uuid
        challenge_pack_version_id:
          type: string
          format: uuid
        challenge_input_set_id:
          type: string
          format: uuid
          description: >
            Optional. If omitted and the challenge pack has exactly one input
            set, it is auto-selected. If omitted and the pack has multiple
            input sets, the request is rejected with code
            missing_challenge_input_set_id.
        participants:
          type: array
          minItems: 1
          description: >
            Uniform participants used for every child run. May be omitted when
            `eval_session.run_matrix` supplies participants per child run.
          items:
            $ref: "#/components/schemas/CreateEvalSessionParticipant"
        execution_mode:
          type: string
          enum: [single_agent, comparison]
          description: >
            Optional. Defaults to `single_agent` for one participant and
            `comparison` for multiple participants.
        name:
          type: string
        max_iterations:
          type: integer
          minimum: 1
          maximum: 1000
          description: >
            Optional. Applies the same native-agent iteration budget override
            to every child run in the eval session.
        eval_session:
          $ref: "#/components/schemas/CreateEvalSessionConfig"

    CreateEvalSessionParticipant:
      type: object
      required: [agent_deployment_id, label]
      properties:
        agent_deployment_id:
          type: string
          format: uuid
          description: Exact deployment to run as this eval-session participant.
        agent_build_version_id:
          type: string
          format: uuid
          deprecated: true
          description: Deprecated compatibility field. Prefer `agent_deployment_id`.
        label:
          type: string
          minLength: 1

    CreateEvalSessionConfig:
      type: object
      required: [repetitions, aggregation, routing_task_snapshot, schema_version]
      properties:
        repetitions:
          type: integer
          minimum: 1
          maximum: 100
        aggregation:
          $ref: "#/components/schemas/EvalSessionAggregationConfig"
        success_threshold:
          anyOf:
            - $ref: "#/components/schemas/EvalSessionSuccessThresholdConfig"
            - type: "null"
          description: Optional. Explicit `null` is treated the same as omission.
        routing_task_snapshot:
          $ref: "#/components/schemas/EvalSessionRoutingTaskSnapshotInput"
        reliability_weights:
          anyOf:
            - $ref: "#/components/schemas/EvalSessionReliabilityWeightsConfig"
            - type: "null"
          description: Optional. Explicit `null` is treated the same as omission.
        seed_fanout:
          $ref: "#/components/schemas/EvalSessionSeedFanoutConfig"
        run_matrix:
          type: array
          minItems: 1
          maxItems: 100
          description: >
            Explicit child-run matrix for race series. Length must equal
            `repetitions`. Cannot be combined with `seed_fanout`.
          items:
            $ref: "#/components/schemas/EvalSessionRunMatrixEntry"
        schema_version:
          type: integer
          minimum: 1

    EvalSessionRunMatrixEntry:
      type: object
      required: [key, participants]
      properties:
        key:
          type: string
          minLength: 1
          description: Stable client-generated key for this matrix cell.
        deployment_lineup:
          type: string
          minLength: 1
          description: Optional challenge-pack lineup name used for this child run.
        seed:
          type: integer
          minimum: 1
          description: Optional deterministic seed assigned to this child run.
        participants:
          type: array
          minItems: 1
          items:
            $ref: "#/components/schemas/CreateEvalSessionParticipant"

    EvalSessionAggregationConfig:
      type: object
      required: [method, report_variance, confidence_interval]
      properties:
        method:
          type: string
          enum: [median, mean, weighted_mean]
        report_variance:
          type: boolean
        confidence_interval:
          type: number
          exclusiveMinimum: 0
          exclusiveMaximum: 1
        reliability_weight:
          type: number
          minimum: 0
          maximum: 1
          description: Optional explicit override for repeated-eval routing weight.

    EvalSessionSuccessThresholdConfig:
      type: object
      required: [min_pass_rate]
      properties:
        min_pass_rate:
          type: number
          minimum: 0
          maximum: 1
        require_all_dimensions:
          type: array
          items:
            type: string
            minLength: 1

    EvalSessionRoutingTaskSnapshotInput:
      type: object
      required: [routing, task]
      properties:
        routing:
          type: object
          additionalProperties: true
        task:
          type: object
          additionalProperties: true

    EvalSessionReliabilityWeightsConfig:
      type: object
      properties:
        per_dimension:
          type: object
          minProperties: 1
          additionalProperties:
            type: number
            minimum: 0
            maximum: 1
          description: Values must sum to more than 0.
        per_judge:
          type: object
          additionalProperties:
            type: number
            minimum: 0
        per_run:
          $ref: "#/components/schemas/EvalSessionPerRunReliabilityConfig"

    EvalSessionPerRunReliabilityConfig:
      type: object
      required: [policy]
      properties:
        policy:
          type: string
          enum: [equal, downweight_outliers]
        params:
          type: object
          additionalProperties: true

    EvalSessionSeedFanoutConfig:
      type: object
      required: [strategy, seeds]
      properties:
        strategy:
          type: string
          enum: [explicit]
        seeds:
          type: array
          minItems: 1
          maxItems: 100
          uniqueItems: true
          items:
            type: integer
            minimum: 1
          description: Must contain exactly one seed per requested repetition.

    CreateEvalSessionResponse:
      type: object
      required: [eval_session, run_ids]
      properties:
        eval_session:
          $ref: "#/components/schemas/EvalSessionResponse"
        run_ids:
          type: array
          items:
            type: string
            format: uuid
        seeded_runs:
          type: array
          items:
            $ref: "#/components/schemas/EvalSessionSeededRun"
        series_runs:
          type: array
          items:
            $ref: "#/components/schemas/EvalSessionSeriesRun"

    EvalSessionSeededRun:
      type: object
      required: [run_id, seed]
      properties:
        run_id:
          type: string
          format: uuid
        seed:
          type: integer
          minimum: 1

    EvalSessionSeriesRun:
      type: object
      required: [run_id]
      properties:
        run_id:
          type: string
          format: uuid
        matrix_key:
          type: string
        deployment_lineup:
          type: string
        seed:
          type: integer
          minimum: 1

    EvalSessionResponse:
      type: object
      required:
        [id, status, repetitions, aggregation_config, success_threshold_config, routing_task_snapshot, schema_version, created_at, updated_at]
      properties:
        id:
          type: string
          format: uuid
        status:
          $ref: "#/components/schemas/EvalSessionStatus"
        repetitions:
          type: integer
        aggregation_config:
          $ref: "#/components/schemas/EvalSessionAggregationResponseConfig"
        success_threshold_config:
          $ref: "#/components/schemas/EvalSessionSuccessThresholdResponseConfig"
        routing_task_snapshot:
          $ref: "#/components/schemas/EvalSessionRoutingTaskSnapshotResponse"
        schema_version:
          type: integer
        created_at:
          type: string
          format: date-time
        started_at:
          type: string
          format: date-time
        finished_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    EvalSessionAggregationResponseConfig:
      allOf:
        - $ref: "#/components/schemas/EvalSessionAggregationConfig"
        - type: object
          required: [schema_version]
          properties:
            schema_version:
              type: integer
            reliability_weights:
              $ref: "#/components/schemas/EvalSessionReliabilityWeightsConfig"

    EvalSessionSuccessThresholdResponseConfig:
      allOf:
        - $ref: "#/components/schemas/EvalSessionSuccessThresholdConfig"
        - type: object
          required: [schema_version]
          properties:
            schema_version:
              type: integer

    EvalSessionRoutingTaskSnapshotResponse:
      allOf:
        - $ref: "#/components/schemas/EvalSessionRoutingTaskSnapshotInput"
        - type: object
          required: [schema_version]
          properties:
            schema_version:
              type: integer

    EvalSessionValidationEnvelope:
      type: object
      required: [errors]
      properties:
        errors:
          type: array
          items:
            $ref: "#/components/schemas/EvalSessionValidationDetail"

    EvalSessionValidationDetail:
      type: object
      required: [field, code, message]
      properties:
        field:
          type: string
        code:
          type: string
        message:
          type: string

    ListEvalSessionsResponse:
      type: object
      required: [items, limit, offset]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/EvalSessionListItem"
        limit:
          type: integer
        offset:
          type: integer

    EvalSessionListItem:
      type: object
      required: [eval_session, summary, aggregate_result, evidence_warnings]
      properties:
        eval_session:
          $ref: "#/components/schemas/EvalSessionResponse"
        summary:
          $ref: "#/components/schemas/EvalSessionRunSummary"
        aggregate_result:
          $ref: "#/components/schemas/EvalSessionAggregateResult"
        evidence_warnings:
          type: array
          items:
            type: string

    EvalSessionDetail:
      type: object
      required: [eval_session, runs, summary, aggregate_result, evidence_warnings]
      properties:
        eval_session:
          $ref: "#/components/schemas/EvalSessionResponse"
        runs:
          type: array
          items:
            $ref: "#/components/schemas/EvalSessionChildRun"
        summary:
          $ref: "#/components/schemas/EvalSessionRunSummary"
        aggregate_result:
          $ref: "#/components/schemas/EvalSessionAggregateResult"
        evidence_warnings:
          type: array
          items:
            type: string

    EvalSessionRunSummary:
      type: object
      required: [run_counts]
      properties:
        run_counts:
          $ref: "#/components/schemas/EvalSessionRunCounts"

    EvalSessionRunCounts:
      type: object
      required: [total, draft, queued, provisioning, running, scoring, completed, failed, cancelled]
      properties:
        total:
          type: integer
        draft:
          type: integer
        queued:
          type: integer
        provisioning:
          type: integer
        running:
          type: integer
        scoring:
          type: integer
        completed:
          type: integer
        failed:
          type: integer
        cancelled:
          type: integer

    EvalSessionAggregateResult:
      anyOf:
        - $ref: "#/components/schemas/EvalSessionAggregateDocument"
        - type: "null"

    EvalSessionAggregateDocument:
      type: object
      required: [schema_version, child_run_count, scored_child_count]
      properties:
        schema_version:
          type: integer
        child_run_count:
          type: integer
        scored_child_count:
          type: integer
        top_level_source:
          type: string
        overall:
          $ref: "#/components/schemas/EvalSessionMetricAggregate"
        dimensions:
          type: object
          additionalProperties:
            $ref: "#/components/schemas/EvalSessionMetricAggregate"
        task_success:
          type: array
          items:
            $ref: "#/components/schemas/EvalSessionTaskSuccess"
        pass_at_k:
          $ref: "#/components/schemas/EvalSessionPassMetricSeries"
        pass_pow_k:
          $ref: "#/components/schemas/EvalSessionPassMetricSeries"
        metric_routing:
          $ref: "#/components/schemas/EvalSessionMetricRouting"
        participants:
          type: array
          items:
            $ref: "#/components/schemas/EvalSessionParticipantAggregate"
        comparison:
          $ref: "#/components/schemas/EvalSessionRepeatedComparison"
        series_report:
          $ref: "#/components/schemas/EvalSessionSeriesReport"

    EvalSessionParticipantAggregate:
      type: object
      required: [lane_index, label]
      properties:
        lane_index:
          type: integer
        label:
          type: string
        overall:
          $ref: "#/components/schemas/EvalSessionMetricAggregate"
        dimensions:
          type: object
          additionalProperties:
            $ref: "#/components/schemas/EvalSessionMetricAggregate"
        task_success:
          type: array
          items:
            $ref: "#/components/schemas/EvalSessionTaskSuccess"
        pass_at_k:
          $ref: "#/components/schemas/EvalSessionPassMetricSeries"
        pass_pow_k:
          $ref: "#/components/schemas/EvalSessionPassMetricSeries"
        metric_routing:
          $ref: "#/components/schemas/EvalSessionMetricRouting"
        cost_usd:
          $ref: "#/components/schemas/EvalSessionCostAggregate"

    EvalSessionSeriesReport:
      type: object
      required: [rank_metric, rows]
      properties:
        rank_metric:
          type: string
          enum: [overall_score, composite_agent_score]
        rows:
          type: array
          items:
            $ref: "#/components/schemas/EvalSessionSeriesReportRow"

    EvalSessionSeriesReportRow:
      type: object
      required: [rank, lane_index, label, observed_runs]
      properties:
        rank:
          type: integer
        lane_index:
          type: integer
        label:
          type: string
        deployment_lineup:
          type: string
        participant_label:
          type: string
        observed_runs:
          type: integer
        overall_score:
          type: number
        correctness_score:
          type: number
        success_rate:
          type: number
        composite_agent_score:
          type: number
        mean_cost_usd:
          type: number
        total_cost_usd:
          type: number

    EvalSessionCostAggregate:
      type: object
      required: [n, total, mean, min, max]
      properties:
        n:
          type: integer
        total:
          type: number
        mean:
          type: number
        min:
          type: number
        max:
          type: number

    EvalSessionTaskSuccess:
      type: object
      required: [task_key, observed_trials, successful_trials, success_rate, source]
      properties:
        task_key:
          type: string
        challenge_identity_id:
          type: string
          format: uuid
        challenge_key:
          type: string
        title:
          type: string
        observed_trials:
          type: integer
        successful_trials:
          type: integer
        success_rate:
          type: number
        source:
          type: string
        pass_at_k:
          type: object
          additionalProperties:
            type: number
        pass_pow_k:
          type: object
          additionalProperties:
            type: number

    EvalSessionPassMetricSeries:
      type: object
      required: [effective_k]
      properties:
        effective_k:
          type: integer
        by_k:
          type: object
          additionalProperties:
            $ref: "#/components/schemas/EvalSessionMetricAggregate"

    EvalSessionMetricRouting:
      type: object
      required: [source, reliability_weight, reasoning, primary_metric, effective_k, composite_agent_score]
      properties:
        source:
          type: string
        reliability_weight:
          type: number
        reasoning:
          type: string
        primary_metric:
          type: string
        effective_k:
          type: integer
        composite_agent_score:
          type: number
        composite_interval:
          $ref: "#/components/schemas/EvalSessionAggregateInterval"

    EvalSessionRepeatedComparison:
      type: object
      required: [status, effective_k]
      properties:
        status:
          type: string
          enum: [clear_winner, no_clear_winner, insufficient_evidence]
        reason_code:
          type: string
        compared_metric:
          type: string
        effective_k:
          type: integer
        winner_lane_index:
          type: integer
        winner_label:
          type: string
        leader_lane_index:
          type: integer
        leader_label:
          type: string
        leader_value:
          type: number
        leader_interval:
          $ref: "#/components/schemas/EvalSessionAggregateInterval"
        runner_up_lane_index:
          type: integer
        runner_up_label:
          type: string
        runner_up_value:
          type: number
        runner_up_interval:
          $ref: "#/components/schemas/EvalSessionAggregateInterval"

    EvalSessionMetricAggregate:
      type: object
      required: [n, mean, median, std_dev, min, max, high_variance, high_variance_rule]
      properties:
        n:
          type: integer
        mean:
          type: number
        median:
          type: number
        std_dev:
          type: number
        min:
          type: number
        max:
          type: number
        interval:
          $ref: "#/components/schemas/EvalSessionAggregateInterval"
        high_variance:
          type: boolean
        high_variance_rule:
          type: string

    EvalSessionAggregateInterval:
      type: object
      required: [estimator, lower, upper]
      properties:
        estimator:
          type: string
        lower:
          type: number
        upper:
          type: number

    EvalSessionChildRun:
      type: object
      required:
        [id, workspace_id, challenge_pack_version_id, official_pack_mode, name, status, execution_mode, created_at, updated_at, links]
      properties:
        id:
          type: string
          format: uuid
        workspace_id:
          type: string
          format: uuid
        challenge_pack_version_id:
          type: string
          format: uuid
        challenge_input_set_id:
          type: string
          format: uuid
        eval_session_id:
          type: string
          format: uuid
        official_pack_mode:
          type: string
          enum: [full, suite_only]
        name:
          type: string
        status:
          $ref: "#/components/schemas/RunStatus"
        execution_mode:
          type: string
        seed:
          type: integer
          minimum: 1
          description: Seed assigned to this child run when the eval session was created via seeded fanout.
        matrix_key:
          type: string
          description: Stable series matrix key persisted on this child run, when created from `run_matrix`.
        deployment_lineup:
          type: string
          description: Deployment lineup name persisted on this child run, when created from `run_matrix`.
        queued_at:
          type: string
          format: date-time
        started_at:
          type: string
          format: date-time
        finished_at:
          type: string
          format: date-time
        cancelled_at:
          type: string
          format: date-time
        failed_at:
          type: string
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        links:
          $ref: "#/components/schemas/RunLinks"

    CreateRunResponse:
      type: object
      required: [id, workspace_id, challenge_pack_version_id, official_pack_mode, status, execution_mode, race_context, created_at, links]
      properties:
        id:
          type: string
          format: uuid
        workspace_id:
          type: string
          format: uuid
        challenge_pack_version_id:
          type: string
          format: uuid
        challenge_input_set_id:
          type: string
          format: uuid
        official_pack_mode:
          type: string
          enum: [full, suite_only]
        status:
          $ref: "#/components/schemas/RunStatus"
        execution_mode:
          type: string
        created_at:
          type: string
          format: date-time
        queued_at:
          type: string
          format: date-time
        race_context:
          type: boolean
          description: >
            Whether live peer-standings injection is enabled for this run.
        race_context_min_step_gap:
          type: integer
          minimum: 1
          maximum: 10
          description: >
            Per-run cadence override. Null when the backend default is in use.
        ci_metadata:
          $ref: "#/components/schemas/RunCIMetadata"
        links:
          $ref: "#/components/schemas/RunLinks"

    CreateRunErrorResponse:
      type: object
      required: [error, run]
      description: >
        Returned when the run is created in the database but the Temporal
        workflow could not be started (HTTP 502).
      properties:
        error:
          type: object
          required: [code, message]
          properties:
            code:
              type: string
            message:
              type: string
        run:
          $ref: "#/components/schemas/CreateRunResponse"

    RunWorkflowErrorResponse:
      type: object
      required: [error, run]
      description: >
        Returned when a run workflow operation could not be completed in
        Temporal (HTTP 502).
      properties:
        error:
          type: object
          required: [code, message]
          properties:
            code:
              type: string
            message:
              type: string
        run:
          $ref: "#/components/schemas/RunDetail"

    RunDetail:
      type: object
      required: [id, workspace_id, challenge_pack_version_id, official_pack_mode, name, status, execution_mode, race_context, created_at, updated_at, links]
      properties:
        id:
          type: string
          format: uuid
        workspace_id:
          type: string
          format: uuid
        challenge_pack_version_id:
          type: string
          format: uuid
        challenge_input_set_id:
          type: string
          format: uuid
        official_pack_mode:
          type: string
          enum: [full, suite_only]
        name:
          type: string
        status:
          $ref: "#/components/schemas/RunStatus"
        execution_mode:
          type: string
        temporal_workflow_id:
          type: string
        temporal_run_id:
          type: string
        queued_at:
          type: string
          format: date-time
        started_at:
          type: string
          format: date-time
        finished_at:
          type: string
          format: date-time
        cancelled_at:
          type: string
          format: date-time
        failed_at:
          type: string
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        race_context:
          type: boolean
          description: >
            Whether live peer-standings injection is enabled for this run.
        race_context_min_step_gap:
          type: integer
          minimum: 1
          maximum: 10
          description: >
            Per-run cadence override. Null when the backend default is in use.
        ci_metadata:
          $ref: "#/components/schemas/RunCIMetadata"
        regression_coverage:
          $ref: "#/components/schemas/RegressionCoverage"
        links:
          $ref: "#/components/schemas/RunLinks"

    RegressionCoverage:
      type: object
      required: [suites, unmatched_cases]
      properties:
        suites:
          type: array
          items:
            $ref: "#/components/schemas/RegressionCoverageSuite"
        unmatched_cases:
          type: array
          items:
            $ref: "#/components/schemas/RegressionCoverageCase"

    RegressionCoverageSuite:
      type: object
      required: [id, name, case_count, pass_count, fail_count]
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        case_count:
          type: integer
        pass_count:
          type: integer
        fail_count:
          type: integer

    RegressionCoverageCase:
      type: object
      required: [id, title, outcome]
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
        outcome:
          type: string
          enum: [pending, pass, fail]

    RunAgent:
      type: object
      required: [id, run_id, lane_index, label, agent_deployment_id, agent_deployment_snapshot_id, status, created_at, updated_at]
      properties:
        id:
          type: string
          format: uuid
        run_id:
          type: string
          format: uuid
        lane_index:
          type: integer
          format: int32
        label:
          type: string
        agent_deployment_id:
          type: string
          format: uuid
        agent_deployment_snapshot_id:
          type: string
          format: uuid
        status:
          $ref: "#/components/schemas/RunAgentStatus"
        queued_at:
          type: string
          format: date-time
        started_at:
          type: string
          format: date-time
        finished_at:
          type: string
          format: date-time
        failure_reason:
          type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    ListRunAgentsResponse:
      type: object
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/RunAgent"

    FailureReviewFailureState:
      type: string
      enum: [failed, warning, flaky, incomplete_evidence]

    FailureReviewFailureClass:
      type: string
      enum:
        - incorrect_final_output
        - tool_selection_error
        - tool_argument_error
        - retrieval_grounding_failure
        - policy_violation
        - timeout_or_budget_exhaustion
        - sandbox_failure
        - dependency_resolution_failure
        - malformed_output
        - flaky_non_deterministic
        - insufficient_evidence
        - other

    FailureReviewEvidenceTier:
      type: string
      enum: [none, native_structured, hosted_structured, hosted_black_box, derived_summary]

    FailureReviewPromotionMode:
      type: string
      enum: [full_executable, output_only]

    FailureReviewRemediationArea:
      type: string
      enum:
        - prompt_or_model
        - output_contract
        - tool_or_workflow
        - runtime_or_infra
        - retrieval_or_data
        - flakiness
        - evidence_gap

    FailureReviewRemediationHint:
      type: object
      required: [area, label, summary, evidence]
      properties:
        area:
          $ref: "#/components/schemas/FailureReviewRemediationArea"
        label:
          type: string
        summary:
          type: string
        evidence:
          type: array
          items:
            type: string

    FailureReviewReplayStepRef:
      type: object
      required: [sequence_number, event_type, kind]
      properties:
        sequence_number:
          type: integer
          format: int64
        event_type:
          type: string
        kind:
          type: string

    FailureReviewArtifactRef:
      type: object
      required: [key]
      properties:
        key:
          type: string
        kind:
          type: string
        path:
          type: string
        media_type:
          type: string

    FailureReviewJudgeRef:
      type: object
      required: [key, kind]
      properties:
        key:
          type: string
        kind:
          type: string
        verdict:
          type: string
        state:
          type: string
        normalized_score:
          type: number
          format: double
        reason:
          type: string
        sequence_number:
          type: integer
          format: int64
        event_type:
          type: string

    FailureReviewMetricRef:
      type: object
      required: [key, metric_type]
      properties:
        key:
          type: string
        metric_type:
          type: string
        state:
          type: string
        reason:
          type: string
        numeric_value:
          type: number
          format: double
        text_value:
          type: string
        boolean_value:
          type: boolean
        unit:
          type: string

    FailureReviewItem:
      type: object
      required:
        - run_id
        - run_agent_id
        - challenge_key
        - case_key
        - item_key
        - failure_fingerprint
        - failure_cluster_key
        - failure_state
        - failed_dimensions
        - failed_checks
        - failure_class
        - failure_taxonomy
        - headline
        - detail
        - recommended_action
        - remediation
        - promotable
        - promotion_mode_available
        - replay_step_refs
        - artifact_refs
        - judge_refs
        - metric_refs
        - evidence_tier
        - severity
      properties:
        run_id:
          type: string
          format: uuid
        run_agent_id:
          type: string
          format: uuid
        challenge_identity_id:
          type: string
          format: uuid
        challenge_key:
          type: string
        case_key:
          type: string
        item_key:
          type: string
        failure_fingerprint:
          type: string
          description: Deterministic identity for this exact run-agent failure review item.
        failure_cluster_key:
          type: string
          description: Deterministic grouping key for the same challenge failure shape across runs.
        failure_state:
          $ref: "#/components/schemas/FailureReviewFailureState"
        failed_dimensions:
          type: array
          items:
            type: string
        failed_checks:
          type: array
          items:
            type: string
        failure_class:
          $ref: "#/components/schemas/FailureReviewFailureClass"
        failure_taxonomy:
          $ref: "#/components/schemas/FailureReviewTaxonomy"
        headline:
          type: string
        detail:
          type: string
        recommended_action:
          type: string
        remediation:
          $ref: "#/components/schemas/FailureReviewRemediationHint"
        promotable:
          type: boolean
        promotion_mode_available:
          type: array
          items:
            $ref: "#/components/schemas/FailureReviewPromotionMode"
        replay_step_refs:
          type: array
          items:
            $ref: "#/components/schemas/FailureReviewReplayStepRef"
        artifact_refs:
          type: array
          items:
            $ref: "#/components/schemas/FailureReviewArtifactRef"
        judge_refs:
          type: array
          items:
            $ref: "#/components/schemas/FailureReviewJudgeRef"
        metric_refs:
          type: array
          items:
            $ref: "#/components/schemas/FailureReviewMetricRef"
        evidence_tier:
          $ref: "#/components/schemas/FailureReviewEvidenceTier"
        severity:
          type: string
          enum: [info, warning, blocking]

    FailureReviewClusterHistory:
      type: object
      required:
        - trend
        - window_run_count
        - prior_run_count
        - prior_failure_count
        - last_run_failure_count
      properties:
        trend:
          type: string
          enum: [new, recurring, increasing, decreasing]
          description: Directional label comparing the current cluster count with the most recent prior occurrence.
        window_run_count:
          type: integer
          format: int32
          description: Number of prior comparable scored runs scanned for this cluster.
        prior_run_count:
          type: integer
          format: int32
          description: Number of scanned prior runs where this cluster appeared.
        prior_failure_count:
          type: integer
          format: int32
          description: Total failures from this cluster across scanned prior runs.
        last_seen_run_id:
          type: string
          format: uuid
        last_seen_at:
          type: string
          format: date-time
        last_run_failure_count:
          type: integer
          format: int32
          description: Failure count from the most recent prior run containing this cluster.

    FailureReviewClusterSummary:
      type: object
      required:
        - failure_cluster_key
        - representative_failure_fingerprint
        - count
        - promotable_count
        - severity
        - failure_state
        - failure_class
        - failure_taxonomy
        - evidence_tier
        - challenge_keys
        - case_keys
        - run_agent_ids
        - headline
        - recommended_action
        - remediation
      properties:
        failure_cluster_key:
          type: string
          description: Deterministic grouping key shared by failure items with the same failure shape.
        representative_failure_fingerprint:
          type: string
          description: Fingerprint of a representative failure item from this cluster.
        count:
          type: integer
          format: int32
        promotable_count:
          type: integer
          format: int32
        severity:
          type: string
          enum: [info, warning, blocking]
        failure_state:
          $ref: "#/components/schemas/FailureReviewFailureState"
        failure_class:
          $ref: "#/components/schemas/FailureReviewFailureClass"
        failure_taxonomy:
          $ref: "#/components/schemas/FailureReviewTaxonomy"
        evidence_tier:
          $ref: "#/components/schemas/FailureReviewEvidenceTier"
        challenge_keys:
          type: array
          items:
            type: string
        case_keys:
          type: array
          items:
            type: string
        run_agent_ids:
          type: array
          items:
            type: string
            format: uuid
        headline:
          type: string
        recommended_action:
          type: string
        remediation:
          $ref: "#/components/schemas/FailureReviewRemediationHint"
        history:
          description: Present on first-page all-agent failure-cluster responses; omitted on cursor pages and agent-filtered views.
          $ref: "#/components/schemas/FailureReviewClusterHistory"

    FailureReviewTaxonomy:
      type: object
      required:
        - family
        - code
        - label
        - agent_fault
      properties:
        family:
          type: string
          enum: [agent, workflow, platform, evidence]
          description: Broad owner bucket for triage dashboards and policy.
        code:
          type: string
          description: Stable dotted taxonomy code derived from the failure class.
        label:
          type: string
          description: Human-facing taxonomy label.
        agent_fault:
          type: boolean
          description: >
            Whether the failure should normally count against the agent. This is
            independent from `family`; for example, a workflow-family timeout can
            still be agent-caused.

    ListRunFailuresResponse:
      type: object
      required: [items, clusters]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/FailureReviewItem"
        clusters:
          type: array
          description: Cluster summaries for the full filtered result set before item pagination.
          items:
            $ref: "#/components/schemas/FailureReviewClusterSummary"
        next_cursor:
          type: string
          description: Opaque base64url-encoded pagination cursor.

    ListRunsResponse:
      type: object
      required: [items, total, limit, offset]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/RunDetail"
        total:
          type: integer
          format: int64
        limit:
          type: integer
          format: int32
        offset:
          type: integer
          format: int32

    RegressionSuiteStatus:
      type: string
      enum: [active, archived]

    RegressionCaseStatus:
      type: string
      enum: [proposed, active, muted, archived, rejected]

    RegressionSeverity:
      type: string
      enum: [info, warning, blocking]

    RegressionPromotionMode:
      type: string
      enum: [full_executable, output_only, manual]

    RegressionSuite:
      type: object
      required:
        [
          id,
          workspace_id,
          source_challenge_pack_id,
          name,
          description,
          status,
          source_mode,
          default_gate_severity,
          case_count,
          created_by_user_id,
          created_at,
          updated_at,
        ]
      properties:
        id:
          type: string
          format: uuid
        workspace_id:
          type: string
          format: uuid
        source_challenge_pack_id:
          type: string
          format: uuid
        name:
          type: string
        description:
          type: string
        status:
          $ref: "#/components/schemas/RegressionSuiteStatus"
        source_mode:
          type: string
          enum: [derived_only, mixed_manual]
        default_gate_severity:
          $ref: "#/components/schemas/RegressionSeverity"
        case_count:
          type: integer
        created_by_user_id:
          type: string
          format: uuid
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    RegressionCase:
      type: object
      required:
        [
          id,
          suite_id,
          workspace_id,
          title,
          description,
          status,
          severity,
          promotion_mode,
          source_challenge_pack_version_id,
          source_challenge_identity_id,
          source_case_key,
          evidence_tier,
          failure_class,
          failure_summary,
          payload_snapshot,
          expected_contract,
          metadata,
          validation,
          created_at,
          updated_at,
        ]
      properties:
        id:
          type: string
          format: uuid
        suite_id:
          type: string
          format: uuid
        workspace_id:
          type: string
          format: uuid
        suite_name:
          type: string
        title:
          type: string
        description:
          type: string
        status:
          $ref: "#/components/schemas/RegressionCaseStatus"
        severity:
          $ref: "#/components/schemas/RegressionSeverity"
        promotion_mode:
          $ref: "#/components/schemas/RegressionPromotionMode"
        source_run_id:
          type: string
          format: uuid
        source_run_agent_id:
          type: string
          format: uuid
        source_replay_id:
          type: string
          format: uuid
        source_challenge_pack_version_id:
          type: string
          format: uuid
        source_challenge_input_set_id:
          type: string
          format: uuid
        source_challenge_identity_id:
          type: string
          format: uuid
        source_challenge_key:
          type: string
        source_case_key:
          type: string
        source_item_key:
          type: string
        source_failure_fingerprint:
          type: string
        source_failure_cluster_key:
          type: string
        evidence_tier:
          type: string
        failure_class:
          type: string
        failure_summary:
          type: string
        payload_snapshot:
          type: object
          additionalProperties: true
        expected_contract:
          type: object
          additionalProperties: true
        validator_overrides:
          anyOf:
            - type: object
              additionalProperties: true
            - type: "null"
        metadata:
          type: object
          additionalProperties: true
        latest_promotion:
          $ref: "#/components/schemas/RegressionPromotion"
        validation:
          $ref: "#/components/schemas/RegressionCaseValidation"
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    RegressionCaseValidation:
      type: object
      required:
        [
          status,
          maintenance_status,
          run_count,
          failure_count,
          pass_count,
          reproduction_threshold,
          required_runs,
          remaining_runs,
          recommended_action,
          maintenance_action,
        ]
      properties:
        status:
          type: string
          enum:
            [
              not_validated,
              collecting_signal,
              reproducing,
              passing,
              flaky,
            ]
        maintenance_status:
          type: string
          enum:
            [
              needs_signal,
              keep_active,
              prune_candidate,
              review_flaky,
            ]
          description: Operational maintenance bucket derived from validation status.
        run_count:
          type: integer
          minimum: 0
        failure_count:
          type: integer
          minimum: 0
        pass_count:
          type: integer
          minimum: 0
        reproduction_rate:
          type: number
          format: double
          minimum: 0
          maximum: 1
        reproduction_threshold:
          type: number
          format: double
          minimum: 0
          maximum: 1
        required_runs:
          type: integer
          minimum: 1
        remaining_runs:
          type: integer
          minimum: 0
        last_outcome:
          type: string
          enum: [pass, fail]
        last_validated_at:
          type: string
          format: date-time
        recommended_action:
          type: string
        maintenance_action:
          type: string

    RegressionPromotion:
      type: object
      required:
        [
          id,
          workspace_regression_case_id,
          source_run_id,
          source_run_agent_id,
          source_event_refs,
          promoted_by_user_id,
          promotion_reason,
          promotion_snapshot,
          created_at,
        ]
      properties:
        id:
          type: string
          format: uuid
        workspace_regression_case_id:
          type: string
          format: uuid
        source_run_id:
          type: string
          format: uuid
        source_run_agent_id:
          type: string
          format: uuid
        source_event_refs:
          type: array
          items: {}
        promoted_by_user_id:
          type: string
          format: uuid
        promotion_reason:
          type: string
        promotion_snapshot:
          type: object
          additionalProperties: true
        created_at:
          type: string
          format: date-time

    CreateRegressionSuiteInput:
      type: object
      required: [source_challenge_pack_id, name]
      properties:
        source_challenge_pack_id:
          type: string
          format: uuid
        name:
          type: string
        description:
          type: string
        default_gate_severity:
          $ref: "#/components/schemas/RegressionSeverity"

    PatchRegressionSuiteInput:
      type: object
      minProperties: 1
      properties:
        name:
          type: string
        description:
          type: string
        status:
          $ref: "#/components/schemas/RegressionSuiteStatus"
        default_gate_severity:
          $ref: "#/components/schemas/RegressionSeverity"

    PatchRegressionCaseInput:
      type: object
      minProperties: 1
      properties:
        title:
          type: string
        description:
          type: string
        status:
          $ref: "#/components/schemas/RegressionCaseStatus"
        severity:
          $ref: "#/components/schemas/RegressionSeverity"

    CaptureProductionFailureInput:
      type: object
      required:
        - source_challenge_pack_version_id
        - source_challenge_identity_id
        - source_case_key
        - title
        - failure_summary
        - payload_snapshot
      properties:
        source_challenge_pack_version_id:
          type: string
          format: uuid
        source_challenge_input_set_id:
          type: string
          format: uuid
        source_challenge_identity_id:
          type: string
          format: uuid
        source_case_key:
          type: string
          description: Stable external case, session, or incident key.
        source_item_key:
          type: string
        title:
          type: string
        failure_summary:
          type: string
        failure_class:
          $ref: "#/components/schemas/FailureReviewFailureClass"
        evidence_tier:
          $ref: "#/components/schemas/FailureReviewEvidenceTier"
        severity:
          $ref: "#/components/schemas/RegressionSeverity"
        promotion_mode:
          $ref: "#/components/schemas/RegressionPromotionMode"
        payload_snapshot:
          type: object
          additionalProperties: true
        expected_contract:
          type: object
          additionalProperties: true
        validator_overrides:
          anyOf:
            - type: object
              additionalProperties: true
            - type: "null"
        metadata:
          type: object
          additionalProperties: true
        incident_id:
          type: string
        external_url:
          type: string
        source:
          type: string
          description: External production system label; defaults to production.
        observed_at:
          type: string
          format: date-time

    PromoteFailureInput:
      type: object
      required: [suite_id, promotion_mode, title]
      properties:
        run_agent_id:
          type: string
          format: uuid
          description: >
            Optional disambiguator for compare or multi-agent runs where the same
            challenge identity appears in multiple failure review items.
        suite_id:
          type: string
          format: uuid
        promotion_mode:
          type: string
          enum: [full_executable, output_only]
        title:
          type: string
        failure_summary:
          type: string
        status:
          type: string
          enum: [active, proposed]
          description: >
            Optional initial case status. Manual promotions default to active;
            CI integrations can use proposed to create reviewable candidates.
        severity:
          $ref: "#/components/schemas/RegressionSeverity"
        validator_overrides:
          anyOf:
            - type: object
              additionalProperties: true
            - type: "null"
        metadata:
          anyOf:
            - type: object
              additionalProperties: true
            - type: "null"

    ListRegressionSuitesResponse:
      type: object
      required: [items, total, limit, offset]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/RegressionSuite"
        total:
          type: integer
          format: int64
        limit:
          type: integer
          format: int32
        offset:
          type: integer
          format: int32

    ListRegressionCasesResponse:
      type: object
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/RegressionCase"

    ListWorkspaceRegressionCasesResponse:
      type: object
      required: [items, total, limit, offset]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/RegressionCase"
        total:
          type: integer
          format: int64
        limit:
          type: integer
          format: int32
        offset:
          type: integer
          format: int32

    # --- Ranking ---

    RunRankingResponse:
      type: object
      required: [state]
      properties:
        state:
          $ref: "#/components/schemas/RankingReadState"
        message:
          type: string
        ranking:
          $ref: "#/components/schemas/RunRankingPayload"

    CreateRunRankingInsightsRequest:
      type: object
      required: [provider_account_id, model]
      properties:
        provider_account_id:
          type: string
          format: uuid
        model:
          type: string

    RunRankingInsightsResponse:
      type: object
      required:
        [
          generated_at,
          grounding_scope,
          provider_key,
          provider_model_id,
          recommended_winner,
          why_it_won,
          tradeoffs,
          model_summaries,
          recommended_next_step,
          confidence_notes,
        ]
      properties:
        generated_at:
          type: string
          format: date-time
        grounding_scope:
          type: string
        provider_key:
          type: string
        provider_model_id:
          type: string
        recommended_winner:
          $ref: "#/components/schemas/RunRankingInsightCandidate"
        why_it_won:
          type: string
        tradeoffs:
          type: array
          items:
            type: string
        best_for_reliability:
          $ref: "#/components/schemas/RunRankingInsightRecommendation"
        best_for_cost:
          $ref: "#/components/schemas/RunRankingInsightRecommendation"
        best_for_latency:
          $ref: "#/components/schemas/RunRankingInsightRecommendation"
        model_summaries:
          type: array
          items:
            $ref: "#/components/schemas/RunRankingModelInsight"
        recommended_next_step:
          type: string
        confidence_notes:
          type: string

    RunRankingInsightCandidate:
      type: object
      required: [run_agent_id, label]
      properties:
        run_agent_id:
          type: string
          format: uuid
        label:
          type: string

    RunRankingInsightRecommendation:
      type: object
      required: [run_agent_id, label, reason]
      properties:
        run_agent_id:
          type: string
          format: uuid
        label:
          type: string
        reason:
          type: string

    RunRankingModelInsight:
      type: object
      required:
        [run_agent_id, label, strongest_dimension, weakest_dimension, summary]
      properties:
        run_agent_id:
          type: string
          format: uuid
        label:
          type: string
        strongest_dimension:
          type: string
        weakest_dimension:
          type: string
        summary:
          type: string

    RunRankingPayload:
      type: object
      required: [run_id, evaluation_spec_id, sort, winner, evidence_quality, items]
      properties:
        run_id:
          type: string
          format: uuid
        evaluation_spec_id:
          type: string
          format: uuid
        sort:
          $ref: "#/components/schemas/RunRankingSort"
        winner:
          $ref: "#/components/schemas/RunRankingWinner"
        evidence_quality:
          $ref: "#/components/schemas/EvidenceQuality"
        items:
          type: array
          items:
            $ref: "#/components/schemas/RunRankingItem"

    RunRankingSort:
      type: object
      required: [field, direction, default_order]
      properties:
        field:
          type: string
        direction:
          type: string
        default_order:
          type: boolean

    RunRankingWinner:
      type: object
      required: [strategy, status, reason_code]
      properties:
        run_agent_id:
          type: string
          format: uuid
        strategy:
          type: string
        status:
          type: string
        reason_code:
          type: string

    RunRankingItem:
      type: object
      required: [run_agent_id, lane_index, label, status, has_scorecard, sort_state]
      properties:
        rank:
          type: integer
        run_agent_id:
          type: string
          format: uuid
        lane_index:
          type: integer
          format: int32
        label:
          type: string
        status:
          $ref: "#/components/schemas/RunAgentStatus"
        has_scorecard:
          type: boolean
        evaluation_status:
          type: string
        sort_value:
          type: number
          format: double
        delta_from_top:
          type: number
          format: double
        sort_state:
          type: string
        composite_score:
          type: number
          format: double
        overall_score:
          type: number
          format: double
        correctness_score:
          type: number
          format: double
        reliability_score:
          type: number
          format: double
        latency_score:
          type: number
          format: double
        cost_score:
          type: number
          format: double
        cost_per_correct_usd:
          type: number
          format: double
        dimensions:
          type: object
          additionalProperties:
            $ref: "#/components/schemas/DimensionScore"

    DimensionScore:
      type: object
      required: [state]
      properties:
        state:
          type: string
        score:
          type: number
          format: double

    EvidenceQuality:
      type: object
      properties:
        missing_fields:
          type: array
          items:
            type: string
        warnings:
          type: array
          items:
            type: string

    # --- Multi-turn ---

    SubmitHumanTurnRequest:
      type: object
      required: [message]
      properties:
        message:
          type: string
          minLength: 1

    SubmitHumanTurnResponse:
      type: object
      required: [status]
      properties:
        status:
          type: string
          enum: [submitted]

    HumanTurnStatusResponse:
      type: object
      required: [awaiting_human]
      properties:
        awaiting_human:
          type: boolean
        turn_index:
          type: integer
          minimum: 0
        phase_id:
          type: string
        prompt_hint:
          type: string

    CalibrationReviewRequest:
      type: object
      required: [run_agent_id, turn_index, score]
      properties:
        run_agent_id:
          type: string
          format: uuid
        turn_index:
          type: integer
          minimum: 0
        score:
          type: number
          minimum: 1
          maximum: 5
        rubric_key:
          type: string
        notes:
          type: string

    CalibrationReview:
      type: object
      required: [id, run_agent_id, turn_index, score, reviewer_user_id, created_at]
      properties:
        id:
          type: string
          format: uuid
        run_agent_id:
          type: string
          format: uuid
        turn_index:
          type: integer
        score:
          type: number
        rubric_key:
          type: string
        notes:
          type: string
        reviewer_user_id:
          type: string
          format: uuid
        created_at:
          type: string
          format: date-time

    ListCalibrationReviewsResponse:
      type: object
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/CalibrationReview"

    ArenaTask:
      type: object
      required: [id, case_key, left_run_agent_id, right_run_agent_id, status]
      properties:
        id:
          type: string
          format: uuid
        case_key:
          type: string
        left_run_agent_id:
          type: string
          format: uuid
        right_run_agent_id:
          type: string
          format: uuid
        status:
          type: string

    ListArenaTasksResponse:
      type: object
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/ArenaTask"

    ArenaVoteRequest:
      type: object
      required: [task_id, winner_run_agent_id]
      properties:
        task_id:
          type: string
          format: uuid
        winner_run_agent_id:
          type: string
          format: uuid
        rubric_scores:
          type: object
          additionalProperties:
            type: number

    MultiTurnStatusResponse:
      type: object
      required: [status]
      properties:
        status:
          type: string

    # --- Replays ---

    ReplayResponse:
      type: object
      required: [state, run_agent_id, run_id, run_agent_status, steps, pagination]
      properties:
        state:
          $ref: "#/components/schemas/ReplayState"
        message:
          type: string
        run_agent_id:
          type: string
          format: uuid
        run_id:
          type: string
          format: uuid
        run_agent_status:
          $ref: "#/components/schemas/RunAgentStatus"
        replay:
          $ref: "#/components/schemas/ReplayPayload"
        steps:
          type: array
          items:
            type: object
            additionalProperties: true
        pagination:
          $ref: "#/components/schemas/ReplayPagination"

    TranscriptResponse:
      type: object
      required: [state, run_agent_id, run_id, run_agent_status, turn_count, turns]
      properties:
        state:
          $ref: "#/components/schemas/ReplayState"
        message:
          type: string
        run_agent_id:
          type: string
          format: uuid
        run_id:
          type: string
          format: uuid
        run_agent_status:
          $ref: "#/components/schemas/RunAgentStatus"
        turn_count:
          type: integer
        turns:
          type: array
          items:
            $ref: "#/components/schemas/TranscriptTurn"

    TranscriptTurn:
      type: object
      required: [turn_index, mismatch, completed, awaiting_human, user_simulated]
      properties:
        turn_index:
          type: integer
        phase_id:
          type: string
        actor:
          type: string
          description: scripted, llm, or human
        user_message:
          type: string
        assistant_message:
          type: string
        mismatch:
          type: boolean
        completed:
          type: boolean
        awaiting_human:
          type: boolean
        awaiting_human_hint:
          type: string
        user_simulated:
          type: boolean

    ReplayPayload:
      type: object
      required: [id, summary, event_count, created_at, updated_at]
      properties:
        id:
          type: string
          format: uuid
        artifact_id:
          type: string
          format: uuid
        summary:
          type: object
          additionalProperties: true
        latest_sequence_number:
          type: integer
          format: int64
        event_count:
          type: integer
          format: int64
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    ReplayPagination:
      type: object
      required: [limit, total_steps, has_more]
      properties:
        next_cursor:
          type: string
        limit:
          type: integer
        total_steps:
          type: integer
        has_more:
          type: boolean

    # --- Scorecards ---

    ScorecardResponse:
      type: object
      required: [state, run_agent_status, id, run_agent_id, run_id, evaluation_spec_id, llm_judge_results, scorecard, created_at, updated_at]
      properties:
        state:
          $ref: "#/components/schemas/ReplayState"
        message:
          type: string
        run_agent_status:
          $ref: "#/components/schemas/RunAgentStatus"
        id:
          type: string
          format: uuid
        run_agent_id:
          type: string
          format: uuid
        run_id:
          type: string
          format: uuid
        evaluation_spec_id:
          type: string
          format: uuid
        overall_score:
          type: number
          format: double
        correctness_score:
          type: number
          format: double
        reliability_score:
          type: number
          format: double
        latency_score:
          type: number
          format: double
        cost_score:
          type: number
          format: double
        total_cost_usd:
          type: number
          format: double
          description: Total model cost in USD for this run agent, derived from stored scorecard cost metrics.
        cost_per_correct_usd:
          type: number
          format: double
          description: Total model cost divided by correct validator outcomes for this run agent.
        behavioral_score:
          type: number
          format: double
        llm_judge_results:
          type: array
          items:
            type: object
            required: [id, judge_key, mode, sample_count, model_count, payload, created_at, updated_at]
            properties:
              id:
                type: string
                format: uuid
              judge_key:
                type: string
              mode:
                type: string
              normalized_score:
                type: number
                format: double
              confidence:
                type: string
              variance:
                type: number
                format: double
              sample_count:
                type: integer
                format: int32
              model_count:
                type: integer
                format: int32
              payload:
                type: object
                additionalProperties: true
              created_at:
                type: string
                format: date-time
              updated_at:
                type: string
                format: date-time
        scorecard:
          $ref: "#/components/schemas/ScorecardDocument"
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    ScorecardDocument:
      type: object
      properties:
        run_agent_id:
          type: string
          format: uuid
        evaluation_spec_id:
          type: string
          format: uuid
        status:
          type: string
        strategy:
          type: string
        overall_score:
          type: number
          format: double
        passed:
          type: boolean
        overall_reason:
          type: string
        validity:
          type: string
          enum: [valid, degraded, invalid]
        validity_reason:
          type: string
        warnings:
          type: array
          items:
            type: string
        dimensions:
          type: object
          additionalProperties:
            $ref: "#/components/schemas/ScorecardDimension"
        validator_summary:
          type: object
          additionalProperties:
            type: integer
        validator_details:
          type: array
          items:
            $ref: "#/components/schemas/ValidatorDetail"
        metric_summary:
          type: object
          additionalProperties:
            type: integer
        metric_details:
          type: array
          items:
            $ref: "#/components/schemas/MetricDetail"
        side_metrics:
          type: object
          additionalProperties:
            $ref: "#/components/schemas/SideMetricDetail"
      additionalProperties: true

    SideMetricDetail:
      type: object
      required: [state]
      properties:
        state:
          type: string
          description: Whether the side metric could be computed.
        value:
          type: number
          format: double
          description: Numeric metric value when state is available.
        unit:
          type: string
        numerator:
          type: number
          format: double
        denominator:
          type: number
          format: double
        reason:
          type: string
          description: Explanation when state is unavailable.

    ValidatorDetail:
      type: object
      required: [key, type, verdict, state]
      properties:
        key:
          type: string
        type:
          type: string
        verdict:
          type: string
        state:
          type: string
        outcome_class:
          type: string
          enum: [pass, fail, unavailable, infra_error, pack_error, evaluator_error]
        reason:
          type: string
        normalized_score:
          type: number
          format: double
        regression_case_id:
          type: string
          format: uuid
        evidence:
          $ref: "#/components/schemas/ValidatorEvidence"
        source:
          $ref: "#/components/schemas/ScorecardSource"

    MetricDetail:
      type: object
      required: [key, collector, state]
      properties:
        key:
          type: string
        collector:
          type: string
        state:
          type: string
        reason:
          type: string
        regression_case_id:
          type: string
          format: uuid
        numeric_value:
          type: number
          format: double
        text_value:
          type: string
        boolean_value:
          type: boolean

    ScorecardSource:
      type: object
      description: |
        Optional pointer from a validator or metric result to the run event
        that produced the evaluated value. Omitted when the evidence is
        aggregated across multiple events or sourced from challenge inputs
        that do not live in the trace. Use `sequence` to deep-link into the
        replay timeline — run events are uniquely addressed by
        (run_agent_id, sequence_number).
      required: [kind]
      properties:
        kind:
          type: string
          enum: [run_event, tool_call, model_call, final_output]
          description: |
            Coarse categorization for how the inspector should label the link.
            `run_event` is a generic pointer, `tool_call` identifies a
            grader/tool verification event, `model_call` identifies a
            model.call.completed event (used as the final-output producer on
            native runs that don't emit a dedicated finalized event), and
            `final_output` identifies the synthesized system.output.finalized
            event.
        sequence:
          type: integer
          format: int64
          description: |
            Monotonic per-run-agent sequence number of the originating event.
            Matches `run_events.sequence_number`.
        event_type:
          type: string
          description: Denormalized run event type (e.g. `system.run.completed`).
        field_path:
          type: string
          description: |
            Validator target / JSON path within the event payload the evidence
            was drawn from (e.g. `final_output`, `file:generated_code`).

    ValidatorEvidence:
      discriminator:
        propertyName: kind
      oneOf:
        - $ref: "#/components/schemas/ValidatorTextCompareEvidence"
        - $ref: "#/components/schemas/ValidatorRegexEvidence"
        - $ref: "#/components/schemas/ValidatorJSONSchemaEvidence"
        - $ref: "#/components/schemas/ValidatorJSONPathEvidence"
        - $ref: "#/components/schemas/ValidatorCustomEvidence"

    ValidatorTextCompareEvidence:
      type: object
      required: [kind]
      properties:
        kind:
          type: string
          enum: [text_compare]
        expected:
          type: string
        actual:
          type: string
        source_field:
          type: string

    ValidatorRegexEvidence:
      type: object
      required: [kind]
      properties:
        kind:
          type: string
          enum: [regex_match]
        pattern:
          type: string
        actual:
          type: string
        matched:
          type: boolean
        source_field:
          type: string

    ValidatorJSONSchemaEvidence:
      type: object
      required: [kind]
      properties:
        kind:
          type: string
          enum: [json_schema]
        schema_ref:
          type: string
        actual:
          type: string
        validation_errors:
          type: array
          items:
            type: string
        source_field:
          type: string

    ValidatorJSONPathEvidence:
      type: object
      required: [kind]
      properties:
        kind:
          type: string
          enum: [json_path_match]
        path:
          type: string
        comparator:
          type: string
        actual:
          description: Arbitrary JSON value extracted from the JSONPath comparator target.
        expected:
          description: Arbitrary JSON value declared in the JSONPath expectation.
        exists:
          type: boolean
        source_field:
          type: string

    ValidatorCustomEvidence:
      type: object
      required: [kind]
      properties:
        kind:
          type: string
          enum: [custom]
        raw:
          description: Raw validator evidence payload when no structured mapping is available.

    ScorecardDimension:
      type: object
      properties:
        state:
          type: string
          description: Availability state for this dimension in the stored scorecard.
        score:
          type: number
          format: double
          description: Raw normalized dimension score when the dimension is available.
        reason:
          type: string
        better_direction:
          type: string
        weight:
          type: number
          format: double
          description: Effective dimension weight from the evaluation spec. Defaults to 1.0 when omitted in the spec so clients can render the active scoring behavior explicitly.
        contribution:
          type: number
          format: double
          description: Normalized relative contribution signal derived from the scoring strategy's weighting semantics. For weighted and hybrid strategies it reflects the dimension's share of the weighted score calculation; for binary or failed-gate cases it may not sum to the final overall score and should be treated as relative impact, not a strict score decomposition.
        pass_threshold:
          type: number
          format: double
          description: Dimension pass threshold from the evaluation spec when configured.
        gate:
          type: boolean
          description: Whether this dimension acts as a required gate for the active strategy.
        gate_passed:
          type: boolean
          description: Whether the dimension cleared its required gate threshold. Omitted when the dimension is not a required gate.
      additionalProperties: true

    # --- Compare ---

    ComparisonResponse:
      type: object
      required: [state, status, baseline_run_id, candidate_run_id, generated_at, key_deltas, regression_reasons, evidence_quality, summary, links]
      properties:
        state:
          $ref: "#/components/schemas/ComparisonReadState"
        status:
          type: string
        reason_code:
          type: string
        baseline_run_id:
          type: string
          format: uuid
        candidate_run_id:
          type: string
          format: uuid
        baseline_run_agent_id:
          type: string
          format: uuid
        candidate_run_agent_id:
          type: string
          format: uuid
        generated_at:
          type: string
          format: date-time
        key_deltas:
          type: array
          items:
            $ref: "#/components/schemas/DeltaHighlight"
        regression_reasons:
          type: array
          items:
            type: string
        evidence_quality:
          $ref: "#/components/schemas/EvidenceQuality"
        summary:
          type: object
          additionalProperties: true
          description: Full comparison summary document
        links:
          type: object
          required: [viewer]
          properties:
            viewer:
              type: string

    DeltaHighlight:
      type: object
      required: [metric, better_direction, outcome, state]
      properties:
        metric:
          type: string
        baseline_value:
          type: number
          format: double
        candidate_value:
          type: number
          format: double
        delta:
          type: number
          format: double
        better_direction:
          type: string
        outcome:
          type: string
        state:
          type: string

    # --- Release Gates ---

    ReleaseGatePolicy:
      type: object
      required: [policy_key, policy_version]
      properties:
        policy_key:
          type: string
        policy_version:
          type: integer
        require_comparable:
          type: boolean
        require_evidence_quality:
          type: boolean
        fail_on_candidate_failure:
          type: boolean
        fail_on_both_failed_differently:
          type: boolean
        required_dimensions:
          type: array
          items:
            type: string
        dimensions:
          type: object
          additionalProperties:
            $ref: "#/components/schemas/DimensionThreshold"
        regression_gate_rules:
          $ref: "#/components/schemas/RegressionGateRules"

    DimensionThreshold:
      type: object
      properties:
        warn_delta:
          type: number
          format: double
        fail_delta:
          type: number
          format: double

    RegressionGateRules:
      type: object
      properties:
        no_blocking_regression_failure:
          type: boolean
        no_new_blocking_failure_vs_baseline:
          type: boolean
        max_warning_regression_failures:
          type: integer
          minimum: 0
        suite_ids:
          type: array
          items:
            type: string
            format: uuid

    EvaluateReleaseGateRequest:
      type: object
      required: [baseline_run_id, candidate_run_id, policy]
      properties:
        baseline_run_id:
          type: string
          format: uuid
        candidate_run_id:
          type: string
          format: uuid
        baseline_run_agent_id:
          type: string
          format: uuid
        candidate_run_agent_id:
          type: string
          format: uuid
        policy:
          $ref: "#/components/schemas/ReleaseGatePolicy"

    EvaluateReleaseGateResponse:
      type: object
      required: [baseline_run_id, candidate_run_id, release_gate]
      properties:
        baseline_run_id:
          type: string
          format: uuid
        candidate_run_id:
          type: string
          format: uuid
        release_gate:
          $ref: "#/components/schemas/ReleaseGate"

    ListReleaseGatesResponse:
      type: object
      required: [baseline_run_id, candidate_run_id, release_gates]
      properties:
        baseline_run_id:
          type: string
          format: uuid
        candidate_run_id:
          type: string
          format: uuid
        release_gates:
          type: array
          items:
            $ref: "#/components/schemas/ReleaseGate"

    ReleaseGate:
      type: object
      required: [id, run_comparison_id, policy_key, policy_version, policy_fingerprint, policy_snapshot, verdict, reason_code, summary, evidence_status, evaluation_details, generated_at, updated_at]
      properties:
        id:
          type: string
          format: uuid
        run_comparison_id:
          type: string
          format: uuid
        policy_key:
          type: string
        policy_version:
          type: integer
        policy_fingerprint:
          type: string
        policy_snapshot:
          type: object
          additionalProperties: true
        verdict:
          type: string
          enum: [pass, warn, fail, insufficient_evidence]
        reason_code:
          type: string
        summary:
          type: string
        evidence_status:
          type: string
        evaluation_details:
          $ref: "#/components/schemas/ReleaseGateEvaluationDetails"
        generated_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    ReleaseGateEvaluationDetails:
      type: object
      required: [policy_key, policy_version, comparison_status]
      properties:
        policy_key:
          type: string
        policy_version:
          type: integer
        comparison_status:
          type: string
        missing_fields:
          type: array
          items:
            type: string
        warnings:
          type: array
          items:
            type: string
        triggered_conditions:
          type: array
          items:
            type: string
        required_dimensions:
          type: array
          items:
            type: string
        dimension_results:
          type: object
          additionalProperties:
            $ref: "#/components/schemas/ReleaseGateDimensionEvaluation"
        scorecard_validity:
          $ref: "#/components/schemas/ReleaseGateScorecardValidity"
        regression_violations:
          type: array
          items:
            $ref: "#/components/schemas/ReleaseGateRegressionViolation"

    ReleaseGateScorecardValidity:
      type: object
      properties:
        baseline:
          $ref: "#/components/schemas/ReleaseGateScorecardValiditySide"
        candidate:
          $ref: "#/components/schemas/ReleaseGateScorecardValiditySide"

    ReleaseGateScorecardValiditySide:
      type: object
      required: [validity]
      properties:
        validity:
          type: string
          enum: [valid, degraded, invalid]
        validity_reason:
          type: string

    ReleaseGateDimensionEvaluation:
      type: object
      required: [state, outcome]
      properties:
        state:
          type: string
        better_direction:
          type: string
        observed_delta:
          type: number
          format: double
        worsening_delta:
          type: number
          format: double
        warn_threshold:
          type: number
          format: double
        fail_threshold:
          type: number
          format: double
        outcome:
          type: string

    ReleaseGateRegressionViolation:
      type: object
      required: [rule, severity, regression_case_id, suite_id, evidence]
      properties:
        rule:
          type: string
        severity:
          type: string
        regression_case_id:
          type: string
          format: uuid
        suite_id:
          type: string
          format: uuid
        observed_count:
          type: integer
        evidence:
          $ref: "#/components/schemas/ReleaseGateRegressionEvidence"

    ReleaseGateRegressionEvidence:
      type: object
      required: [scoring_result_id, scoring_result_type]
      properties:
        scoring_result_id:
          type: string
          format: uuid
        scoring_result_type:
          type: string
        replay_step_refs:
          type: array
          items:
            $ref: "#/components/schemas/ReleaseGateReplayStepRef"

    ReleaseGateReplayStepRef:
      type: object
      required: [sequence_number]
      properties:
        sequence_number:
          type: integer
          format: int64
        event_type:
          type: string
        kind:
          type: string

    # --- Artifacts ---

    ListArtifactsResponse:
      type: object
      required: [items, total, limit, offset]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/ArtifactUploadResponse"
        total:
          type: integer
          format: int64
        limit:
          type: integer
          format: int32
        offset:
          type: integer
          format: int32

    ArtifactUploadResponse:
      type: object
      required: [id, workspace_id, artifact_type, visibility, metadata, created_at]
      properties:
        id:
          type: string
          format: uuid
        workspace_id:
          type: string
          format: uuid
        run_id:
          type: string
          format: uuid
        run_agent_id:
          type: string
          format: uuid
        artifact_type:
          type: string
        content_type:
          type: string
        size_bytes:
          type: integer
          format: int64
        checksum_sha256:
          type: string
        visibility:
          type: string
        metadata:
          type: object
          additionalProperties: true
        created_at:
          type: string
          format: date-time
        download:
          type: object
          properties:
            url:
              type: string
            expires_at:
              type: string
              format: date-time

    ArtifactDownloadResponse:
      type: object
      required: [id, workspace_id, artifact_type, metadata, url, expires_at]
      properties:
        id:
          type: string
          format: uuid
        workspace_id:
          type: string
          format: uuid
        artifact_type:
          type: string
        content_type:
          type: string
        size_bytes:
          type: integer
          format: int64
        checksum_sha256:
          type: string
        metadata:
          type: object
          additionalProperties: true
        url:
          type: string
        expires_at:
          type: string
          format: date-time

    # --- Challenge Packs ---

    ChallengePack:
      type: object
      required: [id, name, slug, versions, created_at, updated_at]
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        slug:
          type: string
        description:
          type: string
        versions:
          type: array
          items:
            $ref: "#/components/schemas/ChallengePackVersion"
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    ChallengePackVersion:
      type: object
      required: [id, challenge_pack_id, version_number, lifecycle_status, created_at, updated_at]
      properties:
        id:
          type: string
          format: uuid
        challenge_pack_id:
          type: string
          format: uuid
        version_number:
          type: integer
          format: int32
        lifecycle_status:
          type: string
        deployment_defaults:
          type: object
          description: Default deployment aliases and lineups declared by the challenge pack version.
          properties:
            aliases:
              type: object
              additionalProperties:
                type: string
            lineups:
              type: object
              additionalProperties:
                type: array
                items:
                  type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    ListChallengePacksResponse:
      type: object
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/ChallengePack"

    ChallengeInputSetSummary:
      type: object
      required: [id, challenge_pack_version_id, input_key, name]
      properties:
        id:
          type: string
          format: uuid
        challenge_pack_version_id:
          type: string
          format: uuid
        input_key:
          type: string
        name:
          type: string

    ListChallengeInputSetsResponse:
      type: object
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/ChallengeInputSetSummary"

    PublishChallengePackResponse:
      type: object
      required: [challenge_pack_id, challenge_pack_version_id, evaluation_spec_id, input_set_ids]
      properties:
        challenge_pack_id:
          type: string
          format: uuid
        challenge_pack_version_id:
          type: string
          format: uuid
        evaluation_spec_id:
          type: string
          format: uuid
        input_set_ids:
          type: array
          items:
            type: string
            format: uuid
        bundle_artifact_id:
          type: string
          format: uuid

    ChallengePackCatalogSummary:
      type: object
      required: [slug, name, family, execution_mode, spec_card]
      properties:
        slug:
          type: string
        name:
          type: string
        family:
          type: string
        category:
          type: string
          enum: [enterprise, agent_capability, safety]
        tags:
          type: array
          items:
            type: string
        description:
          type: string
        difficulty:
          type: string
          enum: [easy, medium, hard, expert]
        execution_mode:
          type: string
        estimated_cost_usd:
          type: number
        estimated_runtime_ms:
          type: integer
          format: int64
        spec_card:
          $ref: "#/components/schemas/SpecCard"

    ChallengePackCatalogEntry:
      allOf:
        - $ref: "#/components/schemas/ChallengePackCatalogSummary"
        - type: object
          required: [yaml]
          properties:
            yaml:
              type: string
              description: The raw, runnable challenge-pack bundle YAML.

    InstantiateChallengePackCatalogResponse:
      type: object
      required: [challenge_pack_id, challenge_pack_version_id, slug, already_existed, runnable]
      properties:
        challenge_pack_id:
          type: string
          format: uuid
        challenge_pack_version_id:
          type: string
          format: uuid
        evaluation_spec_id:
          type: string
          format: uuid
        input_set_ids:
          type: array
          items:
            type: string
            format: uuid
        slug:
          type: string
        already_existed:
          type: boolean
          description: True when the workspace already had this pack and the existing copy was returned.
        runnable:
          type: boolean

    # --- Challenge Pack Builder ---

    ChallengePiece:
      type: object
      required:
        [
          id,
          workspace_id,
          kind,
          slug,
          name,
          description,
          definition,
          lifecycle_status,
          created_at,
          updated_at,
        ]
      properties:
        id:
          type: string
          format: uuid
        workspace_id:
          type: string
          format: uuid
        kind:
          type: string
          enum:
            - validator
            - judge
            - input_set
            - challenge
        slug:
          type: string
        name:
          type: string
        description:
          type: string
        definition:
          type: object
          additionalProperties: true
          description: Free-form piece definition; its shape depends on `kind`.
        lifecycle_status:
          type: string
          enum: [active, archived]
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    ListChallengePiecesResponse:
      type: object
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/ChallengePiece"

    StarterPiece:
      type: object
      required: [kind, slug, name, description, definition]
      properties:
        kind:
          type: string
          enum: [validator, judge, input_set, challenge]
        slug:
          type: string
        name:
          type: string
        description:
          type: string
        definition:
          type: object
          additionalProperties: true
          description: Free-form piece definition; its shape depends on `kind`.

    CreateChallengePieceRequest:
      type: object
      required: [kind, slug, name, definition]
      properties:
        kind:
          type: string
          enum:
            - validator
            - judge
            - input_set
            - challenge
        slug:
          type: string
        name:
          type: string
        description:
          type: string
        definition:
          type: object
          additionalProperties: true
          description: Free-form piece definition; its shape depends on `kind`.

    PatchChallengePieceRequest:
      type: object
      properties:
        name:
          type: string
        slug:
          type: string
        description:
          type: string
        definition:
          type: object
          additionalProperties: true
          description: Free-form piece definition; its shape depends on the piece's kind.

    ChallengePackDraft:
      type: object
      required:
        [
          id,
          workspace_id,
          name,
          execution_mode,
          composition,
          status,
          created_at,
          updated_at,
        ]
      properties:
        id:
          type: string
          format: uuid
        workspace_id:
          type: string
          format: uuid
        name:
          type: string
        execution_mode:
          type: string
        challenge_pack_id:
          type: ["string", "null"]
          format: uuid
          description: Set when the draft is linked to an existing challenge pack.
        composition:
          type: object
          additionalProperties: true
          description: Free-form draft composition referencing reusable pieces.
        status:
          type: string
          enum:
            - draft
            - published
            - discarded
        last_published_version_id:
          type: ["string", "null"]
          format: uuid
          description: Set after the draft has been published to a challenge pack version.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    ListChallengePackDraftsResponse:
      type: object
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/ChallengePackDraft"

    CreateChallengePackDraftRequest:
      type: object
      properties:
        name:
          type: string
          description: >
            Display name. Required when from_challenge_pack_version_id is
            omitted; optional when it is set - the server defaults it to the
            source pack's name.
        execution_mode:
          type: string
          enum:
            - native
            - prompt_eval
            - responses
            - multi_turn
        challenge_pack_id:
          type: string
          format: uuid
          description: Optionally link the draft to an existing challenge pack.
        composition:
          type: object
          additionalProperties: true
          description: Free-form draft composition referencing reusable pieces.
        from_challenge_pack_version_id:
          type: string
          format: uuid
          description: >
            Hydrate the draft's composition from an already-published pack
            version (edit-in-builder). Mutually exclusive with `composition`.
            execution_mode and name default from the source pack.

    GenerateChallengePackDraftRequest:
      type: object
      required: [description, provider_account_id, model]
      properties:
        description:
          type: string
          minLength: 1
          maxLength: 8000
          description: >
            Plain-English description of the app to evaluate, counted in
            characters. Treated as untrusted data by the generator, never as
            instructions. The server rejects a blank or whitespace-only value.
        provider_account_id:
          type: string
          format: uuid
          description: >
            Active provider account in this workspace whose credentials author
            the pack (BYOK). Also used as the judge model's provider.
        model:
          type: string
          minLength: 1
          description: Provider model ID used for generation, e.g. `gpt-4.1`.

    PatchChallengePackDraftRequest:
      type: object
      properties:
        name:
          type: string
        execution_mode:
          type: string
          enum:
            - native
            - prompt_eval
            - responses
            - multi_turn
        composition:
          type: object
          additionalProperties: true
          description: Free-form draft composition referencing reusable pieces.

    CompileDraftResponse:
      type: object
      required: [valid, errors, spec_card, yaml]
      properties:
        valid:
          type: boolean
        errors:
          type: array
          items:
            $ref: "#/components/schemas/ValidationErrorDetail"
        spec_card:
          $ref: "#/components/schemas/SpecCard"
        yaml:
          type: string
          description: The equivalent challenge pack bundle rendered as YAML.

    SpecCard:
      type: object
      required:
        [
          pack_name,
          slug,
          family,
          execution_mode,
          challenge_count,
          case_count,
          validator_count,
          judge_count,
          strategy,
          dimensions,
          pass_criteria,
        ]
      properties:
        pack_name:
          type: string
        slug:
          type: string
        family:
          type: string
        description:
          type: string
        execution_mode:
          type: string
        challenge_count:
          type: integer
        case_count:
          type: integer
        validator_count:
          type: integer
        judge_count:
          type: integer
        strategy:
          type: string
        dimensions:
          type: array
          items:
            $ref: "#/components/schemas/SpecCardDimension"
        pass_criteria:
          type: string

    SpecCardDimension:
      type: object
      required: [key, source, gate, summary]
      properties:
        key:
          type: string
        source:
          type: string
        weight:
          type: number
        gate:
          type: boolean
        pass_threshold:
          type: number
        references:
          type: array
          items:
            type: string
        summary:
          type: string

    # --- Agent Builds ---

    CreateAgentBuildRequest:
      type: object
      required: [name]
      properties:
        name:
          type: string
        description:
          type: string

    AgentBuild:
      type: object
      required: [id, workspace_id, name, slug, lifecycle_status, created_at, updated_at]
      properties:
        id:
          type: string
          format: uuid
        workspace_id:
          type: string
          format: uuid
        name:
          type: string
        slug:
          type: string
        description:
          type: string
        lifecycle_status:
          type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    AgentBuildDetail:
      allOf:
        - $ref: "#/components/schemas/AgentBuild"
        - type: object
          required: [versions]
          properties:
            versions:
              type: array
              items:
                $ref: "#/components/schemas/AgentBuildVersion"

    ListAgentBuildsResponse:
      type: object
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/AgentBuild"

    CreateAgentBuildVersionRequest:
      type: object
      properties:
        agent_kind:
          type: string
          default: llm_agent
        interface_spec:
          type: object
          additionalProperties: true
        policy_spec:
          type: object
          additionalProperties: true
        reasoning_spec:
          type: object
          additionalProperties: true
        memory_spec:
          type: object
          additionalProperties: true
        workflow_spec:
          type: object
          additionalProperties: true
        guardrail_spec:
          type: object
          additionalProperties: true
        model_spec:
          type: object
          additionalProperties: true
        output_schema:
          type: object
          additionalProperties: true
        trace_contract:
          type: object
          additionalProperties: true
        publication_spec:
          type: object
          additionalProperties: true
        tools:
          type: array
          items:
            $ref: "#/components/schemas/ToolBinding"
        knowledge_sources:
          type: array
          items:
            $ref: "#/components/schemas/KnowledgeSourceBinding"

    AgentBuildVersion:
      type: object
      required: [id, agent_build_id, version_number, version_status, agent_kind, interface_spec, policy_spec, reasoning_spec, memory_spec, workflow_spec, guardrail_spec, model_spec, output_schema, trace_contract, publication_spec, tools, knowledge_sources, created_at]
      properties:
        id:
          type: string
          format: uuid
        agent_build_id:
          type: string
          format: uuid
        version_number:
          type: integer
          format: int32
        version_status:
          type: string
          enum: [draft, ready, deprecated]
        agent_kind:
          type: string
        interface_spec:
          type: object
          additionalProperties: true
        policy_spec:
          type: object
          additionalProperties: true
        reasoning_spec:
          type: object
          additionalProperties: true
        memory_spec:
          type: object
          additionalProperties: true
        workflow_spec:
          type: object
          additionalProperties: true
        guardrail_spec:
          type: object
          additionalProperties: true
        model_spec:
          type: object
          additionalProperties: true
        output_schema:
          type: object
          additionalProperties: true
        trace_contract:
          type: object
          additionalProperties: true
        publication_spec:
          type: object
          additionalProperties: true
        tools:
          type: array
          items:
            $ref: "#/components/schemas/ToolBindingResponse"
        knowledge_sources:
          type: array
          items:
            $ref: "#/components/schemas/KnowledgeSourceBindingResponse"
        created_at:
          type: string
          format: date-time

    ToolBinding:
      type: object
      required: [tool_id, binding_role, binding_config]
      properties:
        tool_id:
          type: string
        binding_role:
          type: string
        binding_config:
          type: object
          additionalProperties: true

    ToolBindingResponse:
      type: object
      required: [tool_id, binding_role, binding_config]
      properties:
        tool_id:
          type: string
          format: uuid
        binding_role:
          type: string
        binding_config:
          type: object
          additionalProperties: true

    KnowledgeSourceBinding:
      type: object
      required: [knowledge_source_id, binding_role, binding_config]
      properties:
        knowledge_source_id:
          type: string
        binding_role:
          type: string
        binding_config:
          type: object
          additionalProperties: true

    KnowledgeSourceBindingResponse:
      type: object
      required: [knowledge_source_id, binding_role, binding_config]
      properties:
        knowledge_source_id:
          type: string
          format: uuid
        binding_role:
          type: string
        binding_config:
          type: object
          additionalProperties: true

    # --- Agent Deployments ---

    AgentDeployment:
      type: object
      required: [id, organization_id, workspace_id, current_build_version_id, name, status, created_at, updated_at]
      properties:
        id:
          type: string
          format: uuid
        organization_id:
          type: string
          format: uuid
        workspace_id:
          type: string
          format: uuid
        current_build_version_id:
          type: string
          format: uuid
        name:
          type: string
        status:
          type: string
        latest_snapshot_id:
          type: string
          format: uuid
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    ListAgentDeploymentsResponse:
      type: object
      required: [items, total, limit, offset]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/AgentDeployment"
        total:
          type: integer
          format: int64
        limit:
          type: integer
          format: int32
        offset:
          type: integer
          format: int32

    CreateAgentDeploymentRequest:
      type: object
      required: [name, agent_build_id, build_version_id, runtime_profile_id, model]
      properties:
        name:
          type: string
        agent_build_id:
          type: string
          format: uuid
        build_version_id:
          type: string
          format: uuid
        runtime_profile_id:
          type: string
          format: uuid
        provider_account_id:
          type: string
          format: uuid
        model:
          type: string
        deployment_config:
          type: object
          additionalProperties: true

    AgentDeploymentCreateResponse:
      type: object
      required: [id, workspace_id, agent_build_id, current_build_version_id, name, slug, deployment_type, status, created_at, updated_at]
      properties:
        id:
          type: string
          format: uuid
        workspace_id:
          type: string
          format: uuid
        agent_build_id:
          type: string
          format: uuid
        current_build_version_id:
          type: string
          format: uuid
        name:
          type: string
        slug:
          type: string
        deployment_type:
          type: string
        status:
          type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    QuickCreateAgentRequest:
      type: object
      required: [name, runtime_profile_id, provider_account_id, model]
      description: >
        Either instructions or template must be provided. An explicit
        instructions string takes precedence; otherwise the template supplies
        the agent's policy.
      properties:
        name:
          type: string
        description:
          type: string
        instructions:
          type: string
          description: System prompt for the agent. Stored as the version policy.
        agent_kind:
          type: string
          description: Defaults to llm_agent.
        template:
          type: string
          description: Built-in build-version template key (e.g. honest-agent).
        model_spec:
          type: object
          additionalProperties: true
        runtime_profile_id:
          type: string
          format: uuid
        provider_account_id:
          type: string
          format: uuid
        model:
          type: string
        deployment_config:
          type: object
          additionalProperties: true

    QuickCreateAgentResponse:
      type: object
      required: [build, version, deployment]
      properties:
        build:
          $ref: "#/components/schemas/AgentBuild"
        version:
          $ref: "#/components/schemas/AgentBuildVersion"
        deployment:
          $ref: "#/components/schemas/AgentDeploymentCreateResponse"

    # --- Hosted Runs ---

    HostedRunEvent:
      type: object
      required: [run_agent_id, external_run_id, event_type, occurred_at]
      properties:
        run_agent_id:
          type: string
          format: uuid
        external_run_id:
          type: string
        event_type:
          type: string
          enum: [run_started, final_answer, error, run_finished]
        occurred_at:
          type: string
          format: date-time
        final_status:
          type: string
          enum: [completed, failed]
        output:
          type: object
          additionalProperties: true
        error_message:
          type: string
        latency_ms:
          type: integer
          format: int64
        metadata:
          type: object
          additionalProperties: true
          description: >
            Optional hosted trace metadata. When `metadata.trace_events` (or
            `metadata.events`) is present, AgentClash ingests each entry as an
            additional canonical replay event so hosted runs can surface
            structured internal subagent/model/tool steps in replay and UI.

    # --- Agent Tryouts ---

    AgentTryoutTemplate:
      type: object
      required: [slug, name, description, available, input_schema, tool_policy, runtime, evaluation_spec, default_model_policy, anonymous_enabled, max_input_bytes, max_duration_seconds, max_cost_usd]
      properties:
        slug:
          type: string
        name:
          type: string
        description:
          type: string
        available:
          type: boolean
          description: Whether this template can currently be executed by the hosted tryout runtime.
        unavailable_reason:
          type: string
          description: Product-facing reason shown when a template is not executable in the current environment.
        input_schema:
          type: object
          additionalProperties: true
        tool_policy:
          type: object
          additionalProperties: true
        runtime:
          type: object
          additionalProperties: true
          description: Server-owned runtime policy including adapter, sandbox policy, expected artifacts, and validation metadata.
        evaluation_spec:
          type: object
          additionalProperties: true
        default_model_policy:
          type: object
          additionalProperties: true
        anonymous_enabled:
          type: boolean
        max_input_bytes:
          type: integer
          format: int64
        max_duration_seconds:
          type: integer
          format: int32
        max_cost_usd:
          type: number
          format: double

    AgentTryoutInputAttachment:
      type: object
      required: [id, filename, media_type, size_bytes]
      properties:
        id:
          type: string
          format: uuid
        filename:
          type: string
        media_type:
          type: string
        size_bytes:
          type: integer
          format: int64

    ListAgentTryoutTemplatesResponse:
      type: object
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/AgentTryoutTemplate"

    CreateAgentTryoutRequest:
      type: object
      required: [template_slug, input]
      properties:
        template_slug:
          type: string
        input:
          type: object
          additionalProperties: true
        selected_harness_kind:
          type: string
          description: >-
            Optional agent harness to run the tryout. One of codex_e2b,
            claude_e2b, openclaw_e2b, hermes_e2b. Empty uses the hosted default.
          enum: [codex_e2b, claude_e2b, openclaw_e2b, hermes_e2b]
        selected_model_policy:
          type: object
          additionalProperties: true
          description: Optional explicit model policy for the selected harness.
        judge:
          $ref: "#/components/schemas/AgentTryoutJudgeSelection"
        agent_instructions:
          type: string
          maxLength: 8000
          description: >-
            Optional user-authored agent system prompt. When present it becomes
            the agent's primary behavioral directive (the template still
            supplies task framing, inputs, and expected deliverables). Stored in
            input_snapshot under agent_design.instructions.
        agent_tool_slugs:
          type: array
          items:
            type: string
          maxItems: 24
          description: >-
            Optional tool-library slugs the agent is equipped with. Unknown
            slugs are dropped; the list is capped at 24. Surfaced in the prompt
            as the agent's abilities and stored under
            agent_design.tool_slugs.
        agent_name:
          type: string
          maxLength: 120
          description: >-
            Optional display name for the authored agent. Stored under
            agent_design.name.

    AgentTryoutJudgeSelection:
      type: object
      description: >-
        LLM-as-judge selection for anonymous tryouts. The judge always runs on
        hosted platform keys; the model must come from the hosted allowlist
        (default gpt-5-mini, claude-haiku-4-5, gemini-2.5-flash). Omitting the
        object enables the default judge. The selection is baked into the
        tryout's evaluation_spec_snapshot as llm_judges declarations, and the
        verdict appears in the completed summary under scorecard.judge with
        model, strictness, verdict (approved | needs_edits | rejected |
        not_judged), score, and per-criterion entries carrying status and the
        judge's reasoning.
      properties:
        model:
          type: string
          description: Judge model from the hosted allowlist. Empty uses the default.
        strictness:
          type: string
          enum: [lenient, standard, harsh]
          description: How harshly the judge grades. Defaults to standard.

    ClaimAgentTryoutRequest:
      type: object
      required: [workspace_id]
      properties:
        workspace_id:
          type: string
          format: uuid

    AgentTryoutResponse:
      type: object
      required: [id, template_slug, status, input_snapshot, template_snapshot, tool_policy_snapshot, evaluation_spec_snapshot, selected_model_policy, summary, redaction_status, cost_limit_usd, max_duration_seconds, created_at, updated_at]
      properties:
        id:
          type: string
          format: uuid
        organization_id:
          type: string
          format: uuid
        workspace_id:
          type: string
          format: uuid
        template_slug:
          type: string
        status:
          type: string
          enum: [queued, running, completed, failed, cancelled]
        input_snapshot:
          type: object
          additionalProperties: true
        template_snapshot:
          type: object
          additionalProperties: true
        tool_policy_snapshot:
          type: object
          additionalProperties: true
        evaluation_spec_snapshot:
          type: object
          additionalProperties: true
        selected_model_policy:
          type: object
          additionalProperties: true
        selected_harness_kind:
          type: string
          description: Agent harness the tryout ran on, when explicitly selected.
        summary:
          type: object
          additionalProperties: true
          description: >-
            Free-form status/result payload. On a completed tryout it carries
            outputs and scorecard (with scorecard.judge), and — best-effort —
            a coaching block matching AgentTryoutCoaching when AI coaching was
            produced. Coaching is omitted on any failure.
          properties:
            coaching:
              $ref: "#/components/schemas/AgentTryoutCoaching"
        redaction_status:
          type: string
          enum: [pending, passed, failed, not_required]
        run_id:
          type: string
          format: uuid
        parent_tryout_id:
          type: string
          format: uuid
          description: Source tryout this one was rerun from, when applicable.
        cost_limit_usd:
          type: number
          format: double
        actual_cost_usd:
          type: number
          format: double
        latency_ms:
          type: integer
          format: int64
        max_duration_seconds:
          type: integer
          format: int32
        created_by_user_id:
          type: string
          format: uuid
        claimed_by_user_id:
          type: string
          format: uuid
        claimed_at:
          type: string
          format: date-time
        expires_at:
          type: string
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    PublicAgentTryoutResponse:
      type: object
      required: [id, template_slug, status, input_snapshot, template_snapshot, tool_policy_snapshot, evaluation_spec_snapshot, selected_model_policy, summary, redaction_status, cost_limit_usd, max_duration_seconds, created_at, updated_at]
      properties:
        id:
          type: string
          format: uuid
        template_slug:
          type: string
        status:
          type: string
          enum: [queued, running, completed, failed, cancelled]
        input_snapshot:
          type: object
          additionalProperties: true
        template_snapshot:
          type: object
          additionalProperties: true
        tool_policy_snapshot:
          type: object
          additionalProperties: true
        evaluation_spec_snapshot:
          type: object
          additionalProperties: true
        selected_model_policy:
          type: object
          additionalProperties: true
        selected_harness_kind:
          type: string
          description: Agent harness the tryout ran on, when explicitly selected.
        summary:
          type: object
          additionalProperties: true
          description: >-
            Free-form status/result payload. On a completed tryout it carries
            outputs and scorecard (with scorecard.judge), and — best-effort —
            a coaching block matching AgentTryoutCoaching when AI coaching was
            produced. Coaching is omitted on any failure.
          properties:
            coaching:
              $ref: "#/components/schemas/AgentTryoutCoaching"
        redaction_status:
          type: string
          enum: [pending, passed, failed, not_required]
        run_id:
          type: string
          format: uuid
        cost_limit_usd:
          type: number
          format: double
        actual_cost_usd:
          type: number
          format: double
        latency_ms:
          type: integer
          format: int64
        max_duration_seconds:
          type: integer
          format: int32
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    ListAgentTryoutsResponse:
      type: object
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/AgentTryoutResponse"

    TryoutTimelineEvent:
      type: object
      required: [cursor, sequence, type, summary, occurred_at]
      properties:
        cursor:
          type: integer
          format: int64
          description: Stable, monotonic resume cursor for this event. Pass the page's `next_cursor` as the `after` query param to continue.
        sequence:
          type: integer
          format: int64
          description: Per-agent sequence number of the underlying run event.
        type:
          type: string
          description: Stable product-level timeline category for frontend rendering.
          enum: [started, planning, tool_call, sandbox_command, file_written, file_activity, validation, scoring, finished, activity]
        summary:
          type: string
          description: Short, human-readable, secret-free description of the event.
        occurred_at:
          type: string
          format: date-time
        payload:
          type: object
          additionalProperties: true
          description: >-
            Optional expandable structured detail. Redacted to a non-sensitive
            allow-list on the public/anonymous endpoint; the full run-event
            payload on the workspace endpoint. Omitted when no detail is
            available.

    AgentTryoutEventsResponse:
      type: object
      required: [tryout_id, status, cost_limit_usd, events, next_cursor, has_more]
      properties:
        tryout_id:
          type: string
          format: uuid
        status:
          type: string
          enum: [queued, running, completed, failed, cancelled]
        run_id:
          type: string
          format: uuid
        cost_limit_usd:
          type: number
          format: double
        actual_cost_usd:
          type: number
          format: double
        latency_ms:
          type: integer
          format: int64
        events:
          type: array
          items:
            $ref: "#/components/schemas/TryoutTimelineEvent"
        next_cursor:
          type: integer
          format: int64
          description: Cursor to pass as `after` on the next poll to resume from where this page ended.
        has_more:
          type: boolean
          description: >-
            True when more rows were available beyond this page at query time.
            This reflects raw run-event rows scanned, not displayable events
            produced: a page can return an empty `events` array while `has_more`
            is `true` (e.g. when every scanned row is an internal/streaming type
            that the timeline filters out). Always advance using `next_cursor`
            rather than treating `has_more` as "more displayable events exist",
            and rate-limit polling independently — combine with `status` to
            decide when the tryout is finished and polling can stop.

    AgentTryoutShareLink:
      type: object
      required: [id, resource_type, resource_id, search_indexing, view_count, created_at, updated_at]
      properties:
        id:
          type: string
          format: uuid
        resource_type:
          type: string
          enum: [agent_tryout]
        resource_id:
          type: string
          format: uuid
        search_indexing:
          type: boolean
        view_count:
          type: integer
          format: int64
        expires_at:
          type: string
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        url:
          type: string

    AgentTryoutShareResponse:
      type: object
      required: [share, token]
      properties:
        share:
          $ref: "#/components/schemas/AgentTryoutShareLink"
        token:
          type: string

    RerunAgentTryoutRequest:
      type: object
      required: [selected_model_policy]
      properties:
        selected_model_policy:
          type: object
          additionalProperties: true
          description: >-
            The model policy to rerun under. Must set a `mode` or at least one
            explicit `models[]` entry; explicit models must name a known
            provider.
        agent_design_provided:
          type: boolean
          description: >-
            When true the rerun overrides the source tryout's agent design with
            the agent_* fields below (after normalization), making the agent
            design — not just the model — the varied dimension. When false or
            omitted the rerun inherits the source's agent_design unchanged.
        agent_instructions:
          type: string
          maxLength: 8000
          description: >-
            Override agent system prompt for the rerun. Only applied when
            agent_design_provided is true.
        agent_tool_slugs:
          type: array
          items:
            type: string
          maxItems: 24
          description: >-
            Override tool-library slugs for the rerun. Unknown slugs are
            dropped; capped at 24. Only applied when agent_design_provided is
            true.
        agent_name:
          type: string
          maxLength: 120
          description: >-
            Override agent display name for the rerun. Only applied when
            agent_design_provided is true.

    AgentTryoutCoaching:
      type: object
      description: >-
        Best-effort AI coaching produced after a judge verdict exists. Appears
        under a completed tryout's summary.coaching. Omitted entirely on any
        failure (no verdict, provider error, unparsable output).
      required: [suggestions]
      properties:
        suggestions:
          type: array
          maxItems: 3
          items:
            $ref: "#/components/schemas/AgentTryoutCoachingSuggestion"

    AgentTryoutCoachingSuggestion:
      type: object
      description: One specific, applyable improvement to the user's agent.
      required: [id, title, detail, kind]
      properties:
        id:
          type: string
        title:
          type: string
        detail:
          type: string
        kind:
          type: string
          enum: [prompt, tool, model]
          description: >-
            Whether the suggestion changes the agent's prompt, its tools, or its
            model.
        proposed_instructions:
          type: string
          description: >-
            For kind=prompt, the full revised agent instructions to apply.
            Absent for other kinds.
        add_tool_slugs:
          type: array
          items:
            type: string
          description: >-
            For kind=tool, library tool slugs to add to the agent. Only slugs
            recognized by the tool library are returned. Absent for other kinds.

    PromoteAgentTryoutRequest:
      type: object
      properties:
        target:
          type: string
          enum: [vibe_eval]
          default: vibe_eval
          description: Promotion target. Only `vibe_eval` is supported in v1.
        title:
          type: string
          description: Optional title for the created draft conversation.

    AgentTryoutPromotionResult:
      type: object
      required: [target, workspace_id, draft_id]
      properties:
        target:
          type: string
        workspace_id:
          type: string
          format: uuid
          description: Workspace that owns the created challenge pack draft.
        draft_id:
          type: string
          format: uuid
          description: >-
            The created `challenge_pack_drafts` row. Open it in the visual pack
            builder at
            `/workspaces/{workspace_id}/challenge-packs/builder/{draft_id}`, or
            drive it through
            `/v1/workspaces/{workspaceID}/challenge-pack-drafts/{draftID}`.

    CompareAgentTryoutsRequest:
      type: object
      required: [tryout_ids]
      properties:
        tryout_ids:
          type: array
          minItems: 2
          maxItems: 4
          items:
            type: string
            format: uuid

    AgentTryoutCompareParticipant:
      type: object
      required: [id, template_slug, status, redaction_status, selected_model_policy, cost_limit_usd, summary, events_url]
      properties:
        id:
          type: string
          format: uuid
        template_slug:
          type: string
        status:
          type: string
          enum: [queued, running, completed, failed, cancelled]
        redaction_status:
          type: string
          enum: [pending, passed, failed, not_required]
        selected_model_policy:
          type: object
          additionalProperties: true
        run_id:
          type: string
          format: uuid
        parent_tryout_id:
          type: string
          format: uuid
        cost_limit_usd:
          type: number
          format: double
        actual_cost_usd:
          type: number
          format: double
        latency_ms:
          type: integer
          format: int64
        summary:
          type: object
          additionalProperties: true
        events_url:
          type: string
          description: Link to the workspace events timeline for full evidence.

    AgentTryoutCompareResult:
      type: object
      required: [workspace_id, participants]
      properties:
        workspace_id:
          type: string
          format: uuid
        participants:
          type: array
          items:
            $ref: "#/components/schemas/AgentTryoutCompareParticipant"

    AgentTryoutArtifact:
      type: object
      required: [id, artifact_type, created_at]
      properties:
        id:
          type: string
          format: uuid
        key:
          type: string
          description: Template-defined logical name of the output (e.g. "deck_outline").
        path:
          type: string
          description: Sandbox-relative path the file was produced at (e.g. "deck.md").
        artifact_type:
          type: string
        content_type:
          type: string
        size_bytes:
          type: integer
          format: int64
        download_url:
          type: string
          description: Signed, time-limited URL to fetch the artifact content. Absent if no signer is configured.
        download_expires_at:
          type: string
          format: date-time
        created_at:
          type: string
          format: date-time

    ListAgentTryoutArtifactsResponse:
      type: object
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/AgentTryoutArtifact"
