openapi: "3.1.0"
info:
  title: Fabro Run API
  version: "0.2.0"
  description: HTTP API for managing Fabro workflow run executions.

tags:
  - name: Discovery
    description: API discovery and health
  - name: Install
    description: First-run browser install workflow
  - name: Integrations
    description: External provider callbacks and integration endpoints
  - name: Auth
    description: Browser authentication
  - name: Runs
    description: Run management operations
  - name: Automations
    description: Server-managed automation definitions and automation-triggered runs
  - name: Environments
    description: Server-managed execution environment catalog
  - name: MCP Servers
    description: Server-managed MCP server definitions referenced by id from workflow configs
  - name: Sandboxes
    description: Provider-backed sandbox inventory
  - name: Sessions
    description: Ask Fabro sessions bound to runs
  - name: Human-in-the-Loop
    description: Questions, answers, and steering for runs
  - name: Run Outputs
    description: Files produced by runs
  - name: Run Internals
    description: Internal run details (stages, turns, context, configuration)
  - name: Workflows
    description: Workflow definitions and execution
  - name: Workflow Versions
    description: Immutable, content-addressed workflow packages
  - name: Billing
    description: Token counts and billed totals
  - name: Insights
    description: SQL query editor and history
  - name: Models
    description: Available LLM models
  - name: Completions
    description: Single-turn LLM completions
  - name: Settings
    description: Platform configuration
  - name: System
    description: Server runtime, maintenance, and event streaming

security:
  - BearerAuth: []
  - SessionCookie: []

paths:
  # ── Discovery ────────────────────────────────────────────────────────

  /:
    get:
      operationId: getRoot
      tags: [Discovery]
      summary: API Discovery
      description: Returns discovery URLs for the API.
      security: []
      responses:
        "200":
          description: Discovery URLs
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RootResponse"

  /health:
    get:
      operationId: getHealth
      tags: [Discovery]
      summary: Health Check
      description: Returns service health status. Used by load balancers and monitoring.
      security: []
      responses:
        "200":
          description: Service is healthy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"

  /install/session:
    get:
      operationId: getInstallSession
      tags: [Install]
      summary: Get install session
      description: >
        Returns the current browser-install session snapshot. Requires the one-time
        install token in `Authorization: Bearer`, `?token=`, or `X-Install-Token`.
      security: []
      responses:
        "200":
          description: Current install session state
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InstallSessionResponse"
        "401":
          description: Invalid or missing install token
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /install/llm/test:
    post:
      operationId: testInstallLlmCredentials
      tags: [Install]
      summary: Validate install LLM credentials
      description: Validates an LLM API key without persisting it. Requires the one-time install token.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InstallLlmTestInput"
      responses:
        "200":
          description: Credentials validated successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InstallLlmValidationResponse"
        "401":
          description: Invalid or missing install token
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "422":
          description: Credential validation failed
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /install/llm:
    put:
      operationId: putInstallLlm
      tags: [Install]
      summary: Save install LLM settings
      description: >-
        Records the LLM providers and API keys chosen during the browser
        install. An empty `providers` list marks the LLM step as completed
        and explicitly skipped. Requires the one-time install token.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InstallLlmProvidersInput"
      responses:
        "204":
          description: LLM settings recorded
        "401":
          description: Invalid or missing install token
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "422":
          description: Invalid install input
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /install/server:
    put:
      operationId: putInstallServer
      tags: [Install]
      summary: Save install server configuration
      description: Records the canonical server URL confirmed by the operator. Requires the one-time install token.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InstallServerConfigInput"
      responses:
        "204":
          description: Server configuration recorded
        "401":
          description: Invalid or missing install token
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "422":
          description: Invalid canonical URL
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /install/object-store/test:
    post:
      operationId: testInstallObjectStore
      tags: [Install]
      summary: Validate install object-store configuration
      description: Validates the browser-install object-store selection without persisting it. Requires the one-time install token.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InstallObjectStoreInput"
      responses:
        "200":
          description: Object-store configuration validated successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InstallObjectStoreValidationResponse"
        "401":
          description: Invalid or missing install token
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "422":
          description: Object-store validation failed
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /install/object-store:
    put:
      operationId: putInstallObjectStore
      tags: [Install]
      summary: Save install object-store configuration
      description: Records the object-store mode selected during browser install. Requires the one-time install token.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InstallObjectStoreInput"
      responses:
        "204":
          description: Object-store configuration recorded
        "401":
          description: Invalid or missing install token
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "422":
          description: Invalid install input
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /install/sandbox/test:
    post:
      operationId: testInstallSandbox
      tags: [Install]
      summary: Validate install sandbox configuration
      description: Validates the browser-install sandbox-provider selection without persisting it. For Daytona, performs a cheap authenticated call against the Daytona SDK to verify the API key. For Docker, returns ok without further checks. Requires the one-time install token.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InstallSandboxInput"
      responses:
        "200":
          description: Sandbox configuration validated successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InstallSandboxValidationResponse"
        "401":
          description: Invalid or missing install token
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "422":
          description: Sandbox validation failed
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /install/sandbox:
    put:
      operationId: putInstallSandbox
      tags: [Install]
      summary: Save install sandbox configuration
      description: Records the sandbox provider selected during browser install. Requires the one-time install token.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InstallSandboxInput"
      responses:
        "204":
          description: Sandbox configuration recorded
        "401":
          description: Invalid or missing install token
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "422":
          description: Invalid install input
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /install/github/token/test:
    post:
      operationId: testInstallGithubToken
      tags: [Install]
      summary: Validate install GitHub token
      description: Validates a GitHub personal access token without persisting it. Requires the one-time install token.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InstallGithubTokenTestInput"
      responses:
        "200":
          description: GitHub token validated successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InstallGithubTokenTestResponse"
        "401":
          description: Invalid or missing install token
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "422":
          description: GitHub token validation failed
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /install/github/token:
    put:
      operationId: putInstallGithubToken
      tags: [Install]
      summary: Save install GitHub token
      description: Records the GitHub personal access token chosen during the browser install. Requires the one-time install token.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InstallGithubTokenInput"
      responses:
        "204":
          description: GitHub token recorded
        "401":
          description: Invalid or missing install token
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "422":
          description: Invalid install input
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /install/github/app/manifest:
    post:
      operationId: createInstallGithubAppManifest
      tags: [Install]
      summary: Build install GitHub App manifest
      description: Builds the GitHub App manifest and stores the temporary callback state for the browser install. Requires the one-time install token.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InstallGithubAppManifestInput"
      responses:
        "200":
          description: GitHub App manifest ready for browser handoff
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InstallGithubAppManifestResponse"
        "401":
          description: Invalid or missing install token
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "422":
          description: Invalid install input or missing prior steps
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /install/github/app/redirect:
    get:
      operationId: completeInstallGithubAppRedirect
      tags: [Install]
      summary: Complete install GitHub App redirect
      description: Manifest-conversion callback target used by GitHub during browser install. Authorized by the callback `state` query parameter rather than the install token.
      security: []
      parameters:
        - name: code
          in: query
          required: true
          schema:
            type: string
        - name: state
          in: query
          required: true
          schema:
            type: string
      responses:
        "302":
          description: Browser redirected back into the install SPA
        "400":
          description: Invalid or expired GitHub App callback state
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: GitHub manifest conversion failed
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /install/finish:
    post:
      operationId: finishInstall
      tags: [Install]
      summary: Finalize browser install
      description: Persists settings, runtime secrets, and install outputs, then schedules the install-mode process to exit cleanly. Requires the one-time install token.
      security: []
      responses:
        "202":
          description: Install persisted successfully; restart handoff in progress
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InstallFinishResponse"
        "401":
          description: Invalid or missing install token
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "422":
          description: Install session is incomplete
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Install persistence failed
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/health:
    get:
      operationId: getApiHealth
      tags: [Discovery]
      summary: Health Check (API)
      description: >
        Returns service health status under the versioned API prefix. Mirrors
        `/health` for callers that prefer a uniform `/api/v1` base.
      security: []
      responses:
        "200":
          description: Service is healthy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"

  /api/v1/health/diagnostics:
    post:
      operationId: runDiagnostics
      tags: [Discovery]
      summary: Run server health diagnostics
      description: Probes external services and server configuration. May be slow.
      responses:
        "200":
          description: Diagnostics report
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DiagnosticsReport"
        "504":
          description: Diagnostics operation timed out
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/openapi.json:
    get:
      operationId: getOpenApiSpec
      tags: [Discovery]
      summary: OpenAPI Specification
      description: Returns the OpenAPI spec as JSON.
      security: []
      responses:
        "200":
          description: OpenAPI specification
          content:
            application/json:
              schema:
                type: object

  /api/v1/webhooks/github:
    post:
      operationId: receiveGithubWebhook
      tags: [Integrations]
      summary: Receive GitHub Webhook
      description: Receives GitHub App webhook deliveries. Requests are authenticated by `X-Hub-Signature-256`, not API bearer auth.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
      responses:
        "200":
          description: Webhook accepted
        "401":
          description: Missing or invalid webhook signature

  /api/v1/user:
    get:
      operationId: getUser
      tags: [Discovery]
      summary: Current User
      description: Returns info about the authenticated user.
      responses:
        "200":
          description: User info
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserResponse"
        "401":
          description: Not authenticated
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  # ── Auth ─────────────────────────────────────────────────────────────

  /api/v1/auth/config:
    get:
      operationId: getAuthConfig
      tags: [Auth]
      summary: Retrieve auth configuration
      description: Returns the browser login methods enabled for this server.
      security: []
      responses:
        "200":
          description: Enabled authentication methods
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AuthConfigResponse"

  /api/v1/auth/me:
    get:
      operationId: getAuthMe
      tags: [Auth]
      summary: Retrieve current browser user
      description: Returns the authenticated browser session user and demo-mode state.
      responses:
        "200":
          description: Current authenticated browser user
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AuthMeResponse"
        "401":
          description: Not authenticated
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/auth/sessions:
    get:
      operationId: listAuthSessions
      tags: [Auth]
      summary: List authenticated sessions
      description: Returns the current browser session and active CLI session chains for the authenticated user.
      responses:
        "200":
          description: Authenticated sessions known to the server
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AuthSessionsResponse"
        "401":
          description: Not authenticated
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/auth/sessions/{id}:
    delete:
      operationId: deleteAuthSession
      tags: [Auth]
      summary: Revoke an authenticated session
      description: Revokes an active CLI session chain. Browser sessions are not revocable in this API version.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Session revoked
        "400":
          description: Malformed or non-revocable session id
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Not authenticated
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Session not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /auth/login/dev-token:
    post:
      operationId: loginDevToken
      tags: [Auth]
      summary: Login with development token
      description: Creates a browser session from an enabled development token.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DevTokenLoginRequest"
      responses:
        "200":
          description: Browser session created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DevTokenLoginResponse"
        "401":
          description: Invalid or disabled development token
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Session secret is not configured
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  # ── Sessions ──────────────────────────────────────────────────────────

  /api/v1/runs/{id}/sessions:
    parameters:
      - $ref: "#/components/parameters/RunId"
    get:
      operationId: listRunSessions
      tags: [Sessions]
      summary: List run sessions
      parameters:
        - name: page[limit]
          in: query
          schema:
            type: integer
            default: 20
            minimum: 1
            maximum: 100
        - name: page[offset]
          in: query
          schema:
            type: integer
            default: 0
            minimum: 0
        - name: order
          in: query
          schema:
            type: string
            enum: [updated_desc, created_desc]
            default: updated_desc
      responses:
        "200":
          description: Ask Fabro sessions for the run
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaginatedSessionList"
    post:
      operationId: createRunSession
      tags: [Sessions]
      summary: Create run session
      description: Creates a read-only Ask Fabro session bound to the run.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateRunSessionRequest"
      responses:
        "201":
          description: Session created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionRecord"
        "400":
          description: Invalid input
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/sessions/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          $ref: "#/components/schemas/SessionId"
    get:
      operationId: getSession
      tags: [Sessions]
      summary: Get session
      responses:
        "200":
          description: Session detail
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionDetail"
        "404":
          description: Session not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/v1/sessions/{id}/events:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          $ref: "#/components/schemas/SessionId"
    get:
      operationId: listSessionEvents
      tags: [Sessions]
      summary: List session events
      description: Returns run event envelopes filtered to this session's durable `run.session.*` events. `since_seq` uses the owning run event sequence.
      parameters:
        - name: since_seq
          in: query
          schema:
            type: integer
            default: 1
            minimum: 1
        - name: limit
          in: query
          schema:
            type: integer
            default: 100
            minimum: 1
            maximum: 1000
      responses:
        "200":
          description: Session-scoped run events
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaginatedEventList"
        "404":
          description: Session not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/sessions/{id}/attach:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          $ref: "#/components/schemas/SessionId"
    get:
      operationId: attachSessionEvents
      tags: [Sessions]
      summary: Attach to session events
      description: Replays and streams this session's durable `run.session.*` events from the owning run event log. The stream remains open until the client disconnects or the server shuts down.
      parameters:
        - name: since_seq
          in: query
          schema:
            type: integer
            minimum: 1
      responses:
        "200":
          description: Streamed session-scoped run events
          content:
            text/event-stream:
              schema:
                type: string
        "404":
          description: Session not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/sessions/{id}/turns:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          $ref: "#/components/schemas/SessionId"
    post:
      operationId: submitSessionTurn
      tags: [Sessions]
      summary: Submit a session turn
      description: Starts a streamed turn immediately. Background turns are not supported in this API version.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SubmitTurnRequest"
      responses:
        "200":
          description: Streamed session events
          headers:
            x-fabro-turn-id:
              description: Durable turn id accepted for this streamed turn.
              schema:
                $ref: "#/components/schemas/TurnId"
          content:
            text/event-stream:
              schema:
                type: string
        "400":
          description: Invalid input
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Session not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Session already has an active turn
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
            x-fabro-active-turn-id:
              description: Durable id of the currently active turn.
              schema:
                $ref: "#/components/schemas/TurnId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/sessions/{id}/turns/{turnId}/interrupt:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          $ref: "#/components/schemas/SessionId"
      - name: turnId
        in: path
        required: true
        schema:
          $ref: "#/components/schemas/TurnId"
    post:
      operationId: interruptSessionTurn
      tags: [Sessions]
      summary: Interrupt a session turn
      responses:
        "202":
          description: Interrupt requested
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventEnvelope"
        "404":
          description: Session not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Turn is not active for this session
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  # ── Workflow Versions ─────────────────────────────────────────────────

  /api/v1/workflow-versions:
    post:
      operationId: createWorkflowVersion
      tags: [Workflow Versions]
      summary: Create Workflow Version
      description: >-
        Validates and stores an immutable workflow package in content-addressed
        storage. Repeating the same canonical content returns the same identifier.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WorkflowVersion"
      responses:
        "201":
          description: Workflow version stored or already present
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreateWorkflowVersionResponse"
        "400":
          description: Malformed JSON (`invalid_json`)
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "413":
          description: Request body exceeds 2 MiB (`workflow_version_too_large`)
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "422":
          description: >-
            Invalid workflow content (`workflow_version_invalid`) or an absent,
            invalid, or non-canonical dependency
            (`workflow_version_dependency_not_found`)
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Workflow version storage failed
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  # ── Runs ──────────────────────────────────────────────────────────────

  /api/v1/runs:
    get:
      operationId: listRuns
      tags: [Runs]
      summary: List Runs
      description: |
        Returns durable run summaries from the backing store, including runs
        persisted before the current server boot. Supports per-status filtering
        and sorting for both list and kanban renderings. Archived runs are
        hidden by default; pass `include_archived=true` (or `status=archived`)
        to include them. Runs in the `removing` bucket are hidden unless
        explicitly requested via `status=removing`.
      parameters:
        - $ref: "#/components/parameters/PageLimit"
        - $ref: "#/components/parameters/PageOffset"
        - $ref: "#/components/parameters/IncludeArchived"
        - $ref: "#/components/parameters/ParentRunId"
        - $ref: "#/components/parameters/RunStatusFilter"
        - $ref: "#/components/parameters/RunsSort"
        - $ref: "#/components/parameters/RunsSortDirection"
      responses:
        "200":
          description: Paginated durable run summaries
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaginatedRunList"
    post:
      operationId: createRun
      tags: [Runs]
      summary: Create Run
      description: Creates a new workflow run in `submitted` status from a self-contained manifest.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RunManifest"
      responses:
        "201":
          description: Run created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Run"
        "400":
          description: Invalid Graphviz source
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/archive:
    post:
      operationId: batchArchiveRuns
      tags: [Runs]
      summary: Archive Runs
      description: >
        Marks up to 250 terminal runs as archived in one fail-soft,
        non-transactional request. Each run is processed independently and
        successful items emit the same per-run archive events as
        `POST /api/v1/runs/{id}/archive`. A valid batch returns `200` even
        when some items fail; inspect `results` and `summary` for per-run
        outcomes. Invalid request bodies are rejected before mutating any run.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BatchRunLifecycleRequest"
      responses:
        "200":
          description: Batch processed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BatchRunLifecycleResponse"
        "400":
          description: Invalid batch request
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Not authenticated
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Request-level server error
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/delete:
    post:
      operationId: batchDeleteRuns
      tags: [Runs]
      summary: Delete Runs
      description: >
        Deletes up to 250 runs in one fail-soft, non-transactional request.
        Each run is processed independently. A valid batch returns `200` even
        when some items fail; inspect `results` and `summary` for per-run
        outcomes. Invalid request bodies are rejected before mutating any run.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BatchDeleteRunsRequest"
      responses:
        "200":
          description: Batch processed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BatchDeleteRunsResponse"
        "400":
          description: Invalid batch request
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Not authenticated
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Request-level server error
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/unarchive:
    post:
      operationId: batchUnarchiveRuns
      tags: [Runs]
      summary: Unarchive Runs
      description: >
        Restores up to 250 archived runs in one fail-soft, non-transactional
        request. Each run is processed independently and successful items emit
        the same per-run unarchive events as
        `POST /api/v1/runs/{id}/unarchive`. A valid batch returns `200` even
        when some items fail; inspect `results` and `summary` for per-run
        outcomes. Invalid request bodies are rejected before mutating any run.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BatchRunLifecycleRequest"
      responses:
        "200":
          description: Batch processed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BatchRunLifecycleResponse"
        "400":
          description: Invalid batch request
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Not authenticated
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Request-level server error
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/resolve:
    get:
      operationId: resolveRun
      tags: [Runs]
      summary: Resolve Run Selector
      description: Resolves a run selector to one durable run summary using server-owned selector semantics.
      parameters:
        - $ref: "#/components/parameters/RunSelector"
      responses:
        "200":
          description: Durable run summary
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Run"
        "400":
          description: Selector is invalid or ambiguous
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: No run matched the selector
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/preflight:
    post:
      operationId: runPreflight
      tags: [Runs]
      summary: Validate Workflow Manifest
      description: Validates runtime readiness for a workflow manifest without creating a run.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RunManifest"
      responses:
        "200":
          description: Preflight report
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PreflightResponse"
        "400":
          description: Invalid manifest or workflow
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/validate:
    post:
      operationId: validateRunManifest
      tags: [Runs]
      summary: Validate Workflow Manifest
      description: Validates workflow structure and diagnostics without runtime readiness checks.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RunManifest"
      responses:
        "200":
          description: Validation result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ValidateResponse"
        "400":
          description: Invalid manifest or workflow
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/graph/render:
    post:
      operationId: renderWorkflowGraph
      tags: [Runs]
      summary: Render Workflow Graph
      description: Validates and renders a workflow manifest as SVG without creating a run.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RenderWorkflowGraphRequest"
      responses:
        "200":
          description: Rendered graph image
          content:
            image/svg+xml:
              schema:
                type: string
                format: binary
        "400":
          description: Invalid manifest or workflow
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}:
    get:
      operationId: retrieveRun
      tags: [Runs]
      summary: Retrieve Run
      description: Returns the durable run summary for a run.
      parameters:
        - $ref: "#/components/parameters/RunId"
      responses:
        "200":
          description: Durable run summary
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Run"
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    patch:
      operationId: updateRun
      tags: [Runs]
      summary: Update Run
      description: Updates mutable run metadata. Title updates are allowed for all run states, including archived runs.
      parameters:
        - $ref: "#/components/parameters/RunId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateRunRequest"
      responses:
        "200":
          description: Updated durable run summary
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Run"
        "400":
          description: Invalid title
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    delete:
      operationId: deleteRun
      tags: [Runs]
      summary: Delete Run
      description: Deletes durable store state, local run scratch data, and the run-owned sandbox unless sandbox preservation is enabled. Active runs require `force=true`.
      parameters:
        - $ref: "#/components/parameters/RunId"
        - $ref: "#/components/parameters/ForceRunDelete"
      responses:
        "200":
          description: Run deleted and sandbox preservation details returned
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DeleteRunResponse"
        "204":
          description: Run deleted or already absent
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Run is active and requires `force=true`
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/parent:
    put:
      operationId: linkRunParent
      tags: [Runs]
      summary: Link Run Parent
      description: Links a run under an orchestration parent. Parent links are mutable for all run states, including archived and terminal runs.
      parameters:
        - $ref: "#/components/parameters/RunId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateRunParentRequest"
      responses:
        "200":
          description: Updated durable run summary
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Run"
        "400":
          description: Self-parent or cycle rejected
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Child or parent run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    delete:
      operationId: unlinkRunParent
      tags: [Runs]
      summary: Unlink Run Parent
      description: Removes a run's orchestration parent. Already-root runs are returned unchanged.
      parameters:
        - $ref: "#/components/parameters/RunId"
      responses:
        "200":
          description: Updated durable run summary
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Run"
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/cancel:
    post:
      operationId: cancelRun
      tags: [Runs]
      summary: Cancel Run
      description: |
        Cancels a pending, runnable, or running run. Pre-execution runs are
        cancelled synchronously. Live runs return after the cancellation
        request is durably recorded and continue converging to a terminal
        cancelled state. Returns 409 if the run has already completed or been
        cancelled.
      parameters:
        - $ref: "#/components/parameters/RunId"
      responses:
        "200":
          description: Run was cancelled synchronously before execution
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Run"
        "202":
          description: Cancellation was durably requested for a live run
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Run"
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Run is not running
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/steer:
    post:
      operationId: steerRun
      tags: [Human-in-the-Loop]
      summary: Steer Run
      description: |
        Send a mid-run steering message to the live agent session(s) of a
        running run. Set `interrupt=true` to atomically interrupt the active
        steerable agent round first, then deliver this message as the next
        user turn. Without `interrupt=true`, the message is appended to the
        steering queue and may buffer until the next steerable agent session.
      parameters:
        - $ref: "#/components/parameters/RunId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SteerRunRequest"
      responses:
        "202":
          description: Steer accepted and forwarded to the worker
        "400":
          description: Invalid request body
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: |
            Run is not currently steerable. Returned when the run is in a
            terminal state, blocked (use the answer endpoint instead), or
            active agent sessions have no live control channel.
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "503":
          description: Worker control channel unavailable
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/pair:
    get:
      operationId: getRunPairStatus
      tags: [Human-in-the-Loop]
      summary: Get Run Pair Status
      parameters:
        - $ref: "#/components/parameters/RunId"
      responses:
        "200":
          description: Current pair and active pairable targets
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RunPairStatusResponse"
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    post:
      operationId: startRunPair
      tags: [Human-in-the-Loop]
      summary: Start Run Pair
      description: Starts pairing with exactly one selected active API-mode agent target.
      parameters:
        - $ref: "#/components/parameters/RunId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PairStartRequest"
      responses:
        "200":
          description: Pair mode installed for the selected target
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PairRecord"
        "400":
          description: Invalid request body
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Run is not pairable, already paired, or selected target is not active/pairable
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "503":
          description: Worker control channel unavailable
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/pair/{pair_id}:
    get:
      operationId: getRunPair
      tags: [Human-in-the-Loop]
      summary: Get Run Pair
      parameters:
        - $ref: "#/components/parameters/RunId"
        - name: pair_id
          in: path
          required: true
          schema:
            $ref: "#/components/schemas/PairId"
      responses:
        "200":
          description: Pair record
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PairRecord"
        "404":
          description: Run or pair not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    delete:
      operationId: endRunPair
      tags: [Human-in-the-Loop]
      summary: End Run Pair
      parameters:
        - $ref: "#/components/parameters/RunId"
        - name: pair_id
          in: path
          required: true
          schema:
            $ref: "#/components/schemas/PairId"
      responses:
        "200":
          description: Pair ended
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PairRecord"
        "404":
          description: Run or pair not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Pair is not current or active
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "503":
          description: Worker control channel unavailable
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/pair/{pair_id}/messages:
    post:
      operationId: sendRunPairMessage
      tags: [Human-in-the-Loop]
      summary: Send Run Pair Message
      parameters:
        - $ref: "#/components/parameters/RunId"
        - name: pair_id
          in: path
          required: true
          schema:
            $ref: "#/components/schemas/PairId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PairMessageRequest"
      responses:
        "202":
          description: Pair message accepted by the runtime
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PairMessageRecord"
        "400":
          description: Invalid request body
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Run or pair not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Pair is not current/active, target is gone, or message was rejected
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "503":
          description: Worker control channel unavailable
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/pair/{pair_id}/transcript:
    get:
      operationId: getRunPairTranscript
      tags: [Human-in-the-Loop]
      summary: Get Run Pair Transcript
      parameters:
        - $ref: "#/components/parameters/RunId"
        - name: pair_id
          in: path
          required: true
          schema:
            $ref: "#/components/schemas/PairId"
        - $ref: "#/components/parameters/SinceSeq"
        - $ref: "#/components/parameters/EventLimit"
      responses:
        "200":
          description: Compact transcript entries for the pair window
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PairTranscriptResponse"
        "400":
          description: Invalid query parameter
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Run or pair not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/interrupt:
    post:
      operationId: interruptRun
      tags: [Human-in-the-Loop]
      summary: Interrupt Run
      description: |
        Interrupt the active steerable agent round without sending steering
        text. The agent keeps its steering lease and waits for a later steer
        message before starting another LLM round.
      parameters:
        - $ref: "#/components/parameters/RunId"
      responses:
        "202":
          description: Interrupt accepted and forwarded to the worker
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: |
            Run is not currently interruptible. Returned when the run is in a
            terminal state, blocked (use the answer endpoint instead), has no
            active steerable agent session, or active agent sessions have no
            live control channel.
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "503":
          description: Worker control channel unavailable
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/start:
    post:
      operationId: startRun
      tags: [Runs]
      summary: Start Run
      description: Requests start for a submitted run. User-created runs become runnable; parent-generated child runs may become pending until approved. Provide `resume=true` to resume an interrupted run from checkpoint. Returns 409 if the run is not startable.
      parameters:
        - $ref: "#/components/parameters/RunId"
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StartRunRequest"
      responses:
        "200":
          description: Run started
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Run"
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Run is not in submitted status
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/approve:
    post:
      operationId: approveRun
      tags: [Runs]
      summary: Approve Run
      description: Approves a pending run that requires pre-execution approval and makes it runnable.
      parameters:
        - $ref: "#/components/parameters/RunId"
      responses:
        "200":
          description: Run approved
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Run"
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Run is not pending approval
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/deny:
    post:
      operationId: denyRun
      tags: [Runs]
      summary: Deny Run
      description: Denies a pending run that requires pre-execution approval and fails it with `approval_denied`.
      parameters:
        - $ref: "#/components/parameters/RunId"
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DenyRunRequest"
      responses:
        "200":
          description: Run denied
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Run"
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Run is not pending approval
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/retry:
    post:
      operationId: retryRun
      tags: [Runs]
      summary: Retry Run
      description: >
        Creates a fresh run from the terminal source run's captured
        durable definition, records `retried_from` on the new run, and schedules
        it for execution. The source run is left unchanged. Active and archived
        runs are not retryable.
      parameters:
        - $ref: "#/components/parameters/RunId"
      responses:
        "201":
          description: New retry run created and scheduled for execution
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Run"
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Source run is not retryable
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/pause:
    post:
      operationId: pauseRun
      tags: [Runs]
      summary: Pause Run
      description: Pauses a running run. Returns 409 if the run is not running.
      parameters:
        - $ref: "#/components/parameters/RunId"
      responses:
        "200":
          description: Run paused
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Run"
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Run is not running
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/unpause:
    post:
      operationId: unpauseRun
      tags: [Runs]
      summary: Unpause Run
      description: Resumes a paused run. Returns 409 if the run is not paused.
      parameters:
        - $ref: "#/components/parameters/RunId"
      responses:
        "200":
          description: Run unpaused
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Run"
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Run is not paused
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/archive:
    post:
      operationId: archiveRun
      tags: [Runs]
      summary: Archive Run
      description: >
        Marks a terminal run (`succeeded`, `failed`, or `dead`) as `archived`.
        Archived runs are hidden from default listings and are read-only until
        unarchived. Idempotent on already-archived runs. Returns 409 if the run
        is not terminal.
      parameters:
        - $ref: "#/components/parameters/RunId"
      responses:
        "200":
          description: Run archived (or already archived)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Run"
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Run is not terminal and cannot be archived
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/rewind:
    post:
      operationId: rewindRun
      tags: [Runs]
      summary: Rewind Run
      description: >
        Creates a new run from an earlier checkpoint of a terminal source run,
        archives the source run, and records `run.superseded_by` on the source
        after archive succeeds. Returns 207 when the new run was created but
        the source archive step failed.
      parameters:
        - $ref: "#/components/parameters/RunId"
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RewindRequest"
      responses:
        "200":
          description: Source archived and new run created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RewindResponse"
        "207":
          description: New run created but source archive failed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RewindResponse"
        "400":
          description: Invalid rewind target
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Source run is archived or is not terminal
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "501":
          description: Operation unsupported for this run
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/fork:
    post:
      operationId: forkRun
      tags: [Runs]
      summary: Fork Run
      description: >
        Creates a new run from a checkpoint of the source run. The source run
        is left untouched.
      parameters:
        - $ref: "#/components/parameters/RunId"
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ForkRequest"
      responses:
        "200":
          description: New run created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ForkResponse"
        "400":
          description: Invalid fork target
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Source run is archived
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "501":
          description: Operation unsupported for this run
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/timeline:
    get:
      operationId: getRunTimeline
      tags: [Runs]
      summary: Get Run Timeline
      description: >
        Returns checkpoint timeline entries from durable run-store checkpoints.
        Metadata branches are write-only archives and are not read by this endpoint.
      parameters:
        - $ref: "#/components/parameters/RunId"
      responses:
        "200":
          description: Run checkpoint timeline
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/TimelineEntryResponse"
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "501":
          description: Operation unsupported for this run
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/unarchive:
    post:
      operationId: unarchiveRun
      tags: [Runs]
      summary: Unarchive Run
      description: >
        Restores an archived run to its prior terminal status. Idempotent on
        runs that are terminal but not archived (returns the current status
        without emitting an event). Returns 409 if the run is active.
      parameters:
        - $ref: "#/components/parameters/RunId"
      responses:
        "200":
          description: Run unarchived (or already not archived)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Run"
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Run is active and cannot be unarchived
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/graph:
    get:
      operationId: retrieveRunGraph
      tags: [Runs]
      summary: Render SVG
      description: Renders the workflow graph as an SVG image using Graphviz.
      parameters:
        - $ref: "#/components/parameters/RunId"
        - name: direction
          in: query
          required: false
          description: Optional Graphviz rank direction override for the rendered graph.
          schema:
            type: string
            enum:
              - LR
              - TB
              - BT
              - RL
      responses:
        "200":
          description: SVG image of the workflow graph
          content:
            image/svg+xml:
              schema:
                type: string
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/graph/source:
    get:
      operationId: retrieveRunGraphSource
      tags: [Runs]
      summary: Retrieve Graphviz DOT source
      description: Returns the raw Graphviz DOT source for the workflow graph (the contents of the workflow's `.fabro` file).
      parameters:
        - $ref: "#/components/parameters/RunId"
      responses:
        "200":
          description: Graphviz DOT source
          content:
            text/vnd.graphviz:
              schema:
                type: string
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/checkpoint:
    get:
      operationId: retrieveRunCheckpoint
      tags: [Run Internals]
      summary: Retrieve Run Checkpoint
      description: Returns the latest checkpoint data for a run, or null if no checkpoint has been recorded yet.
      parameters:
        - $ref: "#/components/parameters/RunId"
      responses:
        "200":
          description: Checkpoint data (null if not yet available)
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/RunCheckpoint"
                  - type: "null"
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/state:
    get:
      operationId: getRunState
      tags: [Run Internals]
      summary: Get Run State
      description: Returns the internal event-sourced run projection. This is not a stable public contract.
      parameters:
        - $ref: "#/components/parameters/RunId"
      responses:
        "200":
          description: Current run projection
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RunProjection"
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/logs:
    get:
      operationId: getRunLogs
      tags: [Run Internals]
      summary: Get Run Logs
      description: Returns the worker tracing log for a run when it is available.
      parameters:
        - $ref: "#/components/parameters/RunId"
      responses:
        "200":
          description: Per-run worker tracing log
          content:
            text/plain; charset=utf-8:
              schema:
                type: string
        "404":
          description: Run not found, or no run log has been written yet
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/pull_request:
    post:
      operationId: createRunPullRequest
      tags: [Runs]
      summary: Create Run Pull Request
      description: |
        Durably requests creation of a pull request for a completed run. The
        server generates the pull request content and creates the GitHub pull
        request after this request returns. Poll the URL in the Location
        response header until the creation succeeds or fails.

        If a creation is already pending for the run, the response returns
        that creation unchanged; any different `model` or `force` values in
        the new request are ignored.
      parameters:
        - $ref: "#/components/parameters/RunId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateRunPullRequestRequest"
      responses:
        "202":
          description: Pull request creation was durably accepted
          headers:
            Location:
              description: URL for the latest pull request creation on this run.
              schema:
                type: string
                format: uri-reference
            Retry-After:
              description: Suggested number of seconds before polling the creation status.
              schema:
                type: integer
                minimum: 0
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PullRequestCreation"
        "400":
          description: Pull request creation does not apply to this run
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: >-
            Pull request already exists for this run. Clients can GET
            /runs/{id}/pull_request to retrieve the stored record.
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "503":
          description: GitHub integration is unavailable on the server
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    put:
      operationId: linkRunPullRequest
      tags: [Runs]
      summary: Link Run Pull Request
      description: Links or replaces the GitHub pull request association for a run without modifying the remote pull request.
      parameters:
        - $ref: "#/components/parameters/RunId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/LinkRunPullRequestRequest"
      responses:
        "200":
          description: Pull request linked
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PullRequestLink"
        "400":
          description: Pull request link request is invalid
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    delete:
      operationId: unlinkRunPullRequest
      tags: [Runs]
      summary: Unlink Run Pull Request
      description: Removes Fabro's stored pull request association for a run without modifying the remote pull request.
      parameters:
        - $ref: "#/components/parameters/RunId"
      responses:
        "200":
          description: Pull request unlinked
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PullRequestLink"
        "404":
          description: Run or stored pull request record not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    get:
      operationId: getRunPullRequest
      tags: [Runs]
      summary: Get Run Pull Request
      description: Returns the stored pull request record for a run plus live GitHub details when available.
      parameters:
        - $ref: "#/components/parameters/RunId"
      responses:
        "200":
          description: Pull request detail
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PullRequestResponse"
        "404":
          description: Run or stored pull request record not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/pull_request/creation:
    get:
      operationId: getRunPullRequestCreation
      tags: [Runs]
      summary: Get Run Pull Request Creation
      description: Returns the latest explicit pull request creation requested for this run.
      parameters:
        - $ref: "#/components/parameters/RunId"
      responses:
        "200":
          description: Latest pull request creation state
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PullRequestCreation"
        "404":
          description: Run or pull request creation not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/pull_request/merge:
    post:
      operationId: mergeRunPullRequest
      tags: [Runs]
      summary: Merge Run Pull Request
      description: Merges the stored pull request for a run on GitHub.
      parameters:
        - $ref: "#/components/parameters/RunId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MergeRunPullRequestRequest"
      responses:
        "200":
          description: Pull request merged
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MergeRunPullRequestResponse"
        "400":
          description: Pull request merge does not apply to this run
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Run or stored pull request record not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: GitHub rejected the merge request
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "503":
          description: GitHub integration is unavailable on the server
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/pull_request/close:
    post:
      operationId: closeRunPullRequest
      tags: [Runs]
      summary: Close Run Pull Request
      description: Closes the stored pull request for a run on GitHub.
      parameters:
        - $ref: "#/components/parameters/RunId"
      responses:
        "200":
          description: Pull request closed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CloseRunPullRequestResponse"
        "400":
          description: Pull request close does not apply to this run
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Run or stored pull request record not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: GitHub rejected the close request
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "503":
          description: GitHub integration is unavailable on the server
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/events:
    get:
      operationId: listRunEvents
      tags: [Run Internals]
      summary: List Run Events
      description: |
        Returns a paginated JSON list of stored run events. Ascending order
        uses `since_seq` as an inclusive cursor. Descending order uses
        `before_seq` as an exclusive cursor and starts at the newest event
        when `before_seq` is omitted.
      parameters:
        - $ref: "#/components/parameters/RunId"
        - $ref: "#/components/parameters/SinceSeq"
        - $ref: "#/components/parameters/EventLimit"
        - $ref: "#/components/parameters/BeforeSeq"
        - $ref: "#/components/parameters/EventOrder"
      responses:
        "200":
          description: Paginated list of run events
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaginatedEventList"
        "400":
          description: Invalid cursor and order combination
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    post:
      operationId: appendRunEvent
      tags: [Run Internals]
      summary: Append Run Event
      description: Appends a validated event to the run event log. Intended for trusted internal callers.
      parameters:
        - $ref: "#/components/parameters/RunId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RunEvent"
      responses:
        "200":
          description: Event appended
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AppendEventResponse"
        "400":
          description: Invalid event payload
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/events/{seq}:
    get:
      operationId: getRunEventDetail
      tags: [Run Internals]
      summary: Get Run Event Detail
      description: Returns one stored run event by source event sequence with content fields separated and truncated.
      parameters:
        - $ref: "#/components/parameters/RunId"
        - name: seq
          in: path
          required: true
          schema:
            type: integer
            minimum: 1
        - name: max_content_length
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 200000
            default: 20000
      responses:
        "200":
          description: Run event detail
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RunEventDetailResponse"
        "400":
          description: Invalid query parameter
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Run or event not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/attach:
    get:
      operationId: attachRunEvents
      tags: [Run Internals]
      summary: Attach Run Events
      description: Opens an ordered server-sent event stream starting at `since_seq`, replaying persisted events and continuing with live updates while the run remains active.
      parameters:
        - $ref: "#/components/parameters/RunId"
        - $ref: "#/components/parameters/SinceSeq"
      responses:
        "200":
          description: Server-sent event stream
          content:
            text/event-stream:
              schema:
                type: string
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/blobs:
    post:
      operationId: writeRunBlob
      tags: [Run Internals]
      summary: Write Run Blob
      description: Writes an opaque binary blob and returns its content-addressed blob hash.
      parameters:
        - $ref: "#/components/parameters/RunId"
      requestBody:
        required: true
        content:
          application/octet-stream:
            schema:
              type: string
              format: binary
          multipart/form-data:
            schema:
              type: object
              required:
                - manifest
              properties:
                manifest:
                  $ref: "#/components/schemas/ArtifactBatchUploadManifest"
              additionalProperties:
                type: string
                format: binary
              description: |
                Strict multipart upload format. The `manifest` part must arrive first with JSON
                matching `ArtifactBatchUploadManifest`. Each subsequent file part name must match
                a manifest entry `part` value.
            encoding:
              manifest:
                contentType: application/json
      responses:
        "200":
          description: Blob written
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WriteBlobResponse"
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/blobs/{blobHash}:
    get:
      operationId: readRunBlob
      tags: [Run Internals]
      summary: Read Run Blob
      description: Reads a previously stored blob by hash.
      parameters:
        - $ref: "#/components/parameters/RunId"
        - $ref: "#/components/parameters/BlobHash"
      responses:
        "200":
          description: Blob contents
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        "404":
          description: Run or blob not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/stages/{stageId}/logs/output:
    get:
      operationId: getRunStageCommandLog
      tags: [Run Internals]
      summary: Tail Command Log
      description: Returns a byte-offset slice of a command stage output log. Bytes are base64-encoded and are not snapped to UTF-8 boundaries.
      parameters:
        - $ref: "#/components/parameters/RunId"
        - $ref: "#/components/parameters/StageId"
        - $ref: "#/components/parameters/CommandLogOffset"
        - $ref: "#/components/parameters/CommandLogLimit"
      responses:
        "200":
          description: Command log bytes.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CommandLogResponse"
        "400":
          description: Invalid stage, offset, or limit.
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Run or stage not found.
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/questions:
    get:
      operationId: listRunQuestions
      tags: [Human-in-the-Loop]
      summary: List Run Questions
      description: Returns pending human-in-the-loop questions for a run. Questions are generated when the workflow needs user input to proceed.
      parameters:
        - $ref: "#/components/parameters/RunId"
        - $ref: "#/components/parameters/PageLimit"
        - $ref: "#/components/parameters/PageOffset"
      responses:
        "200":
          description: Array of pending questions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaginatedApiQuestionList"
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/questions/{qid}/answer:
    post:
      operationId: submitRunAnswer
      tags: [Human-in-the-Loop]
      summary: Submit Run Answer
      description: Submits an answer to a pending question. The answer can be freeform text or a selected option key, depending on the question type.
      parameters:
        - $ref: "#/components/parameters/RunId"
        - $ref: "#/components/parameters/QuestionId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SubmitAnswerRequest"
      responses:
        "204":
          description: Answer accepted
        "400":
          description: Invalid option key
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Question no longer exists or already answered
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/stages:
    get:
      operationId: listRunStages
      tags: [Run Internals]
      summary: List Run Stages
      description: Returns the ordered list of stages in a run's workflow graph with their current status and timing. Stages are bounded by the workflow graph size, typically fewer than 20.
      parameters:
        - $ref: "#/components/parameters/RunId"
        - $ref: "#/components/parameters/PageLimit"
        - $ref: "#/components/parameters/PageOffset"
      responses:
        "200":
          description: Array of run stages
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaginatedRunStageList"
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/stages/{stageId}/events:
    get:
      operationId: listStageEvents
      tags: [Run Internals]
      summary: List Stage Events
      description: Returns a paginated JSON list of stored run events scoped to a single stage visit.
      parameters:
        - $ref: "#/components/parameters/RunId"
        - $ref: "#/components/parameters/StageId"
        - $ref: "#/components/parameters/SinceSeq"
        - $ref: "#/components/parameters/EventLimit"
      responses:
        "200":
          description: Paginated list of stage events
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaginatedEventList"
        "404":
          description: Run not found.
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/stages/{stageId}/context-window:
    get:
      operationId: getRunStageContextWindow
      tags: [Run Internals]
      summary: Get Stage Context Window
      description: |
        Returns the latest best-effort model-visible context-window usage snapshot for an agent stage.
        Known stages without applicable or observed data return `available: false`; missing runs or stages return 404.
      parameters:
        - $ref: "#/components/parameters/RunId"
        - $ref: "#/components/parameters/StageId"
      responses:
        "200":
          description: Latest context-window snapshot or a typed unavailable state.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StageContextWindow"
        "404":
          description: Run or stage not found.
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/artifacts:
    get:
      operationId: listRunArtifacts
      tags: [Run Internals]
      summary: List Run Artifacts
      description: Lists captured artifact files for a run.
      parameters:
        - $ref: "#/components/parameters/RunId"
      responses:
        "200":
          description: Artifact files captured for the run
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RunArtifactListResponse"
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/artifacts/download:
    get:
      operationId: downloadRunArtifacts
      tags: [Run Internals]
      summary: Download Run Artifacts
      description: |
        Streams a ZIP archive with the latest captured version of each artifact path.
        Stage order, retry number, and then stage ID determine the latest version, matching the artifacts page.
        Captures from the graph's boundary nodes are excluded, identified by their `start` and `exit` handler type rather than by node name.

        The archive streams, so the response status is sent before the first artifact is read.
        A failure after that point aborts the transfer rather than returning `500`.
        The ZIP central directory is written last, so a truncated download does not open as a valid archive.
      parameters:
        - $ref: "#/components/parameters/RunId"
      responses:
        "200":
          description: ZIP archive containing the latest artifact files
          headers:
            Content-Disposition:
              description: Attachment filename for the ZIP archive
              schema:
                type: string
          content:
            application/zip:
              schema:
                type: string
                format: binary
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Artifact archive could not be prepared
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/files:
    get:
      operationId: listRunFiles
      tags: [Run Outputs]
      summary: List Run Files Changed
      description: |
        Returns the set of file changes produced by a run as a list of before/after diffs.

        While the run's sandbox is reachable, diffs are resolved live against the sandbox working tree at the current HEAD. Degraded responses keep the same `data: FileDiff[]` shape. File contents are null on every entry; non-sensitive non-flagged entries include `unified_patch`, while sensitive / binary / symlink / submodule / truncated entries render through the same placeholder flags used by the live path.

        Responses are bounded by per-file (256 KiB / 20k lines), per-run aggregate (5 MiB), and per-request (200 files) caps. Files exceeding a cap are returned with `truncated: true` and empty `contents`. Sensitive paths (credentials, keys) are elided with `sensitive: true` and empty `contents`.
      parameters:
        - $ref: "#/components/parameters/RunId"
        - $ref: "#/components/parameters/PageLimit"
        - $ref: "#/components/parameters/PageOffset"
        - name: scope
          in: query
          required: false
          description: Diff scope to return. Defaults to committed changes only. Sandbox-backed responses honor all scopes for tracked files; untracked files are excluded. Final-patch fallback responses always represent the stored committed/final diff.
          schema:
            type: string
            default: committed
            enum:
              - committed
              - uncommitted
              - all
        - name: from_sha
          in: query
          required: false
          description: Explicit start SHA for a commit-range diff. Must be supplied together with `to_sha`; when present, `scope` must be omitted and the response scope is `range`.
          schema:
            type: string
            pattern: "^[0-9a-f]{7,40}$"
        - name: to_sha
          in: query
          required: false
          description: Explicit end SHA for a commit-range diff. Must be supplied together with `from_sha`; when present, `scope` must be omitted and the response scope is `range`.
          schema:
            type: string
            pattern: "^[0-9a-f]{7,40}$"
      responses:
        "200":
          description: File diffs for the run
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaginatedRunFileList"
        "400":
          description: Malformed query parameter (invalid SHA format, one-sided SHA range, or `scope` combined with an explicit SHA range).
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Run not found (or caller lacks access; returned as 404 to prevent enumeration).
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "503":
          description: Transient sandbox subprocess failure (timeout, process kill). Safe to retry.
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/commits:
    get:
      operationId: listRunCommits
      tags: [Run Outputs]
      summary: List Run Commits
      description: |
        Returns commits on the run branch since the run's base SHA, sourced directly from sandbox Git.

        The list uses first-parent chronological history and is capped by `limit` (default and maximum: 100). Commit data is Git-authoritative; Fabro-generated commit messages are not required for correctness.
      parameters:
        - $ref: "#/components/parameters/RunId"
        - name: limit
          in: query
          required: false
          description: Maximum number of commits to return. Defaults to 100 and is capped at 100.
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 100
      responses:
        "200":
          description: Commits on the run branch since the run base.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaginatedRunCommitList"
        "400":
          description: Malformed run id or query parameter.
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Run not found.
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Run has no active sandbox or no base SHA.
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "503":
          description: Sandbox Git history is temporarily unavailable.
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/stages/{stageId}/artifacts:
    get:
      operationId: listStageArtifacts
      tags: [Run Internals]
      summary: List Stage Artifacts
      description: Lists artifact filenames stored for a stage.
      parameters:
        - $ref: "#/components/parameters/RunId"
        - $ref: "#/components/parameters/StageId"
      responses:
        "200":
          description: Artifact filenames for the stage
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ArtifactListResponse"
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    post:
      operationId: putStageArtifact
      tags: [Run Internals]
      summary: Put Stage Artifact
      description: |
        Uploads one or more artifacts for a stage. Intended for trusted internal callers.

        The server accepts both:
        - `application/octet-stream` for single-file uploads with the `filename` query parameter
        - strict manifest-first `multipart/form-data` uploads documented by `ArtifactBatchUploadManifest`

        The generated Rust client currently exposes the octet-stream variant because the OpenAPI
        code generator in this repo does not support multiple request media types on one operation.
      parameters:
        - $ref: "#/components/parameters/RunId"
        - $ref: "#/components/parameters/StageId"
        - $ref: "#/components/parameters/ArtifactRetry"
        - name: filename
          in: query
          required: false
          description: Relative artifact path for `application/octet-stream` uploads. Ignored for multipart uploads.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/octet-stream:
            schema:
              type: string
              format: binary
      responses:
        "204":
          description: Artifact written
        "400":
          description: Invalid filename, multipart manifest, checksum, or upload body
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/stages/{stageId}/artifacts/download:
    get:
      operationId: getStageArtifact
      tags: [Run Internals]
      summary: Get Stage Artifact
      description: Downloads an artifact by filename.
      parameters:
        - $ref: "#/components/parameters/RunId"
        - $ref: "#/components/parameters/StageId"
        - $ref: "#/components/parameters/ArtifactFilename"
        - $ref: "#/components/parameters/ArtifactRetry"
      responses:
        "200":
          description: Artifact contents
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        "400":
          description: Missing filename or retry
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Run, stage, or artifact not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/billing:
    get:
      operationId: retrieveRunBilling
      tags: [Run Outputs]
      summary: Retrieve Run Billing
      description: Returns token counts and billed totals broken down by stage and model for a specific run.
      parameters:
        - $ref: "#/components/parameters/RunId"
      responses:
        "200":
          description: Billing data
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RunBilling"
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/settings:
    get:
      operationId: retrieveRunSettings
      tags: [Run Internals]
      summary: Retrieve Run Settings
      description: Returns the persisted dense `WorkflowSettings` snapshot used to launch this run.
      parameters:
        - $ref: "#/components/parameters/RunId"
      responses:
        "200":
          description: Run settings
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WorkflowSettings"
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/preview:
    post:
      operationId: generatePreviewUrl
      tags: [Human-in-the-Loop]
      summary: Preview URL
      description: Generates a preview URL for a port exposed by the run's sandbox environment.
      parameters:
        - $ref: "#/components/parameters/RunId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PreviewUrlRequest"
      responses:
        "201":
          description: Preview URL created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PreviewUrlResponse"
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Run has no active sandbox
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/ssh:
    post:
      operationId: createRunSshAccess
      tags: [Human-in-the-Loop]
      summary: Sandbox Access Command
      description: Creates a command for connecting to the run's sandbox environment. Daytona runs return a time-limited SSH command; Docker runs return a local docker exec command.
      parameters:
        - $ref: "#/components/parameters/RunId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SshAccessRequest"
      responses:
        "201":
          description: Sandbox access command created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SshAccessResponse"
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Run has no active sandbox or provider does not support access commands
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/sandboxes:
    get:
      operationId: listSandboxes
      tags: [Sandboxes]
      summary: List Sandboxes
      description: Lists Fabro-managed sandboxes directly from configured sandbox providers.
      responses:
        "200":
          description: Provider-backed sandbox inventory
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SandboxListResponse"

  /api/v1/sandboxes/{id}:
    get:
      operationId: retrieveSandbox
      tags: [Sandboxes]
      summary: Retrieve Sandbox
      description: Retrieves a Fabro-managed sandbox by provider-native id by searching all configured sandbox providers.
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Sandbox found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SandboxInfo"
        "404":
          description: No provider found a Fabro-managed sandbox with this id
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: More than one provider matched this sandbox id
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: Provider lookup failed before a definitive result could be determined
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/sandbox:
    get:
      operationId: retrieveRunSandbox
      tags: [Human-in-the-Loop]
      summary: Retrieve Run Sandbox Details
      description: Returns provider-neutral details about the sandbox owned by this run, including identity, normalized state, image/snapshot, resources, labels, and timestamps.
      parameters:
        - $ref: "#/components/parameters/RunId"
      responses:
        "200":
          description: Sandbox details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SandboxDetails"
        "404":
          description: Run not found or run has no sandbox
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Sandbox provider exists but inspection failed because the sandbox is gone or inaccessible
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "501":
          description: Sandbox provider has no details implementation
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/sandbox/services:
    get:
      operationId: listSandboxServices
      tags: [Human-in-the-Loop]
      summary: List Sandbox Services
      description: Lists listening TCP services discovered inside the run sandbox.
      parameters:
        - $ref: "#/components/parameters/RunId"
      responses:
        "200":
          description: Listening TCP services
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SandboxServiceListResponse"
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Run has no active sandbox or service discovery failed
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/sandbox/vnc:
    post:
      operationId: createSandboxVncPreview
      tags: [Human-in-the-Loop]
      summary: Create Sandbox VNC Preview
      description: Starts or ensures Daytona Computer Use for the run sandbox and returns a signed noVNC preview URL.
      parameters:
        - $ref: "#/components/parameters/RunId"
      responses:
        "201":
          description: Signed noVNC preview URL created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VncPreviewResponse"
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Run has no active sandbox, Computer Use startup failed, or signed preview generation failed
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "501":
          description: Sandbox provider does not support VNC previews
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/sandbox/files:
    get:
      operationId: listSandboxFiles
      tags: [Human-in-the-Loop]
      summary: List Sandbox Files
      description: Lists directory entries from the run's sandbox environment.
      parameters:
        - $ref: "#/components/parameters/RunId"
        - in: query
          name: path
          required: true
          schema:
            type: string
        - in: query
          name: depth
          required: false
          schema:
            type: integer
            minimum: 1
      responses:
        "200":
          description: Directory entries
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SandboxFileListResponse"
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Run has no active sandbox
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/runs/{id}/sandbox/file:
    get:
      operationId: getSandboxFile
      tags: [Human-in-the-Loop]
      summary: Download Sandbox File
      description: Downloads a file from the run's sandbox environment.
      parameters:
        - $ref: "#/components/parameters/RunId"
        - in: query
          name: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: File contents
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        "404":
          description: Run or file not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Run has no active sandbox
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    put:
      operationId: putSandboxFile
      tags: [Human-in-the-Loop]
      summary: Upload Sandbox File
      description: Uploads a file into the run's sandbox environment.
      parameters:
        - $ref: "#/components/parameters/RunId"
        - in: query
          name: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/octet-stream:
            schema:
              type: string
              format: binary
      responses:
        "204":
          description: File written
        "404":
          description: Run not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Run has no active sandbox
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  # ── Automations ──────────────────────────────────────────────────────

  /api/v1/automations:
    get:
      operationId: listAutomations
      tags: [Automations]
      summary: List automations
      description: Returns all configured automation definitions.
      responses:
        "200":
          description: Automation definitions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AutomationListResponse"
    post:
      operationId: createAutomation
      tags: [Automations]
      summary: Create automation
      description: Creates a new automation definition.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateAutomationRequest"
      responses:
        "201":
          description: Automation created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Automation"
        "400":
          description: Malformed JSON request body
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Automation id already exists
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "422":
          description: Automation failed domain validation
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/automations/{id}:
    get:
      operationId: retrieveAutomation
      tags: [Automations]
      summary: Retrieve automation
      description: Returns one automation definition by id.
      parameters:
        - $ref: "#/components/parameters/AutomationId"
      responses:
        "200":
          description: Automation definition
          headers:
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Automation"
        "404":
          description: Automation not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    put:
      operationId: replaceAutomation
      tags: [Automations]
      summary: Replace automation
      description: Replaces an automation definition when `If-Match` matches the current automation revision.
      parameters:
        - $ref: "#/components/parameters/AutomationId"
        - $ref: "#/components/parameters/IfMatch"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ReplaceAutomationRequest"
      responses:
        "200":
          description: Automation replaced
          headers:
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Automation"
        "400":
          description: Malformed JSON request body or invalid revision header
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Automation not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Automation revision mismatch
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "422":
          description: Automation failed domain validation
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "428":
          description: Missing required `If-Match` header
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    delete:
      operationId: deleteAutomation
      tags: [Automations]
      summary: Delete automation
      description: Deletes an automation definition when `If-Match` matches the current automation revision.
      parameters:
        - $ref: "#/components/parameters/AutomationId"
        - $ref: "#/components/parameters/IfMatch"
      responses:
        "204":
          description: Automation deleted
        "400":
          description: Invalid revision header
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Automation not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Automation revision mismatch
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "428":
          description: Missing required `If-Match` header
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/automations/{id}/runs:
    get:
      operationId: listAutomationRuns
      tags: [Automations]
      summary: List automation runs
      description: Returns durable runs created by one automation.
      parameters:
        - $ref: "#/components/parameters/AutomationId"
        - $ref: "#/components/parameters/PageLimit"
        - $ref: "#/components/parameters/PageOffset"
      responses:
        "200":
          description: Paginated durable runs for the automation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaginatedRunList"
        "400":
          description: Invalid pagination parameter
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Automation not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    post:
      operationId: createAutomationRun
      tags: [Automations]
      summary: Create automation run
      description: Creates a new run by firing the automation's enabled API trigger.
      parameters:
        - $ref: "#/components/parameters/AutomationId"
      responses:
        "201":
          description: Run created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Run"
        "404":
          description: Automation not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Automation has no enabled API trigger
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "422":
          description: Automation target could not be materialized into a run
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  # ── MCP Servers ──────────────────────────────────────────────────────

  /api/v1/mcp-servers:
    get:
      operationId: listMcpServers
      tags: [MCP Servers]
      summary: List MCP servers
      description: Returns all server-managed MCP server definitions with transport env/header values omitted.
      responses:
        "200":
          description: MCP server definitions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/McpServerListResponse"
        "500":
          description: MCP server store operation failed
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    post:
      operationId: createMcpServer
      tags: [MCP Servers]
      summary: Create MCP server
      description: Creates a new MCP server definition. The id is the runtime MCP server name used in qualified tool names.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateMcpServerRequest"
      responses:
        "201":
          description: MCP server created
          headers:
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/McpServer"
        "400":
          description: Malformed JSON request body
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: MCP server id already exists
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "422":
          description: MCP server failed domain validation
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: MCP server store operation failed
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/mcp-servers/{id}:
    get:
      operationId: retrieveMcpServer
      tags: [MCP Servers]
      summary: Retrieve MCP server
      description: Returns one MCP server definition by id with transport env/header values omitted.
      parameters:
        - $ref: "#/components/parameters/McpServerId"
      responses:
        "200":
          description: MCP server definition
          headers:
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/McpServer"
        "400":
          description: Invalid MCP server id
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: MCP server not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: MCP server store operation failed
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    put:
      operationId: replaceMcpServer
      tags: [MCP Servers]
      summary: Replace MCP server
      description: Replaces an MCP server definition when `If-Match` matches the current MCP server revision.
      parameters:
        - $ref: "#/components/parameters/McpServerId"
        - $ref: "#/components/parameters/IfMatch"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ReplaceMcpServerRequest"
      responses:
        "200":
          description: MCP server replaced
          headers:
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/McpServer"
        "400":
          description: Malformed JSON request body, invalid MCP server id, or invalid revision header
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: MCP server not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: MCP server revision mismatch
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "422":
          description: MCP server failed domain validation
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "428":
          description: Missing required `If-Match` header
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: MCP server store operation failed
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    delete:
      operationId: deleteMcpServer
      tags: [MCP Servers]
      summary: Delete MCP server
      description: Deletes an MCP server definition when `If-Match` matches the current MCP server revision.
      parameters:
        - $ref: "#/components/parameters/McpServerId"
        - $ref: "#/components/parameters/IfMatch"
      responses:
        "204":
          description: MCP server deleted
        "400":
          description: Invalid MCP server id or revision header
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: MCP server not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: MCP server revision mismatch
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "428":
          description: Missing required `If-Match` header
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: MCP server store operation failed
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  # ── Environments ─────────────────────────────────────────────────────

  /api/v1/environments:
    get:
      operationId: listEnvironments
      tags: [Environments]
      summary: List environments
      description: Returns all server-managed environment definitions, sorted by id.
      responses:
        "200":
          description: Environment definitions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EnvironmentListResponse"
        "500":
          description: Environment store operation failed
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    post:
      operationId: createEnvironment
      tags: [Environments]
      summary: Create environment
      description: |
        Creates a server-owned environment definition in the environment catalog.
        REST environment requests only accept inline Dockerfile content; local
        Dockerfile paths are supported by workflow/settings files but rejected
        by this API.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateEnvironmentRequest"
      responses:
        "201":
          description: Environment created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Environment"
        "400":
          description: Malformed JSON request body
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Environment id already exists
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "422":
          description: Environment failed domain validation
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Environment store operation failed
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/environments/{id}:
    get:
      operationId: retrieveEnvironment
      tags: [Environments]
      summary: Retrieve environment
      description: Returns one server-managed environment definition by id.
      parameters:
        - $ref: "#/components/parameters/EnvironmentId"
      responses:
        "200":
          description: Environment definition
          headers:
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Environment"
        "400":
          description: Invalid environment id
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Environment not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Environment store operation failed
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    put:
      operationId: replaceEnvironment
      tags: [Environments]
      summary: Replace environment
      description: |
        Replaces an environment definition when `If-Match` matches the current
        environment revision. The path id is authoritative; the request body
        omits `id`.
      parameters:
        - $ref: "#/components/parameters/EnvironmentId"
        - $ref: "#/components/parameters/IfMatch"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ReplaceEnvironmentRequest"
      responses:
        "200":
          description: Environment replaced
          headers:
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Environment"
        "400":
          description: Malformed JSON request body, invalid environment id, or invalid revision header
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Environment not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Environment revision mismatch or protected environment conflict
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "422":
          description: Environment failed domain validation
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "428":
          description: Missing required `If-Match` header
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Environment store operation failed
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    delete:
      operationId: deleteEnvironment
      tags: [Environments]
      summary: Delete environment
      description: Deletes a non-default environment definition when `If-Match` matches the current environment revision.
      parameters:
        - $ref: "#/components/parameters/EnvironmentId"
        - $ref: "#/components/parameters/IfMatch"
      responses:
        "204":
          description: Environment deleted
        "400":
          description: Invalid environment id or revision header
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Environment not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Environment revision mismatch or protected environment conflict
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "428":
          description: Missing required `If-Match` header
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Environment store operation failed
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  # ── Workflows ────────────────────────────────────────────────────────

  /api/v1/workflows:
    get:
      operationId: listWorkflows
      tags: [Workflows]
      summary: List workflows
      description: Returns workflow definitions available to the browser workflow pages. Real-mode servers may return 501 until workflow cataloging is implemented.
      parameters:
        - $ref: "#/components/parameters/PageLimit"
        - $ref: "#/components/parameters/PageOffset"
      responses:
        "200":
          description: Paginated workflow summaries
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaginatedWorkflowListResponse"
        "501":
          description: Workflow cataloging is not implemented in real mode
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/workflows/{name}:
    get:
      operationId: retrieveWorkflow
      tags: [Workflows]
      summary: Retrieve workflow
      description: Returns a single workflow definition and its dense settings snapshot.
      parameters:
        - name: name
          in: path
          required: true
          description: Workflow slug or name.
          schema:
            type: string
      responses:
        "200":
          description: Workflow details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WorkflowDetailResponse"
        "404":
          description: Workflow not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "501":
          description: Workflow cataloging is not implemented in real mode
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/workflows/{name}/runs:
    get:
      operationId: listWorkflowRuns
      tags: [Workflows]
      summary: List workflow runs
      description: Returns durable runs associated with one workflow.
      parameters:
        - name: name
          in: path
          required: true
          description: Workflow slug or name.
          schema:
            type: string
        - $ref: "#/components/parameters/PageLimit"
        - $ref: "#/components/parameters/PageOffset"
      responses:
        "200":
          description: Paginated durable runs for the workflow
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaginatedRunList"
        "404":
          description: Workflow not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "501":
          description: Workflow cataloging is not implemented in real mode
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  # ── Insights ──────────────────────────────────────────────────────────

  /api/v1/insights/queries:
    get:
      operationId: listSavedQueries
      tags: [Insights]
      summary: List Saved Queries
      description: Returns a paginated list of saved SQL queries for the insights editor.
      parameters:
        - $ref: "#/components/parameters/PageLimit"
        - $ref: "#/components/parameters/PageOffset"
      responses:
        "200":
          description: Paginated list of saved queries
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaginatedSavedQueryList"
    post:
      operationId: createSavedQuery
      tags: [Insights]
      summary: Create Saved Query
      description: Saves a new named SQL query for later reuse.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SaveQueryRequest"
      responses:
        "201":
          description: Query saved
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SavedQuery"

  /api/v1/insights/queries/{id}:
    get:
      operationId: retrieveSavedQuery
      tags: [Insights]
      summary: Retrieve Saved Query
      description: Returns a single saved query by ID.
      parameters:
        - $ref: "#/components/parameters/InsightQueryId"
      responses:
        "200":
          description: Saved query
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SavedQuery"
        "404":
          description: Query not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    put:
      operationId: updateSavedQuery
      tags: [Insights]
      summary: Update Saved Query
      description: Replaces the name and SQL of an existing saved query.
      parameters:
        - $ref: "#/components/parameters/InsightQueryId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SaveQueryRequest"
      responses:
        "200":
          description: Query updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SavedQuery"
        "404":
          description: Query not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    delete:
      operationId: deleteSavedQuery
      tags: [Insights]
      summary: Delete Saved Query
      description: Permanently removes a saved query.
      parameters:
        - $ref: "#/components/parameters/InsightQueryId"
      responses:
        "204":
          description: Query deleted
        "404":
          description: Query not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/insights/execute:
    post:
      operationId: executeQuery
      tags: [Insights]
      summary: Execute Query
      description: Executes an ad-hoc SQL query against the analytics database and returns columnar results.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ExecuteQueryRequest"
      responses:
        "200":
          description: Query results
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ExecuteQueryResponse"
        "400":
          description: Bad SQL or query error
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/insights/history:
    get:
      operationId: listQueryHistory
      tags: [Insights]
      summary: List Query History
      description: Returns a paginated history of recently executed queries with timing and row counts.
      parameters:
        - $ref: "#/components/parameters/PageLimit"
        - $ref: "#/components/parameters/PageOffset"
      responses:
        "200":
          description: Paginated list of history entries
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaginatedHistoryEntryList"

  # ── Billing ──────────────────────────────────────────────────────────

  /api/v1/billing:
    get:
      operationId: getAggregateBilling
      tags: [Billing]
      summary: Aggregate Billing
      description: Returns aggregate token counts and billed totals across all completed runs since server start.
      responses:
        "200":
          description: Aggregate billing data
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AggregateBilling"

  # ── System ───────────────────────────────────────────────────────────

  /api/v1/attach:
    get:
      operationId: attachEvents
      tags: [System]
      summary: Attach Global Events
      description: Opens a server-sent event stream for live run events across the server.
      parameters:
        - name: run_id
          in: query
          required: false
          description: Optional comma-separated list of run IDs to include.
          schema:
            type: string
      responses:
        "200":
          description: Server-sent event stream
          content:
            text/event-stream:
              schema:
                type: string

  /api/v1/system/info:
    get:
      operationId: getSystemInfo
      tags: [System]
      summary: Retrieve System Info
      description: Returns runtime details about the active Fabro server process.
      responses:
        "200":
          description: System information
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SystemInfoResponse"

  /api/v1/system/resources:
    get:
      operationId: getSystemResources
      tags: [System]
      summary: Retrieve System Resources
      description: Returns server-visible CPU, memory, and storage filesystem resource usage.
      responses:
        "200":
          description: System resource usage
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SystemResourcesResponse"

  /api/v1/system/integrations:
    get:
      operationId: getSystemIntegrations
      tags: [System]
      summary: Retrieve System Integrations
      description: Returns runtime integration status computed from server configuration, vault credentials, and live connection state.
      responses:
        "200":
          description: Runtime integration status
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SystemIntegrationsResponse"

  /api/v1/system/df:
    get:
      operationId: getSystemDiskUsage
      tags: [System]
      summary: Retrieve System Disk Usage
      description: Returns disk usage for the server storage directory.
      parameters:
        - name: verbose
          in: query
          required: false
          description: Include per-run disk usage rows.
          schema:
            type: boolean
            default: false
      responses:
        "200":
          description: Disk usage summary
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DiskUsageResponse"

  /api/v1/system/repair/runs:
    get:
      operationId: getSystemRepairRuns
      tags: [System]
      summary: List Run Repair Issues
      description: Lists cataloged runs that cannot be loaded from durable storage.
      responses:
        "200":
          description: Run repair issues
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SystemRepairRunsResponse"

  /api/v1/system/prune/runs:
    post:
      operationId: pruneRuns
      tags: [System]
      summary: Prune Runs
      description: Deletes completed runs matching the provided filters, or previews the deletion set when dry-run is enabled.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PruneRunsRequest"
      responses:
        "200":
          description: Prune result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PruneRunsResponse"
        "400":
          description: Invalid prune request
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  # ── Secrets ──────────────────────────────────────────────────────────

  /api/v1/secrets:
    get:
      operationId: listSecrets
      tags: [Secrets]
      summary: List vault secrets
      description: Returns workflow-visible vault secret names and timestamps. Secret values are never exposed.
      responses:
        "200":
          description: Secret metadata list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SecretListResponse"
    post:
      operationId: createSecret
      tags: [Secrets]
      summary: Store or update a vault secret
      description: Stores a secret in the workflow-visible vault. Anything stored here may be used by workflows.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateSecretRequest"
      responses:
        "200":
          description: Secret stored
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SecretMetadata"
        "400":
          description: Invalid secret name or request body
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    delete:
      operationId: deleteSecretByName
      tags: [Secrets]
      summary: Delete a vault secret
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DeleteSecretRequest"
      responses:
        "204":
          description: Secret deleted
        "400":
          description: Invalid secret name or request body
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Secret not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Secret store write failed
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  # ── Variables ────────────────────────────────────────────────────────

  /api/v1/variables:
    get:
      operationId: listVariables
      tags: [Variables]
      summary: List variables
      description: Returns non-sensitive variables, including values.
      responses:
        "200":
          description: Variable list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VariableListResponse"
    post:
      operationId: createVariable
      tags: [Variables]
      summary: Store or update a variable
      description: Stores a non-sensitive variable for run config interpolation.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateVariableRequest"
      responses:
        "200":
          description: Variable stored
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Variable"
        "400":
          description: Invalid variable name or request body
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Variable store write failed
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/variables/{name}:
    parameters:
      - name: name
        in: path
        required: true
        schema:
          type: string
          pattern: "^[A-Za-z_][A-Za-z0-9_]*$"
        description: Variable name.
    get:
      operationId: getVariable
      tags: [Variables]
      summary: Get a variable
      responses:
        "200":
          description: Variable
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Variable"
        "400":
          description: Invalid variable name
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Variable not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    put:
      operationId: updateVariable
      tags: [Variables]
      summary: Replace a variable value
      description: Replaces a variable value and preserves the existing description when omitted.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateVariableRequest"
      responses:
        "200":
          description: Variable updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Variable"
        "400":
          description: Invalid variable name or request body
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Variable not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Variable store write failed
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    delete:
      operationId: deleteVariable
      tags: [Variables]
      summary: Delete a variable
      responses:
        "204":
          description: Variable deleted
        "400":
          description: Invalid variable name
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Variable not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Variable store write failed
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  # ── Repos ────────────────────────────────────────────────────────────

  /api/v1/repos/github/{owner}/{name}:
    get:
      operationId: getGithubRepo
      tags: [Repos]
      summary: Check server access to a GitHub repository
      parameters:
        - name: owner
          in: path
          required: true
          schema:
            type: string
        - name: name
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Repository access details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RepoCheckResponse"

  # ── Models ───────────────────────────────────────────────────────────

  /api/v1/models:
    get:
      operationId: listModels
      tags: [Models]
      summary: List Models
      description: |
        Returns one row per provider/model offering from the catalog. Model IDs
        are unique within a provider; `(provider, id)` is the resource identity.
      parameters:
        - $ref: "#/components/parameters/ModelProviderFilter"
        - $ref: "#/components/parameters/ModelQueryFilter"
        - $ref: "#/components/parameters/PageLimit"
        - $ref: "#/components/parameters/PageOffset"
      responses:
        "200":
          description: Paginated list of models
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaginatedModelList"
        "400":
          description: Invalid filter value
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/models/{id}/test:
    post:
      operationId: testModel
      tags: [Models]
      summary: Test Model
      description: Tests a model by sending a simple prompt and reporting pass/fail.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: The canonical model ID or an alias.
        - $ref: "#/components/parameters/ModelTestProviderParam"
        - $ref: "#/components/parameters/ModelTestModeParam"
      responses:
        "200":
          description: Test result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ModelTestResult"
        "400":
          description: Invalid test mode
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Model not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/providers:
    get:
      operationId: listProviders
      tags: [Models]
      summary: List Providers
      description: Returns LLM providers from the catalog with effective config and configured status.
      responses:
        "200":
          description: Provider list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProviderList"

  /api/v1/providers/{provider}/credentials/test:
    post:
      operationId: testProviderCredentials
      tags: [Models]
      summary: Test Provider Credentials
      description: Validates an LLM provider API key against the server's effective catalog without persisting it.
      parameters:
        - name: provider
          in: path
          required: true
          schema:
            type: string
          description: The provider identifier.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProviderCredentialTestRequest"
      responses:
        "200":
          description: Credentials validated successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProviderCredentialTestResponse"
        "400":
          description: Invalid provider credential request
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Provider not found
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "422":
          description: Credential validation failed
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/providers/test:
    post:
      operationId: testProviders
      tags: [Models]
      summary: Test Providers
      description: >
        Tests every configured LLM provider once using the catalog probe model.
        Provider-level failures are returned in the response body with HTTP 200.
      responses:
        "200":
          description: Provider test results
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProviderTestList"

  # ── Completions ───────────────────────────────────────────────────────

  /api/v1/completions:
    post:
      operationId: createCompletion
      tags: [Completions]
      summary: Create Completion
      description: |
        Generate a text completion. Set `stream: true` for SSE streaming.

        All SSE frames use `event: stream_event` with a JSON-serialized StreamEvent
        payload. StreamEvent types: stream_start, text_start, text_delta, text_end,
        tool_call_start, tool_call_delta, tool_call_end, finish, error.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateCompletionRequest"
      responses:
        "200":
          description: Completion result (JSON when stream=false, SSE when stream=true)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CompletionResponse"
        "400":
          description: Invalid request
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  # ── Playground ────────────────────────────────────────────────────────

  /api/v1/playground/chat:
    post:
      operationId: createPlaygroundChat
      tags: [Playground]
      summary: Chat with the playground assistant
      description: |
        Drives a single turn of the playground chat that builds a workflow
        graph incrementally. The server is stateless: each request includes
        the full current draft, and the response streams text deltas plus a
        single `write_workflow_file` tool call carrying the full new contents
        of `workflow.fabro` for the client to parse, diff against its local
        draft, and animate into the canvas.

        Responses are always SSE. Frames use `event: stream_event` with a
        JSON-serialized StreamEvent payload — see /api/v1/completions for the
        StreamEvent shape. The tool call arrives on a `tool_call_end` event
        with the tool name and parsed JSON arguments.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreatePlaygroundChatRequest"
      responses:
        "200":
          description: SSE stream of text deltas and tool calls.
        "400":
          description: Invalid request
          headers:
            x-request-id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  # ── Settings ──────────────────────────────────────────────────────────

  /api/v1/settings:
    get:
      operationId: retrieveServerSettings
      tags: [Settings]
      summary: Retrieve Server Settings
      description: >
        Returns the server's current in-memory settings view as the typed
        `ServerSettings` payload.
      responses:
        "200":
          description: Server settings
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ServerSettings"

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: opaque
      description: >
        Raw dev token passed as `Authorization: Bearer fabro_dev_...` when
        `server.auth.methods` includes `dev-token`.
    SessionCookie:
      type: apiKey
      in: cookie
      name: __fabro_session
      description: >
        Private session cookie issued after a successful web login. The server
        verifies and decodes the cookie before authenticating the request.

  parameters:
    RunId:
      name: id
      in: path
      required: true
      description: Unique run identifier (ULID).
      schema:
        type: string
      example: 01JNQVR7M0EJ5GKAT2SC4ERS1Z

    AutomationId:
      name: id
      in: path
      required: true
      description: Unique automation identifier.
      schema:
        type: string
        pattern: "^[a-z0-9][a-z0-9-]{0,62}$"
      example: nightly-deps

    EnvironmentId:
      name: id
      in: path
      required: true
      description: Unique environment identifier.
      schema:
        type: string
        pattern: "^[a-z0-9][a-z0-9-]{0,62}$"
      example: docker

    McpServerId:
      name: id
      in: path
      required: true
      description: Stable MCP server identifier, used as the runtime MCP server name in qualified tool names.
      schema:
        type: string
        pattern: "^[a-z0-9][a-z0-9-]{0,62}$"
      example: sentry

    IfMatch:
      name: If-Match
      in: header
      required: true
      description: Current resource revision used for optimistic concurrency, as returned in the `ETag` response header.
      schema:
        type: string
      example: '"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"'

    RunSelector:
      name: selector
      in: query
      required: true
      description: Run selector, such as a run ID prefix, workflow slug, or workflow name.
      schema:
        type: string
      example: nightly-build

    StageId:
      name: stageId
      in: path
      required: true
      description: Identifier of a stage within a run's workflow graph, serialized as `node_id@visit`.
      schema:
        type: string
      example: code@2

    CommandLogOffset:
      name: offset
      in: query
      required: false
      description: Byte offset to start reading from. Defaults to `0`.
      schema:
        type: integer
        minimum: 0
        default: 0
      example: 65536

    CommandLogLimit:
      name: limit
      in: query
      required: false
      description: Maximum bytes to return. Defaults to 65536 and is capped at 1048576.
      schema:
        type: integer
        minimum: 1
        maximum: 1048576
        default: 65536
      example: 65536

    BlobHash:
      name: blobHash
      in: path
      required: true
      description: Content-addressed blob hash.
      schema:
        $ref: "#/components/schemas/BlobHash"
      example: 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824

    ArtifactFilename:
      name: filename
      in: query
      required: true
      description: Relative artifact path. `/` is allowed as a path separator. Backslash, empty segments, and traversal segments (`.` and `..`) are invalid.
      schema:
        type: string
      example: src/lib.rs

    ArtifactRetry:
      name: retry
      in: query
      required: true
      description: Retry attempt number for the artifact.
      schema:
        type: integer
        format: int32
        minimum: 0
      example: 1

    SinceSeq:
      name: since_seq
      in: query
      required: false
      description: First event sequence number to include.
      schema:
        type: integer
        minimum: 1
        default: 1
      example: 42

    BeforeSeq:
      name: before_seq
      in: query
      required: false
      description: |
        Exclusive upper event sequence cursor for descending order. Omit on
        the first descending request to start from the newest event.
      schema:
        type: integer
        minimum: 1
      example: 42

    EventOrder:
      name: order
      in: query
      required: false
      description: |
        Event sequence order. `since_seq` is valid only with `asc`;
        `before_seq` is valid only with `desc`.
      schema:
        type: string
        enum: [asc, desc]
        default: asc
      example: desc

    EventLimit:
      name: limit
      in: query
      required: false
      description: Maximum number of events to return.
      schema:
        type: integer
        minimum: 1
        maximum: 1000
        default: 100
      example: 100

    QuestionId:
      name: qid
      in: path
      required: true
      description: Unique identifier of a pending question.
      schema:
        type: string
      example: q-001

    InsightQueryId:
      name: id
      in: path
      required: true
      description: Unique identifier of a saved query.
      schema:
        type: string
      example: "1"

    CheckpointFilter:
      name: checkpoint
      in: query
      required: false
      description: Filter to a specific checkpoint ID. Omit to include all changes.
      schema:
        type: string
      example: cp-3

    PageLimit:
      name: page[limit]
      in: query
      required: false
      description: Maximum number of items to return per page.
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 20
      example: 20

    PageOffset:
      name: page[offset]
      in: query
      required: false
      description: Number of items to skip before returning results.
      schema:
        type: integer
        minimum: 0
        default: 0
      example: 0

    IncludeArchived:
      name: include_archived
      in: query
      required: false
      description: Whether to include archived runs in the response. Defaults to `false`.
      schema:
        type: boolean
        default: false
      example: false

    ParentRunId:
      name: parent_id
      in: query
      required: false
      description: Return only runs currently linked to this orchestration parent.
      schema:
        type: string
      example: 01JNQVR7M0EJ5GKAT2SC4ERS1Z

    RunStatusFilter:
      name: status
      in: query
      required: false
      style: form
      explode: true
      description: |
        Filter runs by status bucket. Repeatable. When omitted, runs in the
        `removing` bucket are hidden; pass `status=removing` to include them.
        Archived runs are hidden unless `include_archived=true` or
        `status=archived` is passed.
      schema:
        type: array
        items:
          $ref: "#/components/schemas/BoardColumn"
      example: [running, blocked]

    RunsSort:
      name: sort
      in: query
      required: false
      description: Field to sort by. Defaults to `created_at`.
      schema:
        type: string
        enum: [created_at, updated_at, status, elapsed, repo, title, workflow, changes, size]
        default: created_at
      example: created_at

    RunsSortDirection:
      name: direction
      in: query
      required: false
      description: Sort direction. Defaults to `desc`.
      schema:
        type: string
        enum: [asc, desc]
        default: desc
      example: desc

    ForceRunDelete:
      name: force
      in: query
      required: false
      description: Whether to force deletion of an active run. Defaults to `false`.
      schema:
        type: boolean
        default: false
      example: false

    ModelProviderFilter:
      name: provider
      in: query
      required: false
      description: Filter models by provider ID. Unknown provider IDs return an empty result set.
      schema:
        $ref: "#/components/schemas/ProviderId"
      example: anthropic

    ModelQueryFilter:
      name: query
      in: query
      required: false
      description: Case-insensitive substring search across `id`, `display_name`, and `aliases`.
      schema:
        type: string
      example: opus

    ModelTestModeParam:
      name: mode
      in: query
      required: false
      description: Test mode for the single-model test endpoint. Defaults to `basic`.
      schema:
        $ref: "#/components/schemas/ModelTestMode"
      example: basic

    ModelTestProviderParam:
      name: provider
      in: query
      required: false
      description: |
        Pin the test to this provider's offering. When omitted, the server
        selects among ready providers by catalog priority.
      schema:
        $ref: "#/components/schemas/ProviderId"
      example: openrouter

  headers:
    XRequestId:
      description: >
        Server-generated request identifier emitted on every response and
        referenced on standard error responses for correlating client errors
        with server logs.
      schema:
        type: string
        format: uuid

    ETag:
      description: Current resource revision for optimistic concurrency. Supply this value via `If-Match` on subsequent mutating requests.
      schema:
        type: string
      example: '"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"'

  schemas:
    AuthConfigResponse:
      description: Browser login methods enabled by server auth settings.
      type: object
      required:
        - methods
      properties:
        methods:
          type: array
          items:
            type: string
          example: ["dev-token", "github"]

    AuthMeResponse:
      description: Current authenticated browser user and session state.
      type: object
      required:
        - user
        - provider
        - demoMode
      properties:
        user:
          $ref: "#/components/schemas/AuthSessionUser"
        provider:
          type: string
          example: dev-token
        demoMode:
          type: boolean

    AuthSessionsResponse:
      type: object
      required:
        - sessions
      properties:
        sessions:
          type: array
          items:
            $ref: "#/components/schemas/AuthSession"

    AuthSession:
      type: object
      required:
        - id
        - kind
        - current
        - provider
        - login
        - label
        - createdAt
        - lastSeenAt
        - expiresAt
        - revocable
      properties:
        id:
          type: string
        kind:
          type: string
          enum: [browser, cli]
        current:
          type: boolean
        provider:
          type: string
          example: github
        login:
          type: string
        label:
          type: string
        userAgent:
          type: string
          nullable: true
        createdAt:
          type: string
          format: date-time
        lastSeenAt:
          type: string
          format: date-time
        expiresAt:
          type: string
          format: date-time
        revocable:
          type: boolean

    AuthSessionUser:
      description: Browser session user profile.
      type: object
      required:
        - login
        - name
        - email
        - avatarUrl
        - userUrl
      properties:
        login:
          type: string
        name:
          type: string
        email:
          type: string
        idpIssuer:
          type: string
        idpSubject:
          type: string
        avatarUrl:
          type: string
        userUrl:
          type: string

    DevTokenLoginRequest:
      description: Browser login payload for development-token auth.
      type: object
      required:
        - token
      properties:
        token:
          type: string

    DevTokenLoginResponse:
      description: Browser development-token login result.
      type: object
      required:
        - ok
      properties:
        ok:
          type: boolean

    InstallSessionResponse:
      description: Current browser-install session snapshot with secrets redacted.
      type: object
      required:
        - completed_steps
        - prefill
      properties:
        completed_steps:
          type: array
          items:
            type: string
        llm:
          oneOf:
            - $ref: "#/components/schemas/InstallLlmSummary"
            - type: "null"
        server:
          oneOf:
            - $ref: "#/components/schemas/InstallServerConfigInput"
            - type: "null"
        object_store:
          oneOf:
            - $ref: "#/components/schemas/InstallObjectStoreSummary"
            - type: "null"
        sandbox:
          oneOf:
            - $ref: "#/components/schemas/InstallSandboxSummary"
            - type: "null"
        github:
          oneOf:
            - $ref: "#/components/schemas/InstallGithubSummary"
            - type: "null"
        prefill:
          $ref: "#/components/schemas/InstallPrefill"

    InstallPrefill:
      description: Server-detected defaults used to prefill the browser install wizard.
      type: object
      required:
        - canonical_url
        - object_store_local_root
      properties:
        canonical_url:
          type: string
          format: uri
        object_store_local_root:
          type: string

    InstallLlmValidationResponse:
      description: Successful response from install-time LLM credential validation.
      type: object
      required:
        - ok
      properties:
        ok:
          type: boolean
          example: true

    InstallLlmTestInput:
      description: Input for install-time LLM credential validation. Supported providers in install v1 are `anthropic`, `openai`, and `gemini`.
      type: object
      required:
        - provider
        - api_key
      properties:
        provider:
          type: string
          example: anthropic
        api_key:
          type: string

    InstallLlmProvidersInput:
      description: >-
        LLM providers selected during browser install. An empty `providers`
        list explicitly marks the LLM step as completed and skipped.
      type: object
      required:
        - providers
      properties:
        providers:
          type: array
          description: >-
            LLM providers to persist. An empty list records an explicit skip:
            the LLM step is marked complete with zero credentials.
          items:
            $ref: "#/components/schemas/InstallLlmProviderInput"

    InstallLlmProviderInput:
      description: One persisted LLM provider configuration collected during browser install. Supported providers in install v1 are `anthropic`, `openai`, and `gemini`.
      type: object
      required:
        - provider
        - api_key
      properties:
        provider:
          type: string
          example: anthropic
        api_key:
          type: string

    InstallLlmSummary:
      description: >-
        Redacted summary of persisted LLM install choices. Present with an
        empty `providers` list when the LLM step was explicitly skipped;
        `null` on the install session means the step is still incomplete.
      type: object
      properties:
        providers:
          type: array
          items:
            type: object
            required:
              - provider
              - configured
            properties:
              provider:
                type: string
              configured:
                type: boolean

    InstallServerConfigInput:
      description: Canonical server URL confirmed during browser install.
      type: object
      required:
        - canonical_url
      properties:
        canonical_url:
          type: string
          format: uri

    InstallObjectStoreValidationResponse:
      description: Successful response from install-time object-store validation.
      type: object
      required:
        - ok
      properties:
        ok:
          type: boolean
          example: true

    InstallObjectStoreInput:
      description: Object-store mode selected during browser install.
      type: object
      required:
        - provider
      properties:
        provider:
          type: string
          enum: [local, s3]
        root:
          type: string
        bucket:
          type: string
        region:
          type: string
        credential_mode:
          type: string
          enum: [runtime, access_key]
        access_key_id:
          type: string
        secret_access_key:
          type: string

    InstallObjectStoreSummary:
      description: Redacted summary of the object-store mode selected during browser install.
      type: object
      required:
        - provider
      properties:
        provider:
          type: string
          enum: [local, s3]
        root:
          type: string
        bucket:
          type: string
        region:
          type: string
        credential_mode:
          type: string
          enum: [runtime, access_key]
        manual_credentials_saved:
          type: boolean

    InstallSandboxValidationResponse:
      description: Successful response from install-time sandbox validation.
      type: object
      required:
        - ok
      properties:
        ok:
          type: boolean
          example: true

    InstallSandboxInput:
      description: Sandbox provider selected during browser install. `api_key` is required for Daytona and ignored for Docker. `allow_local` enables the local sandbox provider alongside the selected runtime; it defaults to true when omitted.
      type: object
      required:
        - provider
      properties:
        provider:
          type: string
          enum: [docker, daytona]
        api_key:
          type: string
        allow_local:
          type: boolean

    InstallSandboxSummary:
      description: Redacted summary of the sandbox provider selected during browser install.
      type: object
      required:
        - provider
      properties:
        provider:
          type: string
          enum: [docker, daytona]
        api_key_saved:
          type: boolean
        allow_local:
          type: boolean

    InstallGithubTokenTestInput:
      description: Input for install-time GitHub token validation.
      type: object
      required:
        - token
      properties:
        token:
          type: string

    InstallGithubTokenTestResponse:
      description: Successful response from install-time GitHub token validation.
      type: object
      required:
        - username
      properties:
        username:
          type: string

    InstallGithubTokenInput:
      description: GitHub personal access token chosen during browser install.
      type: object
      required:
        - token
        - username
      properties:
        token:
          type: string
        username:
          type: string

    InstallGithubAppManifestInput:
      description: Input required to build the browser-install GitHub App manifest.
      type: object
      required:
        - owner
        - app_name
        - allowed_username
      properties:
        owner:
          $ref: "#/components/schemas/InstallGithubAppOwner"
        app_name:
          type: string
        allowed_username:
          type: string

    InstallGithubAppOwner:
      description: Owner of the GitHub App being created during browser install.
      type: object
      required:
        - kind
      properties:
        kind:
          type: string
          enum: [personal, org]
        slug:
          type: string
          description: Required when `kind` is `org`; the organization slug.

    InstallGithubAppManifestResponse:
      description: Browser handoff payload for the GitHub App creation flow.
      type: object
      required:
        - manifest
        - github_form_action
        - state
      properties:
        manifest:
          type: object
          additionalProperties: true
        github_form_action:
          type: string
          format: uri
        state:
          description: |
            CSRF token the browser must echo back to GitHub as a hidden
            `state` form field alongside `manifest`. GitHub preserves it on
            the redirect to `redirect_url` so the server can match the
            callback to this pending install.
          type: string

    InstallGithubSummary:
      description: Redacted summary of the GitHub install strategy selected during browser install.
      type: object
      required:
        - strategy
      properties:
        strategy:
          type: string
          enum: [token, app]
        username:
          type: string
        owner:
          $ref: "#/components/schemas/InstallGithubAppOwner"
        app_name:
          type: string
        slug:
          type: string
        allowed_username:
          type: string

    InstallFinishResponse:
      description: Response returned after install outputs are persisted successfully.
      type: object
      required:
        - status
        - restart_url
      properties:
        status:
          type: string
          enum: [completing]
        restart_url:
          type: string
          format: uri
        dev_token:
          type: string
          description: |
            Dev token used to bootstrap login. Only included when the operator
            chose the personal access token flow; GitHub App installs rely on
            OAuth and do not receive a dev token.

    # ── Automations ──────────────────────────────────────────────────────

    Automation:
      description: Public automation definition.
      type: object
      additionalProperties: false
      required:
        - id
        - revision
        - name
        - description
        - target
        - triggers
      properties:
        id:
          type: string
          pattern: "^[a-z0-9][a-z0-9-]{0,62}$"
          example: nightly-deps
        revision:
          type: string
          pattern: "^[0-9a-f]{64}$"
          description: Stable revision used with `If-Match` for optimistic concurrency.
          example: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
        name:
          type: string
          example: Nightly dependency update
        description:
          type: ["string", "null"]
          example: Keeps dependencies fresh.
        target:
          $ref: "#/components/schemas/AutomationTarget"
        triggers:
          type: array
          items:
            $ref: "#/components/schemas/AutomationTrigger"

    AutomationTarget:
      description: Repository and workflow selected by an automation.
      type: object
      additionalProperties: false
      required:
        - repository
        - ref
        - workflow
      properties:
        repository:
          type: string
          description: GitHub repository slug in `owner/repo` form.
          example: fabro-sh/fabro
        ref:
          type: string
          description: Branch, tag, or SHA selector resolved when materializing a run.
          example: main
        workflow:
          type: string
          description: Workflow slug or path resolved in the target repository.
          example: dependency-update

    AutomationTrigger:
      description: |
        Automation trigger configuration. Unknown `type` discriminator values
        are reported by handlers as domain validation errors with HTTP 422.
      oneOf:
        - $ref: "#/components/schemas/AutomationApiTrigger"
        - $ref: "#/components/schemas/AutomationScheduleTrigger"
      discriminator:
        propertyName: type
        mapping:
          api: "#/components/schemas/AutomationApiTrigger"
          schedule: "#/components/schemas/AutomationScheduleTrigger"

    AutomationApiTrigger:
      description: Trigger that allows callers to create runs through the automation API.
      type: object
      additionalProperties: false
      required:
        - id
        - type
        - enabled
      properties:
        id:
          type: string
          pattern: "^[a-z0-9][a-z0-9_-]{0,62}$"
          example: manual
        type:
          type: string
          enum: [api]
        enabled:
          type: boolean
          example: true

    AutomationScheduleTrigger:
      description: Cron schedule trigger evaluated in UTC.
      type: object
      additionalProperties: false
      required:
        - id
        - type
        - enabled
        - expression
      properties:
        id:
          type: string
          pattern: "^[a-z0-9][a-z0-9_-]{0,62}$"
          example: nightly
        type:
          type: string
          enum: [schedule]
        enabled:
          type: boolean
          example: true
        expression:
          type: string
          description: Five-field cron expression evaluated in UTC.
          example: "0 3 * * *"

    CreateAutomationRequest:
      description: Request body for creating an automation.
      type: object
      additionalProperties: false
      required:
        - id
        - name
        - target
        - triggers
      properties:
        id:
          type: string
          pattern: "^[a-z0-9][a-z0-9-]{0,62}$"
          example: nightly-deps
        name:
          type: string
          example: Nightly dependency update
        description:
          type: ["string", "null"]
          example: Keeps dependencies fresh.
        target:
          $ref: "#/components/schemas/AutomationTarget"
        triggers:
          type: array
          items:
            $ref: "#/components/schemas/AutomationTrigger"

    ReplaceAutomationRequest:
      description: Request body for replacing an automation.
      type: object
      additionalProperties: false
      required:
        - name
        - target
        - triggers
      properties:
        name:
          type: string
          example: Nightly dependency update
        description:
          type: ["string", "null"]
          example: Keeps dependencies fresh.
        target:
          $ref: "#/components/schemas/AutomationTarget"
        triggers:
          type: array
          items:
            $ref: "#/components/schemas/AutomationTrigger"

    AutomationListResponse:
      description: List envelope for automation definitions.
      type: object
      additionalProperties: false
      required:
        - data
        - meta
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Automation"
        meta:
          $ref: "#/components/schemas/AutomationListMeta"

    AutomationListMeta:
      description: Metadata for automation list responses.
      type: object
      additionalProperties: false
      required:
        - total
      properties:
        total:
          type: integer
          format: int64
          minimum: 0
          description: Total number of configured automation definitions.

    # ── MCP Servers ──────────────────────────────────────────────────────

    McpServer:
      description: Public server-managed MCP server definition. Transport env/header values are never returned.
      type: object
      additionalProperties: false
      required:
        - id
        - revision
        - display_name
        - description
        - transport
        - startup_timeout_secs
        - tool_timeout_secs
      properties:
        id:
          type: string
          description: Stable MCP server identifier, used as the runtime MCP server name in qualified tool names.
          pattern: "^[a-z0-9][a-z0-9-]{0,62}$"
          example: sentry
        revision:
          type: string
          pattern: "^[0-9a-f]{64}$"
          description: Stable revision used with `If-Match` for optimistic concurrency.
          example: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
        display_name:
          type: string
          description: Human-readable label for display in management UIs.
          example: Sentry
        description:
          type: ["string", "null"]
          example: Production Sentry MCP server.
        transport:
          $ref: "#/components/schemas/McpTransportView"
        startup_timeout_secs:
          type: integer
          format: int64
          minimum: 0
          description: Seconds to wait for the MCP server to become ready at connect time.
          example: 10
        tool_timeout_secs:
          type: integer
          format: int64
          minimum: 0
          description: Seconds to allow each MCP tool call before timing out.
          example: 60

    CreateMcpServerRequest:
      description: Request body for creating an MCP server definition.
      type: object
      additionalProperties: false
      required:
        - id
        - display_name
        - transport
        - startup_timeout_secs
        - tool_timeout_secs
      properties:
        id:
          type: string
          description: Stable MCP server identifier, used as the runtime MCP server name in qualified tool names.
          pattern: "^[a-z0-9][a-z0-9-]{0,62}$"
          example: sentry
        display_name:
          type: string
          description: Human-readable label for display in management UIs.
          example: Sentry
        description:
          type: ["string", "null"]
          example: Production Sentry MCP server.
        transport:
          $ref: "#/components/schemas/McpTransport"
        startup_timeout_secs:
          type: integer
          format: int64
          minimum: 0
          example: 10
        tool_timeout_secs:
          type: integer
          format: int64
          minimum: 0
          example: 60

    ReplaceMcpServerRequest:
      description: Request body for replacing an MCP server definition. The path id is authoritative.
      type: object
      additionalProperties: false
      required:
        - display_name
        - transport
        - startup_timeout_secs
        - tool_timeout_secs
      properties:
        display_name:
          type: string
          description: Human-readable label for display in management UIs.
          example: Sentry
        description:
          type: ["string", "null"]
          example: Production Sentry MCP server.
        transport:
          $ref: "#/components/schemas/McpTransport"
        startup_timeout_secs:
          type: integer
          format: int64
          minimum: 0
          example: 10
        tool_timeout_secs:
          type: integer
          format: int64
          minimum: 0
          example: 60

    # Write requests use the same transport schema as run config. Read responses
    # use a value-omitting view so secret-bearing env/header values are never returned.

    McpTransportView:
      description: MCP server transport configuration returned by catalog read APIs.
      oneOf:
        - $ref: "#/components/schemas/McpTransportViewStdio"
        - $ref: "#/components/schemas/McpTransportViewHttp"
        - $ref: "#/components/schemas/McpTransportViewSandbox"
      discriminator:
        propertyName: type
        mapping:
          stdio: "#/components/schemas/McpTransportViewStdio"
          http: "#/components/schemas/McpTransportViewHttp"
          sandbox: "#/components/schemas/McpTransportViewSandbox"

    McpTransportViewStdio:
      description: Stdio transport view. Environment variable values are omitted.
      type: object
      additionalProperties: false
      required: [type, command, env_keys]
      properties:
        type:
          type: string
          enum: [stdio]
        command:
          type: array
          minItems: 1
          description: Command and arguments used to launch the MCP server.
          items:
            type: string
        env_keys:
          type: array
          description: Environment variable names configured for this transport.
          items:
            type: string

    McpTransportViewHttp:
      description: HTTP transport view. Header values are omitted.
      type: object
      additionalProperties: false
      required: [type, url, header_keys]
      properties:
        type:
          type: string
          enum: [http]
        protocol:
          $ref: "#/components/schemas/McpHttpProtocol"
        url:
          type: string
          format: uri
        header_keys:
          type: array
          description: HTTP header names configured for this transport.
          items:
            type: string

    McpTransportViewSandbox:
      description: Sandbox transport view. Environment variable values are omitted.
      type: object
      additionalProperties: false
      required: [type, command, port, env_keys]
      properties:
        type:
          type: string
          enum: [sandbox]
        protocol:
          $ref: "#/components/schemas/McpHttpProtocol"
        command:
          type: array
          minItems: 1
          description: Command and arguments used to launch the in-sandbox MCP server.
          items:
            type: string
        port:
          type: integer
          format: int32
          minimum: 1
          maximum: 65535
        env_keys:
          type: array
          description: Environment variable names configured for this transport.
          items:
            type: string

    McpServerListResponse:
      description: List envelope for MCP server definitions.
      type: object
      additionalProperties: false
      required:
        - data
        - meta
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/McpServer"
        meta:
          $ref: "#/components/schemas/McpServerListMeta"

    McpServerListMeta:
      description: Metadata for MCP server list responses.
      type: object
      additionalProperties: false
      required:
        - total
      properties:
        total:
          type: integer
          format: int64
          minimum: 0
          description: Total number of server-managed MCP server definitions.

    # ── Environments ─────────────────────────────────────────────────────

    Environment:
      description: Public server-managed environment definition.
      type: object
      additionalProperties: false
      required:
        - id
        - revision
        - provider
        - image
        - resources
        - network
        - lifecycle
        - labels
        - env
      properties:
        id:
          type: string
          pattern: "^[a-z0-9][a-z0-9-]{0,62}$"
          example: docker
        revision:
          type: string
          pattern: "^[0-9a-f]{64}$"
          description: Stable revision used with `If-Match` for optimistic concurrency.
          example: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
        provider:
          $ref: "#/components/schemas/EnvironmentProvider"
        cwd:
          type: ["string", "null"]
          description: Local-provider command working directory for this environment. Docker and Daytona ignore this value.
          example: /srv/fabro/workspaces/team-a
        image:
          $ref: "#/components/schemas/EnvironmentApiImageSettings"
        resources:
          $ref: "#/components/schemas/EnvironmentResourcesSettings"
        network:
          $ref: "#/components/schemas/EnvironmentNetworkSettings"
        lifecycle:
          $ref: "#/components/schemas/EnvironmentLifecycleSettings"
        labels:
          $ref: "#/components/schemas/StringMap"
        env:
          type: object
          additionalProperties:
            $ref: "#/components/schemas/InterpString"

    CreateEnvironmentRequest:
      description: Request body for creating a server-managed environment.
      type: object
      additionalProperties: false
      required:
        - id
        - provider
        - image
        - resources
        - network
        - lifecycle
        - labels
        - env
      properties:
        id:
          type: string
          pattern: "^[a-z0-9][a-z0-9-]{0,62}$"
          example: docker
        provider:
          $ref: "#/components/schemas/EnvironmentProvider"
        cwd:
          type: ["string", "null"]
          description: Local-provider command working directory for this environment. Docker and Daytona ignore this value.
          example: /srv/fabro/workspaces/team-a
        image:
          $ref: "#/components/schemas/EnvironmentApiImageSettings"
        resources:
          $ref: "#/components/schemas/EnvironmentResourcesSettings"
        network:
          $ref: "#/components/schemas/EnvironmentNetworkSettings"
        lifecycle:
          $ref: "#/components/schemas/EnvironmentLifecycleSettings"
        labels:
          $ref: "#/components/schemas/StringMap"
        env:
          type: object
          additionalProperties:
            $ref: "#/components/schemas/InterpString"

    ReplaceEnvironmentRequest:
      description: Request body for replacing a server-managed environment. The path id is authoritative.
      type: object
      additionalProperties: false
      required:
        - provider
        - image
        - resources
        - network
        - lifecycle
        - labels
        - env
      properties:
        provider:
          $ref: "#/components/schemas/EnvironmentProvider"
        cwd:
          type: ["string", "null"]
          description: Local-provider command working directory for this environment. Docker and Daytona ignore this value.
          example: /srv/fabro/workspaces/team-a
        image:
          $ref: "#/components/schemas/EnvironmentApiImageSettings"
        resources:
          $ref: "#/components/schemas/EnvironmentResourcesSettings"
        network:
          $ref: "#/components/schemas/EnvironmentNetworkSettings"
        lifecycle:
          $ref: "#/components/schemas/EnvironmentLifecycleSettings"
        labels:
          $ref: "#/components/schemas/StringMap"
        env:
          type: object
          additionalProperties:
            $ref: "#/components/schemas/InterpString"

    EnvironmentApiImageSettings:
      description: REST-safe environment image settings. Dockerfile sources are inline-only; local paths are rejected by the REST API.
      type: object
      additionalProperties: false
      required: [docker, dockerfile]
      properties:
        docker:
          type: ["string", "null"]
        dockerfile:
          oneOf:
            - $ref: "#/components/schemas/EnvironmentApiDockerfileSourceInline"
            - type: "null"

    EnvironmentApiDockerfileSourceInline:
      type: object
      additionalProperties: false
      required: [type, value]
      properties:
        type:
          type: string
          enum: [inline]
        value:
          type: string

    EnvironmentListResponse:
      description: List envelope for environment definitions.
      type: object
      additionalProperties: false
      required:
        - data
        - meta
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Environment"
        meta:
          $ref: "#/components/schemas/EnvironmentListMeta"

    EnvironmentListMeta:
      description: Metadata for environment list responses.
      type: object
      additionalProperties: false
      required:
        - total
      properties:
        total:
          type: integer
          format: int64
          minimum: 0
          description: Total number of server-managed environment definitions.

    # ── Pagination ───────────────────────────────────────────────────────

    PaginationMeta:
      description: Pagination metadata included in every paginated response.
      type: object
      required:
        - has_more
      properties:
        has_more:
          type: boolean
          description: Whether additional pages of results are available.
        total:
          type: integer
          format: int64
          minimum: 0
          description: |
            Total number of items matching the current filters. Optional —
            only populated by endpoints that compute the full count cheaply
            (e.g. in-memory filtering). When omitted, clients should rely on
            `has_more` and cursor through pages.
          example: true

    PaginatedRunList:
      description: Paginated list of runs.
      type: object
      required:
        - data
        - meta
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Run"
        meta:
          $ref: "#/components/schemas/PaginationMeta"

    BatchRunLifecycleRequest:
      description: Run IDs to archive or unarchive as one bounded fail-soft batch.
      type: object
      additionalProperties: false
      required:
        - run_ids
      properties:
        run_ids:
          type: array
          description: Run IDs to process, in result order.
          minItems: 1
          maxItems: 250
          uniqueItems: true
          items:
            type: string
            example: 01HZX6M29F1CD5YYMHT1F5D7WQ

    BatchRunLifecycleResponse:
      description: Per-run results for a fail-soft batch archive or unarchive request.
      type: object
      additionalProperties: false
      required:
        - results
        - summary
      properties:
        results:
          type: array
          description: Results ordered exactly like the request `run_ids`.
          items:
            $ref: "#/components/schemas/BatchRunLifecycleResult"
        summary:
          $ref: "#/components/schemas/BatchRunLifecycleSummary"

    BatchRunLifecycleResult:
      description: Result for one run in a batch archive or unarchive request.
      type: object
      additionalProperties: false
      required:
        - run_id
        - ok
        - outcome
      properties:
        run_id:
          type: string
          description: Run ID from the request item.
        ok:
          type: boolean
          description: Whether this item succeeded.
        outcome:
          type: string
          enum:
            - archived
            - already_archived
            - unarchived
            - not_archived
            - not_found
            - conflict
            - error
          description: Machine-readable item outcome.
        run:
          $ref: "#/components/schemas/Run"
          description: Decorated run summary for successful items when it can be loaded.
        error:
          $ref: "#/components/schemas/ErrorResponseEntry"
          description: Structured item error for failed items.

    BatchRunLifecycleSummary:
      description: Aggregate counts for a batch archive or unarchive request.
      type: object
      additionalProperties: false
      required:
        - requested
        - succeeded
        - failed
      properties:
        requested:
          type: integer
          minimum: 0
          description: Number of requested run IDs.
        succeeded:
          type: integer
          minimum: 0
          description: Number of item results with `ok=true`.
        failed:
          type: integer
          minimum: 0
          description: Number of item results with `ok=false`.

    BatchDeleteRunsRequest:
      description: Run IDs to delete as one bounded fail-soft batch.
      type: object
      additionalProperties: false
      required:
        - run_ids
      properties:
        run_ids:
          type: array
          description: Run IDs to process, in result order.
          minItems: 1
          maxItems: 250
          uniqueItems: true
          items:
            type: string
            example: 01HZX6M29F1CD5YYMHT1F5D7WQ
        force:
          type: boolean
          description: Whether to force deletion of active runs. Defaults to `false`.
          default: false

    BatchDeleteRunsResponse:
      description: Per-run results for a fail-soft batch delete request.
      type: object
      additionalProperties: false
      required:
        - results
        - summary
      properties:
        results:
          type: array
          description: Results ordered exactly like the request `run_ids`.
          items:
            $ref: "#/components/schemas/BatchDeleteRunsResult"
        summary:
          $ref: "#/components/schemas/BatchDeleteRunsSummary"

    BatchDeleteRunsResult:
      description: Result for one run in a batch delete request.
      type: object
      additionalProperties: false
      required:
        - run_id
        - ok
        - outcome
      properties:
        run_id:
          type: string
          description: Run ID from the request item.
        ok:
          type: boolean
          description: Whether this item succeeded.
        outcome:
          type: string
          enum:
            - deleted
            - already_absent
            - sandbox_preserved
            - conflict
            - error
          description: Machine-readable item outcome.
        sandbox:
          $ref: "#/components/schemas/DeleteRunSandbox"
          description: Sandbox handoff details when `outcome` is `sandbox_preserved`.
        error:
          $ref: "#/components/schemas/ErrorResponseEntry"
          description: Structured item error for failed items.

    BatchDeleteRunsSummary:
      description: Aggregate counts for a batch delete request.
      type: object
      additionalProperties: false
      required:
        - requested
        - succeeded
        - failed
      properties:
        requested:
          type: integer
          minimum: 0
          description: Number of requested run IDs.
        succeeded:
          type: integer
          minimum: 0
          description: Number of item results with `ok=true`.
        failed:
          type: integer
          minimum: 0
          description: Number of item results with `ok=false`.

    PairId:
      type: string
      description: Durable run pair identifier.
      example: 01HZX6M29F1CD5YYMHT1F5D7WQ

    PairMessageId:
      type: string
      description: Durable pair message identifier.
      example: 01HZX6M4D7Y1QW0Q0P6V8Z4DR5

    PairStatus:
      type: string
      enum: [active, ended, failed]

    PairTarget:
      type: object
      additionalProperties: false
      required:
        - stage_id
        - node_label
      properties:
        stage_id:
          type: string
          example: code@1
        node_label:
          type: string
          example: Code

    PairRecord:
      type: object
      required:
        - pair_id
        - run_id
        - status
        - started_at
        - target
      properties:
        pair_id:
          $ref: "#/components/schemas/PairId"
        run_id:
          type: string
        status:
          $ref: "#/components/schemas/PairStatus"
        started_at:
          type: string
          format: date-time
        ended_at:
          type: ["string", "null"]
          format: date-time
        failure_reason:
          type: ["string", "null"]
        target:
          $ref: "#/components/schemas/PairTarget"

    RunPairStatusResponse:
      type: object
      required:
        - run_id
        - targets
      properties:
        run_id:
          type: string
        current_pair:
          oneOf:
            - $ref: "#/components/schemas/PairRecord"
            - type: "null"
        targets:
          type: array
          items:
            $ref: "#/components/schemas/PairTarget"

    PairStartRequest:
      type: object
      additionalProperties: false
      required:
        - stage_id
      properties:
        stage_id:
          type: string
          example: code@1

    PairMessageRequest:
      type: object
      required:
        - text
      properties:
        text:
          type: string
          minLength: 1
          maxLength: 8192
        client_message_id:
          type: string

    PairMessageRecord:
      type: object
      required:
        - message_id
        - pair_id
        - run_id
        - stage_id
        - text
        - accepted_at
      properties:
        message_id:
          $ref: "#/components/schemas/PairMessageId"
        client_message_id:
          type: ["string", "null"]
        pair_id:
          $ref: "#/components/schemas/PairId"
        run_id:
          type: string
        stage_id:
          type: string
          example: code@1
        text:
          type: string
        accepted_at:
          type: string
          format: date-time

    PairTranscriptResponse:
      type: object
      required:
        - data
        - meta
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/PairTranscriptEntry"
        meta:
          type: object
          required:
            - next_since_seq
            - has_more
          properties:
            next_since_seq:
              type: integer
              minimum: 1
            has_more:
              type: boolean

    PairTranscriptEntry:
      oneOf:
        - $ref: "#/components/schemas/PairTranscriptUserMessage"
        - $ref: "#/components/schemas/PairTranscriptSystemMessage"
        - $ref: "#/components/schemas/PairTranscriptAssistantMessage"
        - $ref: "#/components/schemas/PairTranscriptToolCall"
        - $ref: "#/components/schemas/PairTranscriptError"
        - $ref: "#/components/schemas/PairTranscriptWarning"
      discriminator:
        propertyName: kind

    PairTranscriptUserMessage:
      type: object
      required: [kind, seq, event_id, ts, pair_id, target, message_id, text]
      properties:
        kind:
          type: string
          enum: [user_message]
        seq:
          type: integer
          minimum: 1
        event_id:
          type: string
        ts:
          type: string
          format: date-time
        pair_id:
          $ref: "#/components/schemas/PairId"
        target:
          $ref: "#/components/schemas/PairTarget"
        message_id:
          $ref: "#/components/schemas/PairMessageId"
        client_message_id:
          type: ["string", "null"]
        text:
          type: string

    PairTranscriptSystemMessage:
      type: object
      required: [kind, seq, event_id, ts, pair_id, target, system_message_kind, text]
      properties:
        kind:
          type: string
          enum: [system_message]
        seq:
          type: integer
          minimum: 1
        event_id:
          type: string
        ts:
          type: string
          format: date-time
        pair_id:
          $ref: "#/components/schemas/PairId"
        target:
          $ref: "#/components/schemas/PairTarget"
        system_message_kind:
          type: string
          enum: [human_joined, human_left]
        text:
          type: string

    PairTranscriptAssistantMessage:
      type: object
      additionalProperties: false
      required:
        - kind
        - seq
        - event_id
        - ts
        - pair_id
        - target
        - text
        - tool_call_count
      properties:
        kind:
          type: string
          enum: [assistant_message]
        seq:
          type: integer
          format: uint32
        event_id:
          type: string
        ts:
          type: string
          format: date-time
        pair_id:
          $ref: "#/components/schemas/PairId"
        target:
          $ref: "#/components/schemas/PairTarget"
        text:
          type: string
        tool_call_count:
          type: integer
          minimum: 0

    PairTranscriptToolCall:
      type: object
      required: [kind, seq, event_id, ts, pair_id, target, tool_call_id, tool_name, status, summary, is_error, truncated, detail_ref]
      properties:
        kind:
          type: string
          enum: [tool_call]
        seq:
          type: integer
          minimum: 1
        event_id:
          type: string
        ts:
          type: string
          format: date-time
        pair_id:
          $ref: "#/components/schemas/PairId"
        target:
          $ref: "#/components/schemas/PairTarget"
        tool_call_id:
          type: string
        tool_name:
          type: string
        status:
          type: string
          enum: [started, completed]
        summary:
          type: string
        is_error:
          type: boolean
        truncated:
          type: boolean
        detail_ref:
          $ref: "#/components/schemas/PairTranscriptDetailRef"

    PairTranscriptError:
      type: object
      required: [kind, seq, event_id, ts, pair_id, target, message, detail_ref]
      properties:
        kind:
          type: string
          enum: [error]
        seq:
          type: integer
          minimum: 1
        event_id:
          type: string
        ts:
          type: string
          format: date-time
        pair_id:
          $ref: "#/components/schemas/PairId"
        target:
          $ref: "#/components/schemas/PairTarget"
        message:
          type: string
        detail_ref:
          $ref: "#/components/schemas/PairTranscriptDetailRef"

    PairTranscriptWarning:
      type: object
      required: [kind, seq, event_id, ts, pair_id, target, warning_kind, message, detail_ref]
      properties:
        kind:
          type: string
          enum: [warning]
        seq:
          type: integer
          minimum: 1
        event_id:
          type: string
        ts:
          type: string
          format: date-time
        pair_id:
          $ref: "#/components/schemas/PairId"
        target:
          $ref: "#/components/schemas/PairTarget"
        warning_kind:
          type: string
        message:
          type: string
        detail_ref:
          $ref: "#/components/schemas/PairTranscriptDetailRef"

    PairTranscriptDetailRef:
      type: object
      required: [seq]
      properties:
        seq:
          type: integer
          minimum: 1
        tool_call_id:
          type: string

    RunEventDetailResponse:
      type: object
      required:
        - event
        - properties
        - truncated
        - redacted
        - max_content_length
      properties:
        event:
          type: object
          required:
            - seq
            - id
            - ts
            - run_id
            - event
          properties:
            seq:
              type: integer
              minimum: 1
            id:
              type: string
            ts:
              type: string
              format: date-time
            run_id:
              type: string
            event:
              type: string
            actor:
              $ref: "#/components/schemas/Principal"
            session_id:
              type: string
            node_id:
              type: string
            node_label:
              type: string
            stage_id:
              type: string
            tool_call_id:
              type: string
        properties:
          type: object
          additionalProperties: true
        content:
          type: object
          required: [kind, value]
          properties:
            kind:
              type: string
              enum:
                - text
                - tool_output
                - tool_arguments
                - error
                - details
            value:
              type: string
        truncated:
          type: boolean
        redacted:
          type: boolean
        max_content_length:
          type: integer

    SessionId:
      description: Durable session identifier.
      type: string
      example: 01HZX6M0P7SE4VJ9Y3X2B8E9QF

    TurnId:
      description: Durable session turn identifier.
      type: string
      example: 01HZX6M29F1CD5YYMHT1F5D7WQ

    SessionStatus:
      type: string
      enum: [idle, running, failed]

    PermissionLevel:
      description: Agent tool permission level applied to a session.
      type: string
      enum: [read-only, read-write, full]

    SessionTurn:
      description: Currently active durable session turn.
      type: object
      required:
        - id
        - started_at
        - input
      properties:
        id:
          $ref: "#/components/schemas/TurnId"
        started_at:
          type: string
          format: date-time
        input:
          type: string

    SessionMessage:
      description: Persisted full-fidelity session transcript message.
      type: object
      required:
        - kind
        - timestamp
      properties:
        kind:
          type: string
          enum: [user, assistant, tool_results, system, steering]
        content:
          type: string
        timestamp:
          type: string
          format: date-time
        tool_calls:
          type: array
          items: {}
        provider_parts:
          type: array
          items: {}
        usage: {}
        response_id:
          type: string
        results:
          type: array
          items: {}

    SessionRecord:
      description: Ask Fabro session metadata derived from the owning run event stream.
      type: object
      required:
        - id
        - run_id
        - status
        - active_turn
        - created_at
        - updated_at
      properties:
        id:
          $ref: "#/components/schemas/SessionId"
        run_id:
          type: string
        title:
          type: ["string", "null"]
        status:
          $ref: "#/components/schemas/SessionStatus"
        model:
          type: ["string", "null"]
          description: Canonical model ID selected when the session was created.
        provider:
          oneOf:
            - $ref: "#/components/schemas/ProviderId"
            - type: "null"
          description: Provider selected when the session was created.
        active_turn:
          oneOf:
            - $ref: "#/components/schemas/SessionTurn"
            - type: "null"
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    SessionSummary:
      description: List projection of an Ask Fabro session.
      type: object
      required:
        - id
        - run_id
        - status
        - active_turn
        - created_at
        - updated_at
      properties:
        id:
          $ref: "#/components/schemas/SessionId"
        run_id:
          type: string
        title:
          type: ["string", "null"]
        status:
          $ref: "#/components/schemas/SessionStatus"
        model:
          type: ["string", "null"]
          description: Canonical model ID selected when the session was created.
        provider:
          oneOf:
            - $ref: "#/components/schemas/ProviderId"
            - type: "null"
          description: Provider selected when the session was created.
        active_turn:
          oneOf:
            - $ref: "#/components/schemas/SessionTurn"
            - type: "null"
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    SessionDetail:
      description: Session metadata plus durable transcript projection.
      type: object
      required:
        - id
        - run_id
        - status
        - active_turn
        - created_at
        - updated_at
        - messages
        - last_seq
      properties:
        id:
          $ref: "#/components/schemas/SessionId"
        run_id:
          type: string
        title:
          type: ["string", "null"]
        status:
          $ref: "#/components/schemas/SessionStatus"
        model:
          type: ["string", "null"]
          description: Canonical model ID selected when the session was created.
        provider:
          oneOf:
            - $ref: "#/components/schemas/ProviderId"
            - type: "null"
          description: Provider selected when the session was created.
        active_turn:
          oneOf:
            - $ref: "#/components/schemas/SessionTurn"
            - type: "null"
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        messages:
          type: array
          items:
            $ref: "#/components/schemas/SessionMessage"
        last_seq:
          type: integer
          minimum: 0

    CreateRunSessionRequest:
      type: object
      properties:
        title:
          type: string
        model:
          type: string
          description: |
            Catalog model ID or alias, optionally qualified as
            `provider:selector`. A provider-qualified selector may be a
            canonical model ID, alias, or provider API ID. A value counts as
            qualified only when the text before the first `:` names a known
            provider, so model IDs containing a colon stay whole. Legacy
            `provider/model` references remain accepted. The server stores the
            canonical model ID.
        provider:
          $ref: "#/components/schemas/ProviderId"
          description: Optional provider pin. Provider-qualified model references remain accepted for compatibility.

    SubmitTurnRequest:
      type: object
      required:
        - input
      properties:
        input:
          type: string
        turn_id:
          $ref: "#/components/schemas/TurnId"

    PaginatedSessionList:
      description: Paginated list of sessions.
      type: object
      required:
        - data
        - meta
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/SessionSummary"
        meta:
          $ref: "#/components/schemas/PaginationMeta"

    WorkflowScheduleSummary:
      description: Workflow schedule summary shown in workflow lists.
      type: object
      required:
        - expression
      properties:
        expression:
          type: string
        next_run:
          type: ["string", "null"]
          format: date-time

    WorkflowLastRunSummary:
      description: Most recent run timestamp for a workflow.
      type: object
      properties:
        ran_at:
          type: ["string", "null"]
          format: date-time

    WorkflowListItem:
      description: Workflow summary shown in workflow list pages.
      type: object
      required:
        - name
        - slug
        - filename
      properties:
        name:
          type: string
        slug:
          type: string
        filename:
          type: string
        last_run:
          oneOf:
            - $ref: "#/components/schemas/WorkflowLastRunSummary"
            - type: "null"
        schedule:
          oneOf:
            - $ref: "#/components/schemas/WorkflowScheduleSummary"
            - type: "null"

    PaginatedWorkflowListResponse:
      description: Paginated list of workflows.
      type: object
      required:
        - data
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/WorkflowListItem"
        pagination:
          $ref: "#/components/schemas/PaginationMeta"

    WorkflowDetailResponse:
      description: Workflow definition and dense settings snapshot.
      type: object
      required:
        - name
        - slug
        - description
        - filename
        - settings
        - graph
      properties:
        name:
          type: string
        slug:
          type: string
        description:
          type: string
        filename:
          type: string
        settings:
          $ref: "#/components/schemas/WorkflowSettings"
        graph:
          type: string

    PaginatedModelList:
      description: Paginated list of models.
      type: object
      required:
        - data
        - meta
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Model"
        meta:
          $ref: "#/components/schemas/PaginationMeta"

    ProviderList:
      description: List of LLM providers from the catalog.
      type: object
      required:
        - data
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Provider"

    ProviderCredentialTestRequest:
      description: API key to validate against an LLM provider without persisting it.
      type: object
      required:
        - api_key
      properties:
        api_key:
          type: string

    ProviderCredentialTestResponse:
      description: Successful response from provider credential validation.
      type: object
      required:
        - ok
      properties:
        ok:
          type: boolean
          example: true

    ProviderTestList:
      description: Results from testing all configured LLM providers.
      type: object
      required:
        - data
        - summary
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/ProviderTestResult"
        summary:
          $ref: "#/components/schemas/ProviderTestSummary"

    ProviderTestResult:
      description: Result of testing one configured LLM provider.
      type: object
      required:
        - provider
        - model_id
        - status
        - error_message
      properties:
        provider:
          $ref: "#/components/schemas/ProviderId"
        model_id:
          type: ["string", "null"]
          description: Catalog probe model ID used for the provider, or null when no probe was sent.
          example: "gpt-5.4-mini"
        status:
          $ref: "#/components/schemas/ProviderTestStatus"
        error_message:
          type: ["string", "null"]
          description: Error details when status is "error".

    ProviderTestSummary:
      description: Aggregate provider test status and counts.
      type: object
      required:
        - status
        - total
        - passed
        - failed
      properties:
        status:
          $ref: "#/components/schemas/ProviderTestStatus"
        total:
          type: integer
          format: int32
          minimum: 0
          description: Number of configured providers tested or evaluated.
        passed:
          type: integer
          format: int32
          minimum: 0
          description: Number of providers whose probe succeeded.
        failed:
          type: integer
          format: int32
          minimum: 0
          description: Number of providers with configuration, auth, registration, or probe errors.

    ProviderTestStatus:
      description: Provider test status.
      type: string
      enum:
        - ok
        - error

    Provider:
      description: An LLM provider from the catalog with effective config and configured status.
      type: object
      required:
        - id
        - display_name
        - adapter
        - priority
        - model_count
        - configured
      properties:
        id:
          $ref: "#/components/schemas/ProviderId"
        display_name:
          type: string
          description: Human-readable provider name.
          example: "Anthropic"
        adapter:
          type: string
          enum: [anthropic, openai, gemini, openai_compatible]
          description: Protocol adapter the provider speaks.
          example: "anthropic"
        base_url:
          type: ["string", "null"]
          description: Operator-set base URL override, if any.
        api_key_url:
          type: ["string", "null"]
          description: URL where an operator can obtain an API key for this provider.
        priority:
          type: integer
          format: int32
          description: Catalog ordering priority; higher sorts first.
        aliases:
          type: array
          items:
            type: string
          description: Alternative identifiers that resolve to this provider.
        model_count:
          type: integer
          format: int32
          minimum: 0
          description: Number of catalog models belonging to this provider.
        default_model:
          type: ["string", "null"]
          description: Catalog default model ID for this provider, if any.
        configured:
          type: boolean
          description: |
            Whether credential material is present for this provider on the
            server when this response was produced. Does NOT imply requests
            will succeed.
        expected_secret_name:
          type: ["string", "null"]
          description: |
            Suggested vault secret name for configuring this provider,
            derived from the first vault credential reference in the
            provider catalog. Null when the provider has no vault
            credential (e.g. no-auth or env-only providers). Used to
            prefill the create-secret form.

    ProviderId:
      description: LLM provider identifier.
      type: string
      example: anthropic

    ModelLimits:
      description: Token limits for a model.
      type: object
      required:
        - context_window
        - max_output
      properties:
        context_window:
          type: integer
          format: int64
          description: Maximum context window size in tokens.
          example: 1000000
        max_output:
          type: ["integer", "null"]
          format: int64
          description: Maximum output tokens, if known.
          example: 128000

    ReasoningEffortFeature:
      description: >-
        Whether the model endpoint supports a native reasoning-effort
        parameter. `levels` accepts discrete effort levels; `always_adaptive`
        accepts effort levels with natively always-on adaptive thinking;
        `none` has no native effort parameter.
      type: string
      enum:
        - levels
        - always_adaptive
        - none

    ReasoningEffort:
      description: Native reasoning-effort level requested for an LLM call.
      type: string
      enum:
        - low
        - medium
        - high
        - xhigh
        - max

    ModelFeatures:
      description: Capability flags for a model.
      type: object
      required:
        - tools
        - vision
        - reasoning
        - reasoning_effort
        - prompt_cache
        - cache_control_breakpoints
        - sampling_params
      properties:
        tools:
          type: boolean
          description: Whether the model supports tool use.
        vision:
          type: boolean
          description: Whether the model supports vision/image inputs.
        reasoning:
          type: boolean
          description: Whether the model supports extended reasoning.
        reasoning_effort:
          $ref: "#/components/schemas/ReasoningEffortFeature"
        prompt_cache:
          type: boolean
          description: Whether the model endpoint supports prompt caching.
        cache_control_breakpoints:
          type: boolean
          description: >-
            Whether the endpoint only caches when the request marks the
            cacheable prefix with Anthropic-style cache_control breakpoints
            (e.g. Claude via OpenRouter).
        sampling_params:
          type: boolean
          description: Whether the model accepts classic sampling parameters (temperature, top_p).

    ModelCosts:
      description: Pricing per million tokens in USD.
      type: object
      required:
        - input_cost_per_mtok
        - output_cost_per_mtok
        - cache_input_cost_per_mtok
      properties:
        input_cost_per_mtok:
          type: ["number", "null"]
          format: double
          description: Cost per million input tokens in USD.
          example: 15.0
        output_cost_per_mtok:
          type: ["number", "null"]
          format: double
          description: Cost per million output tokens in USD.
          example: 75.0
        cache_input_cost_per_mtok:
          type: ["number", "null"]
          format: double
          description: Cost per million cached input tokens in USD.
          example: 1.50

    ModelControls:
      description: Request-control values accepted by a provider/model offering.
      type: object
      required:
        - reasoning_effort
      properties:
        reasoning_effort:
          type: array
          description: >-
            Exact reasoning-effort values accepted by this offering. An empty
            array means the request control is unsupported.
          items:
            $ref: "#/components/schemas/ReasoningEffort"

    Model:
      description: |
        One provider's offering of an LLM model. The `id` is unique within
        `provider`; `(provider, id)` is the stable resource identity.
      type: object
      required:
        - id
        - provider
        - family
        - display_name
        - limits
        - training
        - knowledge_cutoff
        - features
        - controls
        - costs
        - estimated_output_tps
        - aliases
        - default
        - small_default
        - configured
      properties:
        id:
          type: string
          description: Canonical human-facing model ID, unique within the provider.
          example: "claude-opus-4-6"
        provider:
          $ref: "#/components/schemas/ProviderId"
        family:
          type: string
          description: Model family grouping.
          example: "claude-4"
        display_name:
          type: string
          description: Human-readable model name.
          example: "Claude Opus 4.6"
        limits:
          $ref: "#/components/schemas/ModelLimits"
        training:
          type: ["string", "null"]
          description: Training data cutoff date (YYYY-MM-DD).
          example: "2025-08-01"
        knowledge_cutoff:
          type: ["string", "null"]
          description: Public knowledge cutoff label, if known.
          example: "May 2025"
        features:
          $ref: "#/components/schemas/ModelFeatures"
        controls:
          $ref: "#/components/schemas/ModelControls"
        costs:
          $ref: "#/components/schemas/ModelCosts"
        estimated_output_tps:
          type: ["number", "null"]
          format: double
          description: Estimated output tokens per second.
        aliases:
          type: array
          items:
            type: string
          description: Alternative names that resolve to this model.
          example: ["opus"]
        default:
          type: boolean
          description: Whether this is the default model for its provider.
        small_default:
          type: boolean
          description: Whether this is the provider's small/default utility model.
        configured:
          type: boolean
          description: |
            Whether credential material is present for this model's provider on the
            server (vault entry or environment variable). Does NOT imply the
            credential is valid or that requests will succeed; call
            `POST /models/{id}/test` to verify usability.

    ModelTestResult:
      description: Result of testing a model in `basic` or `deep` mode.
      type: object
      required:
        - model_id
        - provider
        - status
      properties:
        model_id:
          type: string
          description: The canonical model ID that was tested.
          example: "claude-opus-4-6"
        provider:
          $ref: "#/components/schemas/ProviderId"
        status:
          type: string
          enum:
            - ok
            - error
            - skip
          description: Whether the model responded successfully, failed, or was skipped because its provider is not configured.
        error_message:
          type: ["string", "null"]
          description: Error details when status is "error".

    ModelTestMode:
      description: Single-model test mode.
      type: string
      enum:
        - basic
        - deep

    # ── Completion Schemas ─────────────────────────────────────────────

    CompletionMessage:
      description: A message in the conversation.
      type: object
      required: [role, content]
      properties:
        role:
          type: string
          enum: [system, user, assistant, tool, developer]
          description: The role of the message author.
        content:
          type: array
          description: Content parts of the message.
          items:
            $ref: "#/components/schemas/CompletionContentPart"
        name:
          type: string
          description: Optional name for the message author.
        tool_call_id:
          type: string
          description: Tool call ID for tool result messages.

    CompletionContentPart:
      description: A content part within a message, discriminated by `kind`.
      type: object
      required: [kind]
      properties:
        kind:
          type: string
          description: "Content part type: text, image, tool_call, tool_result, thinking, etc."
        data:
          description: Content data, structure depends on kind.

    CompletionToolDefinition:
      description: A tool available for the model to call.
      type: object
      required: [name, description, parameters]
      properties:
        name:
          type: string
          description: Tool name.
        description:
          type: string
          description: Human-readable tool description.
        parameters:
          description: JSON Schema for the tool's parameters.

    CompletionToolChoice:
      description: Controls how the model selects tools.
      type: object
      required: [mode]
      properties:
        mode:
          type: string
          enum: [auto, none, required, named]
          description: Tool selection mode.
        tool_name:
          type: string
          description: Required when mode is "named".

    CreateCompletionRequest:
      type: object
      required: [messages]
      properties:
        messages:
          type: array
          description: The conversation messages.
          items:
            $ref: "#/components/schemas/CompletionMessage"
        model:
          type: string
          description: Model ID or alias. Server picks a ready-provider default if omitted.
        system:
          type: string
          description: System prompt (convenience; prepended as a system message).
        stream:
          type: boolean
          default: true
          description: Stream response via SSE.
        tools:
          type: array
          description: Tool definitions available to the model.
          items:
            $ref: "#/components/schemas/CompletionToolDefinition"
        tool_choice:
          $ref: "#/components/schemas/CompletionToolChoice"
        schema:
          description: JSON Schema for structured output.
        temperature:
          type: number
          format: double
        max_tokens:
          type: integer
          format: int64
        top_p:
          type: number
          format: double
        stop_sequences:
          type: array
          items:
            type: string
          description: Stop sequences.
        reasoning_effort:
          $ref: "#/components/schemas/ReasoningEffort"
          description: Reasoning effort level.
        provider:
          type: string
          description: Optional provider pin.
        provider_options:
          description: Provider-specific options.

    CompletionUsage:
      description: >
        Five disjoint token buckets for one completion. `input_tokens` excludes
        cache reads and writes, while `output_tokens` excludes reasoning tokens
        when the provider reports them separately.
      type: object
      required:
        - input_tokens
        - output_tokens
        - reasoning_tokens
        - cache_read_tokens
        - cache_write_tokens
      properties:
        input_tokens:
          type: integer
          format: int64
          description: Number of uncached input tokens consumed.
        output_tokens:
          type: integer
          format: int64
          description: Number of non-reasoning output tokens generated.
        reasoning_tokens:
          type: integer
          format: int64
          description: Number of separately reported reasoning tokens.
        cache_read_tokens:
          type: integer
          format: int64
          description: Number of input tokens served from a provider cache.
        cache_write_tokens:
          type: integer
          format: int64
          description: Number of input tokens written to a provider cache.

    CompletionResponse:
      type: object
      required: [id, model, provider, message, stop_reason, usage]
      properties:
        id:
          type: string
        model:
          type: string
          description: Canonical model ID selected for the request.
        provider:
          $ref: "#/components/schemas/ProviderId"
        message:
          $ref: "#/components/schemas/CompletionMessage"
        stop_reason:
          type: string
          description: Why generation stopped (end_turn, max_tokens, tool_calls).
        usage:
          $ref: "#/components/schemas/CompletionUsage"
        output:
          description: Parsed structured output when schema was provided.
        cost_usd:
          type: number
          format: double
          description: >
            USD cost of the completion when known: estimated from catalog
            prices unless the provider returned authoritative billing data.
        cost_source:
          $ref: "#/components/schemas/CostSource"

    CostSource:
      type: string
      description: >
        Whether `cost_usd` came from provider billing data (authoritative)
        or catalog price estimation (estimated).
      enum: [authoritative, estimated]

    # ── Playground ────────────────────────────────────────────────────────

    CreatePlaygroundChatRequest:
      description: >
        Body of POST /api/v1/playground/chat. The server is stateless across
        turns: the browser owns the draft and submits it as the literal
        `workflow.fabro` contents with every turn. The server embeds the
        file in the model's system prompt and exposes a single
        `write_workflow_file` tool that emits the full new contents of
        `workflow.fabro`; the browser parses, diffs, and animates the
        result.
      type: object
      required: [messages, workflow_fabro]
      properties:
        messages:
          type: array
          description: assistant-ui-style message history for the turn.
          items:
            $ref: "#/components/schemas/CompletionMessage"
        workflow_fabro:
          type: string
          description: >
            Full current `workflow.fabro` (Graphviz DOT) contents as
            rendered by the client — a complete `digraph <name> { ... }`
            block including the `start` / `exit` terminals. This is the
            same format the model writes back via `write_workflow_file`.
        model:
          type: string
          description: Model ID or alias. Server picks a ready-provider default if omitted.
        provider:
          $ref: "#/components/schemas/ProviderId"
          description: Optional provider pin.

    PaginatedSavedQueryList:
      description: Paginated list of saved queries.
      type: object
      required:
        - data
        - meta
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/SavedQuery"
        meta:
          $ref: "#/components/schemas/PaginationMeta"

    PaginatedHistoryEntryList:
      description: Paginated list of query history entries.
      type: object
      required:
        - data
        - meta
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/HistoryEntry"
        meta:
          $ref: "#/components/schemas/PaginationMeta"

    PaginatedApiQuestionList:
      description: Paginated list of pending questions.
      type: object
      required:
        - data
        - meta
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/ApiQuestion"
        meta:
          $ref: "#/components/schemas/PaginationMeta"

    PaginatedRunStageList:
      description: Paginated list of run stages.
      type: object
      required:
        - data
        - meta
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/RunStage"
        meta:
          $ref: "#/components/schemas/PaginationMeta"

    # ── Run Schemas ──────────────────────────────────────────────────────

    RunStatus:
      description: >
        Execution status of a run. Archive state is represented separately on
        `RunLifecycle.archived` so terminal status payloads remain intact.
      oneOf:
        - $ref: "#/components/schemas/RunStatusSubmitted"
        - $ref: "#/components/schemas/RunStatusPending"
        - $ref: "#/components/schemas/RunStatusRunnable"
        - $ref: "#/components/schemas/RunStatusStarting"
        - $ref: "#/components/schemas/RunStatusRunning"
        - $ref: "#/components/schemas/RunStatusBlocked"
        - $ref: "#/components/schemas/RunStatusPaused"
        - $ref: "#/components/schemas/RunStatusRemoving"
        - $ref: "#/components/schemas/RunStatusSucceeded"
        - $ref: "#/components/schemas/RunStatusFailed"
        - $ref: "#/components/schemas/RunStatusDead"
      discriminator:
        propertyName: kind
        mapping:
          submitted: "#/components/schemas/RunStatusSubmitted"
          pending: "#/components/schemas/RunStatusPending"
          runnable: "#/components/schemas/RunStatusRunnable"
          starting: "#/components/schemas/RunStatusStarting"
          running: "#/components/schemas/RunStatusRunning"
          blocked: "#/components/schemas/RunStatusBlocked"
          paused: "#/components/schemas/RunStatusPaused"
          removing: "#/components/schemas/RunStatusRemoving"
          succeeded: "#/components/schemas/RunStatusSucceeded"
          failed: "#/components/schemas/RunStatusFailed"
          dead: "#/components/schemas/RunStatusDead"

    RunStatusSubmitted:
      type: object
      required:
        - kind
      properties:
        kind:
          type: string
          enum:
            - submitted

    RunStatusPending:
      type: object
      required:
        - kind
        - reason
      properties:
        kind:
          type: string
          enum:
            - pending
        reason:
          $ref: "#/components/schemas/PendingReason"

    RunStatusRunnable:
      type: object
      required:
        - kind
      properties:
        kind:
          type: string
          enum:
            - runnable

    RunStatusStarting:
      type: object
      required:
        - kind
      properties:
        kind:
          type: string
          enum:
            - starting

    RunStatusRunning:
      type: object
      required:
        - kind
      properties:
        kind:
          type: string
          enum:
            - running

    RunStatusBlocked:
      type: object
      required:
        - kind
        - blocked_reason
      properties:
        kind:
          type: string
          enum:
            - blocked
        blocked_reason:
          $ref: "#/components/schemas/BlockedReason"

    RunStatusPaused:
      type: object
      required:
        - kind
        - prior_block
      properties:
        kind:
          type: string
          enum:
            - paused
        prior_block:
          oneOf:
            - $ref: "#/components/schemas/BlockedReason"
            - type: "null"

    RunStatusRemoving:
      type: object
      required:
        - kind
      properties:
        kind:
          type: string
          enum:
            - removing

    RunStatusSucceeded:
      type: object
      required:
        - kind
        - reason
      properties:
        kind:
          type: string
          enum:
            - succeeded
        reason:
          $ref: "#/components/schemas/SuccessReason"

    RunStatusFailed:
      type: object
      required:
        - kind
        - reason
      properties:
        kind:
          type: string
          enum:
            - failed
        reason:
          $ref: "#/components/schemas/FailureReason"

    RunStatusDead:
      type: object
      required:
        - kind
      properties:
        kind:
          type: string
          enum:
            - dead

    SuccessReason:
      description: Reason attached to a successful terminal run status.
      type: string
      enum:
        - completed
        - partial_success

    FailureReason:
      description: Reason attached to a failed terminal run status.
      type: string
      enum:
        - workflow_error
        - publish_failed
        - cancelled
        - approval_denied
        - terminated
        - transient_infra
        - budget_exhausted
        - launch_failed
        - bootstrap_failed
        - sandbox_init_failed

    PendingReason:
      description: Reason a pre-execution run is pending instead of runnable.
      type: string
      enum:
        - approval_required

    RunRunnableSource:
      description: Source that made a run runnable.
      type: string
      enum:
        - start_requested
        - approved

    FailureCategory:
      description: Product-level classification for grouping and retry policy.
      type: string
      enum:
        - transient_infra
        - deterministic
        - budget_exhausted
        - compilation_loop
        - canceled
        - structural

    FailureSignature:
      description: Stable normalized signature for grouping related failures.
      type: string

    ExecOutputTail:
      description: Redacted tail of command stdout/stderr captured for diagnostics.
      type: object
      properties:
        stdout:
          type: ["string", "null"]
        stderr:
          type: ["string", "null"]
        stdout_truncated:
          type: boolean
          default: false
        stderr_truncated:
          type: boolean
          default: false

    FailureDetail:
      description: Rich diagnostic detail for a failed stage or terminal run.
      type: object
      required:
        - message
        - category
      properties:
        message:
          type: string
        causes:
          type: array
          items:
            type: string
        category:
          $ref: "#/components/schemas/FailureCategory"
        system_actor:
          oneOf:
            - $ref: "#/components/schemas/SystemActorKind"
            - type: "null"
        signature:
          oneOf:
            - $ref: "#/components/schemas/FailureSignature"
            - type: "null"
        exec_output_tail:
          oneOf:
            - $ref: "#/components/schemas/ExecOutputTail"
            - type: "null"

    RunFailure:
      description: Terminal run failure reason and rich diagnostics.
      type: object
      required:
        - reason
        - detail
      properties:
        reason:
          $ref: "#/components/schemas/FailureReason"
        detail:
          $ref: "#/components/schemas/FailureDetail"

    WorkflowPath:
      description: >-
        Canonical portable path inside one workflow version. Paths are UTF-8,
        relative, at most 240 bytes and 16 components, and cannot contain empty,
        dot, parent, backslash, control, tilde-root, or drive-letter segments.
        Map keys receive stricter byte and structural validation in the domain
        model than OpenAPI can express.
      type: string
      minLength: 1
      maxLength: 240
      example: graphs/main.fabro

    WorkflowVersionId:
      description: >-
        SHA-256 identity of validated canonical workflow-version bytes. Hex input is
        case-insensitive; Fabro emits the canonical lowercase form.
      type: string
      pattern: "^[0-9A-Fa-f]{64}$"
      example: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"

    WorkflowVersion:
      description: >-
        Complete immutable package for one rooted workflow. It contains at most
        512 files and 512 workflow dependencies, each file is at most 512 KiB
        of UTF-8 content, and its compact canonical JSON representation is at
        most 2 MiB.
      type: object
      additionalProperties: false
      required:
        - entrypoint
        - files
        - workflow_dependencies
      properties:
        entrypoint:
          $ref: "#/components/schemas/WorkflowPath"
        files:
          type: object
          description: >-
            Workflow-local text files keyed by canonical path. Keys receive
            stricter domain validation than OpenAPI can express; each value is
            limited to 512 KiB of UTF-8 bytes.
          maxProperties: 512
          propertyNames:
            $ref: "#/components/schemas/WorkflowPath"
          additionalProperties:
            type: string
        workflow_dependencies:
          type: object
          description: >-
            Exact stored workflow-version IDs keyed by resolved child-workflow
            path. Keys receive stricter domain validation than OpenAPI can express.
          maxProperties: 512
          propertyNames:
            $ref: "#/components/schemas/WorkflowPath"
          additionalProperties:
            $ref: "#/components/schemas/WorkflowVersionId"

    CreateWorkflowVersionResponse:
      description: Identity of the stored immutable workflow version.
      type: object
      additionalProperties: false
      required:
        - workflow_version_id
      properties:
        workflow_version_id:
          $ref: "#/components/schemas/WorkflowVersionId"

    RunManifest:
      description: Self-contained workflow run manifest.
      type: object
      required:
        - version
        - cwd
        - target
        - workflows
      properties:
        version:
          type: integer
          description: Manifest schema version.
          example: 1
        parent_id:
          type: ["string", "null"]
          description: Optional orchestration parent run ID. Fork and rewind lineage use separate fields and should not set this value.
          example: "01HV6D7S5YF4Z4B2M7K4N0Q6T8"
        title:
          type: ["string", "null"]
          maxLength: 100
          description: Optional explicit run title. The server trims leading/trailing whitespace, rejects blank values, rejects control characters and newline characters, and requires at most 100 characters.
          example: "Add rate limiting to auth endpoints"
        cwd:
          type: string
          description: CLI working directory at invocation time.
          example: "/tmp/project"
        git:
          $ref: "#/components/schemas/GitContext"
        goal:
          $ref: "#/components/schemas/ManifestGoal"
        args:
          $ref: "#/components/schemas/ManifestArgs"
        target:
          $ref: "#/components/schemas/ManifestTarget"
        configs:
          type: array
          items:
            $ref: "#/components/schemas/ManifestConfig"
        workflows:
          type: object
          additionalProperties:
            $ref: "#/components/schemas/ManifestWorkflow"

    GitContext:
      description: Observable git state captured before the run starts.
      type: object
      required:
        - origin_url
        - branch
        - dirty
      properties:
        origin_url:
          type: string
          description: Remote origin URL with any embedded credentials removed.
          example: "https://github.com/acme/my-app.git"
        branch:
          type: string
          description: Current branch name.
          example: feature/foo
        sha:
          type: ["string", "null"]
          description: Current commit SHA, when known.
          example: abc123def
        dirty:
          $ref: "#/components/schemas/DirtyStatus"

    ManifestGoal:
      description: Resolved goal kind and content.
      type: object
      required:
        - type
        - text
      properties:
        type:
          type: string
          enum:
            - value
            - file
            - graph
        text:
          type: string
          description: Resolved goal content.

    ManifestArgs:
      description: Sparse command-local args that affect run settings.
      type: object
      properties:
        model:
          type: string
        provider:
          type: string
        environment:
          type: string
          description: Named environment slug to select for the run.
        verbose:
          type: boolean
        dry_run:
          type: boolean
        auto_approve:
          type: boolean
        preserve_sandbox:
          type: boolean
        label:
          type: array
          items:
            type: string
        input:
          type: array
          description: Raw repeated CLI input overrides, each in `KEY=VALUE` form.
          items:
            type: string

    ManifestTarget:
      type: object
      required:
        - path
      properties:
        path:
          type: string
          description: Resolved path that keys into the workflows map.
          example: .fabro/workflows/smoke/workflow.fabro

    ManifestConfig:
      type: object
      required:
        - type
      properties:
        type:
          type: string
          enum:
            - project
            - user
        path:
          type: ["string", "null"]
        source:
          type: ["string", "null"]

    ManifestWorkflowConfig:
      type: object
      required:
        - path
        - source
      properties:
        path:
          type: string
        source:
          type: string

    ManifestFileEntry:
      description: A bundled file with discovery metadata.
      type: object
      required:
        - content
        - ref
      properties:
        content:
          type: string
        ref:
          $ref: "#/components/schemas/ManifestFileRef"

    ManifestFileRef:
      type: object
      required:
        - type
        - original
      properties:
        type:
          type: string
          enum:
            - file_inline
            - import
            - dockerfile
        original:
          type: string
        from:
          type: ["string", "null"]

    ManifestWorkflow:
      type: object
      required:
        - source
      properties:
        source:
          type: string
        config:
          $ref: "#/components/schemas/ManifestWorkflowConfig"
        files:
          type: object
          additionalProperties:
            $ref: "#/components/schemas/ManifestFileEntry"

    PreflightResponse:
      type: object
      required:
        - ok
        - workflow
        - checks
      properties:
        ok:
          type: boolean
          description: Whether preflight passed using the CLI-compatible success rule.
        workflow:
          $ref: "#/components/schemas/PreflightWorkflowSummary"
        checks:
          $ref: "#/components/schemas/PreflightCheckReport"

    ValidateResponse:
      type: object
      required:
        - ok
        - workflow
      properties:
        ok:
          type: boolean
          description: Whether validation passed with no error diagnostics.
        workflow:
          $ref: "#/components/schemas/PreflightWorkflowSummary"

    RenderWorkflowGraphRequest:
      type: object
      required:
        - manifest
      properties:
        manifest:
          $ref: "#/components/schemas/RunManifest"
        format:
          $ref: "#/components/schemas/RenderWorkflowGraphFormat"
        direction:
          $ref: "#/components/schemas/RenderWorkflowGraphDirection"

    RenderWorkflowGraphFormat:
      type: string
      enum:
        - svg

    RenderWorkflowGraphDirection:
      type: string
      enum:
        - lr
        - tb

    PreflightWorkflowSummary:
      type: object
      required:
        - name
        - nodes
        - edges
        - goal
        - diagnostics
      properties:
        name:
          type: string
        graph_path:
          type: ["string", "null"]
        nodes:
          type: integer
        edges:
          type: integer
        goal:
          type: string
        diagnostics:
          type: array
          items:
            $ref: "#/components/schemas/WorkflowDiagnostic"

    WorkflowDiagnostic:
      type: object
      required:
        - rule
        - severity
        - message
      properties:
        rule:
          type: string
        severity:
          type: string
          enum:
            - error
            - warning
            - info
        message:
          type: string
        node_id:
          type: ["string", "null"]
        edge:
          type: ["array", "null"]
          minItems: 2
          maxItems: 2
          items:
            type: string
        fix:
          type: ["string", "null"]
        source_path:
          type: ["string", "null"]
        line:
          type: ["integer", "null"]
          format: int32
        column:
          type: ["integer", "null"]
          format: int32
        span_start:
          type: ["integer", "null"]
          format: int64
        span_len:
          type: ["integer", "null"]
          format: int64
        related:
          type: array
          items:
            $ref: "#/components/schemas/RelatedWorkflowDiagnostic"
          default: []

    RelatedWorkflowDiagnostic:
      type: object
      required:
        - message
      properties:
        message:
          type: string
        source_path:
          type: ["string", "null"]
        line:
          type: ["integer", "null"]
          format: int32
        column:
          type: ["integer", "null"]
          format: int32

    PreflightCheckReport:
      type: object
      required:
        - title
        - sections
      properties:
        title:
          type: string
        sections:
          type: array
          items:
            $ref: "#/components/schemas/PreflightCheckSection"

    PreflightCheckSection:
      type: object
      required:
        - title
        - checks
      properties:
        title:
          type: string
        checks:
          type: array
          items:
            $ref: "#/components/schemas/PreflightCheckResult"

    PreflightCheckResult:
      type: object
      required:
        - name
        - status
        - summary
        - details
      properties:
        name:
          type: string
        status:
          type: string
          enum:
            - pass
            - warning
            - error
        summary:
          type: string
        details:
          type: array
          items:
            $ref: "#/components/schemas/PreflightCheckDetail"
        remediation:
          type: ["string", "null"]

    PreflightCheckDetail:
      type: object
      required:
        - text
        - warn
      properties:
        text:
          type: string
        warn:
          type: boolean

    SteerRunRequest:
      description: Request body for steering a running run mid-execution.
      type: object
      required:
        - text
      properties:
        text:
          type: string
          description: The steering message text to deliver as a user turn.
          minLength: 1
          maxLength: 8192
          example: Try a different approach
        interrupt:
          type: boolean
          description: |
            When true, apply a worker-control interrupt first, then deliver
            this text as steering in the same control operation. When false
            (default), append to the steering queue and let the agent pick it
            up at the next turn boundary.
          default: false

    StartRunRequest:
      description: Request body for starting or resuming a run.
      type: object
      properties:
        resume:
          type: boolean
          description: Resume from checkpoint instead of starting from submitted state.
          default: false

    DenyRunRequest:
      description: Request body for denying a pending run approval request.
      type: object
      properties:
        reason:
          type: string
          description: Optional human-readable reason for denying execution. Empty or whitespace-only values are stored as absent.
          example: Not approved for execution

    UpdateRunRequest:
      description: Request body for updating mutable run metadata.
      type: object
      required:
        - title
      properties:
        title:
          type: string
          maxLength: 100
          description: New run title. The server trims leading/trailing whitespace, rejects blank values, rejects control characters and newline characters, and requires at most 100 characters.
          example: "Add rate limiting to auth endpoints"

    DeleteRunResponse:
      description: Returned when a run is deleted but its sandbox is intentionally preserved.
      type: object
      required: [deleted, sandbox_preserved, sandbox]
      properties:
        deleted:
          type: boolean
        sandbox_preserved:
          type: boolean
        sandbox:
          $ref: "#/components/schemas/DeleteRunSandbox"

    DeleteRunSandbox:
      type: object
      required: [provider, id]
      properties:
        provider:
          $ref: "#/components/schemas/SandboxProviderKind"
        id:
          type: string

    ApiQuestion:
      description: A pending human-in-the-loop question generated by a workflow stage.
      type: object
      required:
        - id
        - text
        - stage
        - question_type
        - options
        - allow_freeform
      properties:
        id:
          type: string
          description: Unique question identifier.
          example: q-001
        text:
          type: string
          description: The question text displayed to the user.
          example: Should we proceed with the proposed changes?
        stage:
          type: string
          description: Workflow stage identifier that produced the question.
          example: gate
        question_type:
          $ref: "#/components/schemas/QuestionType"
        options:
          type: array
          description: Available options for selection-based questions. Empty for freeform questions.
          items:
            $ref: "#/components/schemas/InterviewOption"
        allow_freeform:
          type: boolean
          description: Whether the user may provide freeform text in addition to selecting options.
          example: true
        timeout_seconds:
          type: ["number", "null"]
          format: double
          description: Timeout for the question when configured by the workflow.
          example: 30
        context_display:
          type: ["string", "null"]
          description: Optional contextual text shown alongside the question.
          example: Latest draft
        review_target:
          description: Optional validated external resource that is the primary subject of this review question.
          oneOf:
            - $ref: "#/components/schemas/ReviewTarget"
            - type: "null"

    QuestionType:
      description: The interaction type of a human-in-the-loop question.
      type: string
      enum:
        - yes_no
        - multiple_choice
        - multi_select
        - freeform
        - confirmation

    SubmitAnswerRequest:
      description: >
        Request body for submitting an answer to a pending question. The
        `kind` discriminator determines which answer shape is submitted.
      oneOf:
        - $ref: "#/components/schemas/SubmitAnswerYesRequest"
        - $ref: "#/components/schemas/SubmitAnswerNoRequest"
        - $ref: "#/components/schemas/SubmitAnswerSelectedRequest"
        - $ref: "#/components/schemas/SubmitAnswerMultiSelectedRequest"
        - $ref: "#/components/schemas/SubmitAnswerTextRequest"
      discriminator:
        propertyName: kind
        mapping:
          "yes": "#/components/schemas/SubmitAnswerYesRequest"
          "no": "#/components/schemas/SubmitAnswerNoRequest"
          selected: "#/components/schemas/SubmitAnswerSelectedRequest"
          multi_selected: "#/components/schemas/SubmitAnswerMultiSelectedRequest"
          text: "#/components/schemas/SubmitAnswerTextRequest"

    SubmitAnswerYesRequest:
      type: object
      required:
        - kind
      properties:
        kind:
          type: string
          enum: ["yes"]
          description: Affirmative answer for yes/no and confirmation questions.

    SubmitAnswerNoRequest:
      type: object
      required:
        - kind
      properties:
        kind:
          type: string
          enum: ["no"]
          description: Negative answer for yes/no questions.

    SubmitAnswerSelectedRequest:
      type: object
      required:
        - kind
        - option_key
      properties:
        kind:
          type: string
          enum: [selected]
          description: Single selected option answer.
        option_key:
          type: string
          description: Key of the selected option.
          example: option_a

    SubmitAnswerMultiSelectedRequest:
      type: object
      required:
        - kind
        - option_keys
      properties:
        kind:
          type: string
          enum: [multi_selected]
          description: Multiple selected option answer.
        option_keys:
          type: array
          items:
            type: string
          description: Keys of selected options.
          example: ["option_a", "option_b"]

    SubmitAnswerTextRequest:
      type: object
      required:
        - kind
        - text
      properties:
        kind:
          type: string
          enum: [text]
          description: Freeform text answer.
        text:
          type: string
          description: Freeform answer text.
          example: "Yes, proceed with the changes."

    ErrorResponseEntry:
      description: A single error entry in an error response.
      type: object
      required:
        - status
        - title
        - detail
      properties:
        status:
          type: string
          description: HTTP status code as a string.
          example: "404"
        title:
          type: string
          description: Short error classification.
          example: Not Found
        detail:
          type: string
          description: Human-readable error description.
          example: Run not found.
        code:
          type: string
          description: Optional machine-readable error code for structured client handling.
          example: access_token_expired
        request_id:
          type: string
          format: uuid
          description: Server-generated request identifier; matches the x-request-id response header.

    ErrorResponse:
      description: Standard error response containing one or more error entries.
      type: object
      required:
        - errors
      properties:
        errors:
          type: array
          description: List of error entries.
          items:
            $ref: "#/components/schemas/ErrorResponseEntry"
        request_id:
          type: string
          format: uuid
          description: Server-generated request identifier; matches the x-request-id response header.
        leftover_env_keys:
          type: array
          description: >-
            Optional list of runtime env keys that were written before an install
            failure. Currently populated by `POST /install/finish` failure
            responses only.
          items:
            type: string
        removed_env_keys:
          type: array
          description: >-
            Optional list of runtime env keys that were actually removed before
            an install failure. Currently populated by `POST /install/finish`
            failure responses only.
          items:
            type: string

    AuthMethod:
      description: Runtime user authentication method.
      type: string
      enum:
        - github
        - dev_token

    SystemActorKind:
      type: string
      enum:
        - engine
        - watchdog
        - timeout

    IdpIdentity:
      type: object
      required:
        - issuer
        - subject
      properties:
        issuer:
          type: string
        subject:
          type: string

    RunServerProvenance:
      type: object
      required:
        - version
      properties:
        version:
          type: string

    RunClientProvenance:
      type: object
      properties:
        user_agent:
          type: string
        name:
          type: string
        version:
          type: string

    RunProvenance:
      type: object
      required:
        - subject
      properties:
        server:
          oneOf:
            - $ref: "#/components/schemas/RunServerProvenance"
            - type: "null"
        client:
          oneOf:
            - $ref: "#/components/schemas/RunClientProvenance"
            - type: "null"
        subject:
          $ref: "#/components/schemas/Principal"

    Principal:
      oneOf:
        - $ref: "#/components/schemas/PrincipalUser"
        - $ref: "#/components/schemas/PrincipalWorker"
        - $ref: "#/components/schemas/PrincipalWebhook"
        - $ref: "#/components/schemas/PrincipalSlack"
        - $ref: "#/components/schemas/PrincipalAgent"
        - $ref: "#/components/schemas/PrincipalSystem"
      discriminator:
        propertyName: kind
        mapping:
          user: "#/components/schemas/PrincipalUser"
          worker: "#/components/schemas/PrincipalWorker"
          webhook: "#/components/schemas/PrincipalWebhook"
          slack: "#/components/schemas/PrincipalSlack"
          agent: "#/components/schemas/PrincipalAgent"
          system: "#/components/schemas/PrincipalSystem"

    PrincipalUser:
      type: object
      required:
        - kind
        - identity
        - login
        - auth_method
      properties:
        kind:
          type: string
          enum: [user]
        identity:
          $ref: "#/components/schemas/IdpIdentity"
        login:
          type: string
        auth_method:
          $ref: "#/components/schemas/AuthMethod"
        avatar_url:
          type: ["string", "null"]

    PrincipalWorker:
      type: object
      required:
        - kind
        - run_id
      properties:
        kind:
          type: string
          enum: [worker]
        run_id:
          type: string

    PrincipalWebhook:
      type: object
      required:
        - kind
        - delivery_id
      properties:
        kind:
          type: string
          enum: [webhook]
        delivery_id:
          type: string

    PrincipalSlack:
      type: object
      required:
        - kind
        - team_id
        - user_id
      properties:
        kind:
          type: string
          enum: [slack]
        team_id:
          type: string
        user_id:
          type: string
        user_name:
          type: ["string", "null"]

    PrincipalAgent:
      type: object
      required:
        - kind
      properties:
        kind:
          type: string
          enum: [agent]
        session_id:
          type: ["string", "null"]
        parent_session_id:
          type: ["string", "null"]
        model:
          type: ["string", "null"]

    PrincipalSystem:
      type: object
      required:
        - kind
        - system_kind
      properties:
        kind:
          type: string
          enum: [system]
        system_kind:
          $ref: "#/components/schemas/SystemActorKind"

    RunEvent:
      description: >
        Internal RunEvent-compatible JSON payload. The server validates this
        body by deserializing into the typed RunEvent struct.
      type: object
      required:
        - id
        - ts
        - run_id
        - event
      properties:
        id:
          type: string
        ts:
          type: string
          format: date-time
        run_id:
          type: string
        node_id:
          type: ["string", "null"]
        node_label:
          type: ["string", "null"]
        stage_id:
          type: ["string", "null"]
          description: Stage execution identity, formatted as "{node_id}@{visit}".
        parallel_group_id:
          type: ["string", "null"]
          description: >
            Durable identity of one execution of a parallel node, formatted as
            "{node_id}@{visit}".
        parallel_branch_id:
          oneOf:
            - $ref: "#/components/schemas/ParallelBranchId"
            - type: "null"
        session_id:
          type: ["string", "null"]
        parent_session_id:
          type: ["string", "null"]
        tool_call_id:
          type: ["string", "null"]
          description: >
            Stable identifier for a tool call, present on agent.tool.* events
            and other durable events that directly describe the same tool
            call.
        actor:
          oneOf:
            - $ref: "#/components/schemas/Principal"
            - type: "null"
        event:
          type: string
          description: Event type discriminator.
          example: stage.started
        properties:
          type: object
          additionalProperties: true
      additionalProperties: true

    AgentSessionActivatedProps:
      description: Properties for the `agent.session.activated` event.
      type: object
      required:
        - capabilities
        - visit
      properties:
        thread_id:
          type: ["string", "null"]
        provider:
          type: ["string", "null"]
        model:
          type: ["string", "null"]
        reasoning_effort:
          oneOf:
            - $ref: "#/components/schemas/ReasoningEffort"
            - type: "null"
        speed:
          oneOf:
            - $ref: "#/components/schemas/BillingSpeed"
            - type: "null"
        permission_level:
          oneOf:
            - $ref: "#/components/schemas/PermissionLevel"
            - type: "null"
        capabilities:
          type: array
          items:
            type: string
            enum: [steer]
        visit:
          type: integer
          minimum: 1

    AgentMessageProps:
      description: Properties for the `agent.message` event.
      type: object
      required:
        - text
        - model
        - billing
        - tool_call_count
        - visit
      properties:
        text:
          type: string
        model:
          $ref: "#/components/schemas/BillingModelRef"
        billing:
          $ref: "#/components/schemas/BilledTokenCounts"
        tool_call_count:
          type: integer
          minimum: 0
        visit:
          type: integer
          minimum: 1
        message:
          oneOf:
            - type: object
              additionalProperties: true
            - type: "null"
          description: Canonical replay-authoritative transcript message, when present.
        context_window:
          oneOf:
            - $ref: "#/components/schemas/StageContextWindowProjection"
            - type: "null"
          description: Latest content-free context-window projection for this agent stage.
        reasoning:
          oneOf:
            - $ref: "#/components/schemas/ReasoningOutput"
            - type: "null"
          description: Readable reasoning the provider returned with this response, if any.

    ReasoningOutput:
      description: >-
        Readable model reasoning normalized into a provider-neutral shape.
        Both members may be present for the same response, and at least one is
        present whenever the object is emitted. Opaque provider material
        (signatures, item IDs, encrypted or redacted payloads) never appears
        here.
      oneOf:
        - $ref: "#/components/schemas/ReasoningOutputWithSummary"
        - $ref: "#/components/schemas/ReasoningOutputTraceOnly"

    ReasoningOutputWithSummary:
      type: object
      required:
        - summary
      properties:
        summary:
          type: string
          description: Model-authored summary of its reasoning.
        trace:
          type: string
          description: Verbatim readable reasoning text, when the provider returns it.

    ReasoningOutputTraceOnly:
      type: object
      required:
        - trace
      not:
        required:
          - summary
      properties:
        trace:
          type: string
          description: Verbatim readable reasoning text, when the provider returns it.

    AgentToolsAvailableProps:
      description: Properties for the `agent.tools.available` event.
      type: object
      required:
        - tools
        - visit
      properties:
        tools:
          type: array
          description: Effective model-callable tools exposed to the stage session.
          items:
            $ref: "#/components/schemas/AgentToolSummary"
        visit:
          type: integer
          minimum: 1

    RunSupersededByProps:
      description: Properties for the `run.superseded_by` audit event emitted on a rewound source run after archive succeeds.
      type: object
      required:
        - new_run_id
        - target_checkpoint_ordinal
        - target_node_id
        - target_visit
      properties:
        new_run_id:
          type: string
        target_checkpoint_ordinal:
          type: integer
          minimum: 1
        target_node_id:
          type: string
        target_visit:
          type: integer
          minimum: 1

    EventSeq:
      description: Assigned sequence number component of a stored event envelope.
      type: object
      required:
        - seq
      properties:
        seq:
          type: integer
          description: Assigned event sequence number.
          example: 42

    EventEnvelope:
      description: >
        Stored event envelope with assigned sequence number. On the wire the
        envelope is flattened: seq sits alongside the RunEvent payload fields
        at the top level of the JSON object.
      allOf:
        - $ref: "#/components/schemas/EventSeq"
        - $ref: "#/components/schemas/RunEvent"

    PaginatedEventList:
      description: Paginated list of stored run events.
      type: object
      required:
        - data
        - meta
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/EventEnvelope"
        meta:
          $ref: "#/components/schemas/PaginationMeta"

    AppendEventResponse:
      description: Assigned sequence number for an appended event.
      type: object
      required:
        - seq
      properties:
        seq:
          type: integer
          description: Assigned event sequence number.
          example: 42

    BlobHash:
      description: >-
        Content-addressed SHA-256 hash of a stored blob. Hex input is case-insensitive;
        Fabro emits the canonical lowercase form.
      type: string
      pattern: "^[0-9A-Fa-f]{64}$"
      example: 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824

    WriteBlobResponse:
      description: Content-addressed hash of a stored blob.
      type: object
      required:
        - hash
      properties:
        hash:
          $ref: "#/components/schemas/BlobHash"

    CommandTermination:
      description: Terminal state for a command execution.
      type: string
      enum:
        - exited
        - timed_out
        - cancelled

    CommandLogResponse:
      description: Byte-offset command log slice.
      type: object
      required:
        - offset
        - next_offset
        - total_bytes
        - bytes_base64
        - eof
        - cas_ref
        - live_streaming
      properties:
        offset:
          type: integer
          minimum: 0
          description: Actual byte offset used for this slice.
          example: 0
        next_offset:
          type: integer
          minimum: 0
          description: Byte offset for the next tail request.
          example: 4096
        total_bytes:
          type: integer
          minimum: 0
          description: Total bytes currently available for the output log.
          example: 8192
        bytes_base64:
          type: string
          description: Base64-encoded raw log bytes.
          example: aGVsbG8K
        eof:
          type: boolean
          description: Whether the output log is finalized.
          example: false
        cas_ref:
          oneOf:
            - type: string
              pattern: '^blob://sha256/[0-9a-f]{64}$'
            - type: "null"
          description: Final CAS reference once the command has completed.
        live_streaming:
          type: boolean
          description: Whether the sandbox provided live output while the command was running.
          example: true

    ArtifactEntry:
      description: A single artifact file for a stage.
      type: object
      required:
        - filename
        - retry
        - size
      properties:
        filename:
          type: string
          description: Artifact filename.
          example: src/lib.rs
        retry:
          type: integer
          format: int32
          minimum: 0
          description: Retry attempt number.
          example: 1
        size:
          type: integer
          format: int64
          minimum: 0
          description: Artifact size in bytes.
          example: 1234

    ArtifactListResponse:
      description: List of artifact files for a stage.
      type: object
      required:
        - data
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/ArtifactEntry"

    ArtifactBatchUploadEntry:
      description: One file entry in a strict multipart artifact upload manifest.
      type: object
      required:
        - part
        - path
      properties:
        part:
          type: string
          description: Multipart field name for the file part.
          example: file1
        path:
          type: string
          description: Relative artifact path to store.
          example: src/lib.rs
        sha256:
          type: ["string", "null"]
          description: Optional SHA-256 checksum for the file contents; hex input is case-insensitive.
          example: 3f785df4c5b7d3f1f4c1f0ecb0f55f1d9f6f6a3d9f0a8a98f7a74f29d1f81a2c
        expected_bytes:
          type: ["integer", "null"]
          format: int64
          minimum: 0
          description: Optional exact byte length expected for the file part.
          example: 1234
        content_type:
          type: ["string", "null"]
          description: Optional client-supplied content type for the file part.
          example: text/plain

    ArtifactBatchUploadManifest:
      description: Manifest for strict multipart artifact uploads.
      type: object
      required:
        - entries
      properties:
        entries:
          type: array
          minItems: 1
          items:
            $ref: "#/components/schemas/ArtifactBatchUploadEntry"

    RunArtifactEntry:
      description: A captured artifact file for a run.
      type: object
      required:
        - stage_id
        - node_slug
        - retry
        - relative_path
        - size
      properties:
        stage_id:
          type: string
          description: Stage ID in `node@visit` form.
        node_slug:
          type: string
          description: Node slug that produced the artifact.
        retry:
          type: integer
          format: int32
          minimum: 0
          description: Retry attempt number.
        relative_path:
          type: string
          description: Artifact path relative to the stage artifact capture directory.
        size:
          type: integer
          format: int64
          minimum: 0
          description: Artifact size in bytes.

    RunArtifactListResponse:
      description: List of captured artifact files for a run.
      type: object
      required:
        - data
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/RunArtifactEntry"

    BlockedReason:
      description: Specific reason a run is blocked on external intervention.
      type: string
      enum:
        - human_input_required

    RunControlAction:
      description: Run control action requested by the API.
      type: string
      enum:
        - cancel
        - pause
        - unpause

    StageOutcome:
      description: Terminal execution outcome of a workflow stage.
      type: string
      enum:
        - succeeded
        - partially_succeeded
        - failed
        - skipped

    StageCompletion:
      description: Terminal completion metadata for a projected workflow stage.
      type: object
      required:
        - outcome
        - timestamp
      properties:
        outcome:
          $ref: "#/components/schemas/StageOutcome"
        notes:
          type: ["string", "null"]
        failure_reason:
          type: ["string", "null"]
        timestamp:
          type: string
          format: date-time

    StageContextWindowCategory:
      description: Category of model-visible input/context tokens.
      type: string
      enum:
        - system_prompt
        - tools
        - mcp_tools
        - skills
        - memory
        - conversation
        - other

    StageContextWindowCountMethod:
      description: Method used to produce the context-window token total and breakdown.
      type: string
      enum:
        - provider_api_scaled_breakdown
        - response_usage_scaled_breakdown
        - local_estimate

    StageContextWindowStaleness:
      description: Freshness of the returned context-window data.
      type: string
      enum:
        - live
        - stored
        - unavailable

    StageContextWindowUnavailableReason:
      description: Why context-window data is unavailable for a known run stage.
      type: string
      enum:
        - not_agent_stage
        - not_observed
        - provider_unconfigured

    StageContextWindowWarning:
      description: Content-free warning about context-window count quality or attribution.
      type: object
      required:
        - code
        - message
      properties:
        code:
          type: string
          description: Stable warning code.
          example: provider_token_count_failed
        message:
          type: string
          description: Human-readable warning that must not include prompt, memory, message, or tool-argument content.
          example: provider input token counting failed; returned local estimate

    StageContextWindowBreakdownItem:
      description: Token usage for one content category.
      type: object
      required:
        - category
        - tokens
        - usage_percent
      properties:
        category:
          $ref: "#/components/schemas/StageContextWindowCategory"
        tokens:
          type: integer
          format: uint64
          minimum: 0
          example: 30000
        usage_percent:
          type: number
          format: double
          minimum: 0
          example: 7.5

    StageContextWindowProjection:
      description: Durable content-free context-window snapshot projected onto an agent stage.
      type: object
      required:
        - provider
        - model
        - context_window_tokens
        - input_tokens
        - usage_percent
        - count_method
        - staleness
        - generated_at
        - breakdown
        - warnings
      properties:
        provider:
          type: string
          example: openai
        model:
          type: string
          example: gpt-5.4
        context_window_tokens:
          type: integer
          format: uint64
          minimum: 0
          example: 400000
        input_tokens:
          type: integer
          format: uint64
          minimum: 0
          example: 123456
        usage_percent:
          type: number
          format: double
          minimum: 0
          example: 30.86
        count_method:
          $ref: "#/components/schemas/StageContextWindowCountMethod"
        staleness:
          $ref: "#/components/schemas/StageContextWindowStaleness"
        generated_at:
          type: string
          format: date-time
          example: "2026-05-23T12:34:56Z"
        event_seq:
          type: ["integer", "null"]
          format: uint32
          minimum: 1
          example: 42
        breakdown:
          type: array
          items:
            $ref: "#/components/schemas/StageContextWindowBreakdownItem"
        warnings:
          type: array
          items:
            $ref: "#/components/schemas/StageContextWindowWarning"

    StageContextWindow:
      description: Best-effort context-window usage for one agent stage.
      type: object
      required:
        - stage_id
        - available
        - unavailable_reason
        - provider
        - model
        - context_window_tokens
        - input_tokens
        - usage_percent
        - count_method
        - staleness
        - generated_at
        - event_seq
        - breakdown
        - warnings
      properties:
        stage_id:
          type: string
          description: Stage ID in `node@visit` form.
          example: implement@1
        available:
          type: boolean
          description: Whether context-window data is available for this known stage.
        unavailable_reason:
          oneOf:
            - $ref: "#/components/schemas/StageContextWindowUnavailableReason"
            - type: "null"
        provider:
          type: ["string", "null"]
          example: openai
        model:
          type: ["string", "null"]
          example: gpt-5.4
        context_window_tokens:
          type: ["integer", "null"]
          format: uint64
          minimum: 0
          example: 400000
        input_tokens:
          type: ["integer", "null"]
          format: uint64
          minimum: 0
          example: 123456
        usage_percent:
          type: ["number", "null"]
          format: double
          minimum: 0
          example: 30.86
        count_method:
          oneOf:
            - $ref: "#/components/schemas/StageContextWindowCountMethod"
            - type: "null"
        staleness:
          $ref: "#/components/schemas/StageContextWindowStaleness"
        generated_at:
          type: ["string", "null"]
          format: date-time
          example: "2026-05-23T12:34:56Z"
        event_seq:
          type: ["integer", "null"]
          format: uint32
          minimum: 1
          example: 42
        breakdown:
          type: array
          items:
            $ref: "#/components/schemas/StageContextWindowBreakdownItem"
        warnings:
          type: array
          items:
            $ref: "#/components/schemas/StageContextWindowWarning"

    ParallelBranchResult:
      description: The outcome and isolated context updates from one parallel branch.
      type: object
      required:
        - id
        - status
        - context_updates
      properties:
        id:
          type: string
        index:
          type: integer
          minimum: 0
          description: >-
            Zero-based input item or outgoing-edge position. Absent only on
            parallel results written before indexed branch identity was added.
        item_label:
          type: string
          description: >-
            Human-readable for_each item identity, derived from name, then
            label, then the zero-based input index.
        status:
          $ref: "#/components/schemas/StageOutcome"
        context_updates:
          type: object
          additionalProperties: true

    StageProjection:
      description: Observable projection data for one workflow stage execution.
      type: object
      required:
        - first_event_seq
        - usage
        - agent_control
        - state
      properties:
        first_event_seq:
          type: integer
          format: uint32
          minimum: 1
        prompt:
          type: ["string", "null"]
        response:
          type: ["string", "null"]
        completion:
          oneOf:
            - $ref: "#/components/schemas/StageCompletion"
            - type: "null"
        provider_used:
          oneOf:
            - $ref: "#/components/schemas/StageModelUsage"
            - type: "null"
          description: Provider and model metadata recorded for the stage attempt.
        diff:
          type: ["string", "null"]
        script_invocation:
          type: ["object", "null"]
          description: Command and environment recorded when the stage script ran.
        script_timing:
          type: ["object", "null"]
          description: Wall-clock and step timing metadata for the stage script.
        parallel_results:
          type: ["array", "null"]
          items:
            $ref: "#/components/schemas/ParallelBranchResult"
          description: Ordered per-branch results produced by a parallel stage.
        parallel_branch_id:
          oneOf:
            - $ref: "#/components/schemas/ParallelBranchId"
            - type: "null"
        output:
          type: ["string", "null"]
        output_bytes:
          type: ["integer", "null"]
          minimum: 0
        live_streaming:
          type: ["boolean", "null"]
        termination:
          oneOf:
            - $ref: "#/components/schemas/CommandTermination"
            - type: "null"
        started_at:
          type: ["string", "null"]
          format: date-time
          description: Wall-clock time the latest attempt of this stage started, if known.
        timing:
          oneOf:
            - $ref: "#/components/schemas/StageTiming"
            - type: "null"
          description: |
            Per-attempt timing breakdown for the latest terminal attempt:
            wall time plus the active inference/tool breakdown. Null while the
            stage is still in flight; the live estimate is derived from
            `live_inference_ms`, `live_tool_ms`, and any open bracket.
        live_inference_ms:
          type: integer
          format: uint64
          minimum: 0
          default: 0
          description: |
            Inference time accumulated from closed brackets during the current
            attempt. Live estimate only — the authoritative value arrives with
            the terminal event and lands in `timing`. Excludes the currently
            open bracket, whose span is measured from `inference.started_at`.
          example: 78230
        live_tool_ms:
          type: integer
          format: uint64
          minimum: 0
          default: 0
          description: |
            Tool time accumulated from closed tool batches during the current
            attempt. A batch spans the first dispatched call through the
            completion that drains the last outstanding one, so tools running
            concurrently within a turn are counted once.
          example: 7588
        tool_batch:
          oneOf:
            - $ref: "#/components/schemas/StageToolBatchProjection"
            - type: "null"
          description: |
            Open tool batch: when the batch started and which calls have not
            yet reported completion.
        usage:
          $ref: "#/components/schemas/BilledTokenCounts"
        model:
          oneOf:
            - $ref: "#/components/schemas/BillingModelRef"
            - type: "null"
        todos:
          oneOf:
            - $ref: "#/components/schemas/TodoListProjection"
            - type: "null"
          description: |
            Todo / task list owned by this stage's root agent session. OpenAI
            child sessions have separate per-session plans that do not appear
            here. Anthropic task lists are root-scoped and shared with child
            sessions, so child mutations of that shared list do appear here.
        subagents:
          type: array
          description: Subagents spawned by this stage, in replay/insertion order.
          items:
            $ref: "#/components/schemas/SubAgentProjection"
        skills:
          $ref: "#/components/schemas/SkillsProjection"
          description: Agent skills discovered and activated during this stage.
        permission_level:
          oneOf:
            - $ref: "#/components/schemas/PermissionLevel"
            - type: "null"
          description: Agent tool permission level applied to this stage session.
        agent_tools:
          type: array
          description: >
            Effective model-callable tools exposed to this agent stage session.
            Tool parameter schemas are intentionally omitted from this projection.
          items:
            $ref: "#/components/schemas/AgentToolSummary"
        mcp_servers:
          type: array
          description: MCP servers observed by this stage.
          items:
            $ref: "#/components/schemas/McpServerProjection"
        context_window:
          oneOf:
            - $ref: "#/components/schemas/StageContextWindowProjection"
            - type: "null"
          description: Latest content-free context-window snapshot for this agent stage.
        inference:
          oneOf:
            - $ref: "#/components/schemas/StageInferenceProjection"
            - type: "null"
          description: >
            Open inference bracket, if the event log contains one. Present means
            a model request was dispatched and no closing event has been seen —
            not that the model is computing right now.
        acp_started_at:
          type: ["string", "null"]
          format: date-time
          description: >
            Start of an external ACP agent process, if one is running. ACP
            agents do not expose Fabro's internal LLM brackets, so the process
            lifetime supplies their live inference estimate.
        agent_control:
          $ref: "#/components/schemas/AgentControlState"
          description: Whether the agent is executing normally or waiting for steering after an interrupt.
        state:
          $ref: "#/components/schemas/StageState"
          description: Lifecycle state of the stage projection.

    StageToolBatchProjection:
      description: >
        One open tool batch: tool calls dispatched together that have not all
        reported completion. `open_call_ids` is a set rather than a count so a
        duplicated completion in a replayed log cannot drain the batch early.
      type: object
      required:
        - session_id
        - started_at
        - open_call_ids
      properties:
        session_id:
          type: string
          description: >
            Root agent session that dispatched the batch. Transitions are
            gated on it so delayed events from a replaced session cannot
            mutate the current batch.
        started_at:
          type: string
          format: date-time
          description: >
            When the batch opened — the first dispatched call observed while no
            other calls were outstanding.
        open_call_ids:
          type: array
          minItems: 1
          uniqueItems: true
          items:
            type: string
          description: Calls dispatched but not yet completed, by tool call id.

    StageInferenceProjection:
      description: >
        One open inference bracket: a dispatched LLM request that has not yet
        produced a message, error, or interrupt. Carries no usage or cost — none
        exists until the turn completes.
      type: object
      required:
        - session_id
        - started_at
        - requested_model
        - retries
      properties:
        session_id:
          type: string
          description: >
            Agent session that opened the bracket, copied from the event
            envelope. Transitions are gated on it so sub-agent rounds cannot
            overwrite the root session's bracket.
        started_at:
          type: string
          format: date-time
          description: When the request was dispatched.
        requested_model:
          $ref: "#/components/schemas/BillingModelRef"
          description: >
            Provider and model the request was sent to. Failover can re-target,
            so `StageProjection.model` stays authoritative for what answered.
        first_output_at:
          type: ["string", "null"]
          format: date-time
          description: When the provider produced its first output, if it has.
        first_output_kind:
          oneOf:
            - $ref: "#/components/schemas/LlmOutputKind"
            - type: "null"
          description: Kind of the first output observed for the current attempt.
        retries:
          type: integer
          format: uint32
          minimum: 0
          description: Attempts that failed and restarted within this bracket.

    LlmOutputKind:
      description: >
        Kind of output a provider produced first for an inference attempt.
        Observed, never inferred.
      type: string
      enum:
        - reasoning
        - text
        - tool_call

    SubAgentProjection:
      description: Current projected state for one subagent spawned by an agent stage.
      type: object
      required:
        - agent_id
        - depth
        - task
        - status
      properties:
        agent_id:
          type: string
        depth:
          type: integer
          minimum: 0
        task:
          type: string
        status:
          $ref: "#/components/schemas/SubAgentStatus"

    SubAgentStatus:
      description: Projected lifecycle status for a subagent.
      oneOf:
        - $ref: "#/components/schemas/SubAgentStatusRunning"
        - $ref: "#/components/schemas/SubAgentStatusCompleted"
        - $ref: "#/components/schemas/SubAgentStatusFailed"
        - $ref: "#/components/schemas/SubAgentStatusClosed"
      discriminator:
        propertyName: kind
        mapping:
          running: "#/components/schemas/SubAgentStatusRunning"
          completed: "#/components/schemas/SubAgentStatusCompleted"
          failed: "#/components/schemas/SubAgentStatusFailed"
          closed: "#/components/schemas/SubAgentStatusClosed"

    SubAgentStatusRunning:
      type: object
      required:
        - kind
      properties:
        kind:
          type: string
          enum: [running]

    SubAgentStatusCompleted:
      type: object
      required:
        - kind
        - success
        - turns_used
      properties:
        kind:
          type: string
          enum: [completed]
        success:
          type: boolean
        turns_used:
          type: integer
          minimum: 0

    SubAgentStatusFailed:
      type: object
      required:
        - kind
        - error
      properties:
        kind:
          type: string
          enum: [failed]
        error:
          description: Provider/tool error payload captured by the subagent event.

    SubAgentStatusClosed:
      type: object
      required:
        - kind
      properties:
        kind:
          type: string
          enum: [closed]

    SkillsProjection:
      description: Agent skills discovered and activated during a stage.
      type: object
      required:
        - available
        - activated
      properties:
        available:
          type: array
          items:
            $ref: "#/components/schemas/AgentSkillSummary"
        activated:
          type: array
          items:
            $ref: "#/components/schemas/ActivatedSkill"

    AgentSkillSummary:
      description: Summary of an available agent skill.
      type: object
      required:
        - name
        - description
      properties:
        name:
          type: string
        description:
          type: string

    ActivatedSkill:
      description: One observed agent skill activation.
      type: object
      required:
        - name
        - source
      properties:
        name:
          type: string
        source:
          $ref: "#/components/schemas/AgentSkillActivationSource"

    AgentSkillActivationSource:
      description: Source that activated an agent skill.
      type: string
      enum: [slash, tool]

    AgentToolSummary:
      description: Summary of one effective model-callable tool exposed to an agent stage.
      type: object
      required:
        - name
        - description
        - source
        - category
        - invoked
      properties:
        name:
          type: string
          description: Exposed model-facing tool name, for example `apply_patch` or `mcp__filesystem__read_file`.
        description:
          type: string
          description: Model-facing tool description.
        source:
          $ref: "#/components/schemas/AgentToolSource"
        category:
          $ref: "#/components/schemas/AgentToolCategory"
        invoked:
          type: boolean
          description: True once this tool has been invoked during the stage.

    AgentToolSource:
      description: Origin of an effective agent tool.
      oneOf:
        - $ref: "#/components/schemas/AgentToolSourceNative"
        - $ref: "#/components/schemas/AgentToolSourceMcp"
        - $ref: "#/components/schemas/AgentToolSourceSkill"
      discriminator:
        propertyName: kind
        mapping:
          native: "#/components/schemas/AgentToolSourceNative"
          mcp: "#/components/schemas/AgentToolSourceMcp"
          skill: "#/components/schemas/AgentToolSourceSkill"

    AgentToolSourceNative:
      type: object
      required:
        - kind
      properties:
        kind:
          type: string
          enum: [native]

    AgentToolSourceMcp:
      type: object
      required:
        - kind
        - server_name
        - original_name
      properties:
        kind:
          type: string
          enum: [mcp]
        server_name:
          type: string
          description: MCP server name that provided the tool.
        original_name:
          type: string
          description: Tool name before MCP qualification.

    AgentToolSourceSkill:
      type: object
      required:
        - kind
      properties:
        kind:
          type: string
          enum: [skill]

    AgentToolCategory:
      description: Coarse tool category for display and grouping.
      type: string
      enum: [read, write, shell, subagent, other]

    McpServerProjection:
      description: Projected state for one MCP server observed by an agent stage.
      type: object
      required:
        - server_name
        - tool_count
        - status
        - invoked
      properties:
        server_name:
          type: string
        tool_count:
          type: integer
          minimum: 0
        status:
          $ref: "#/components/schemas/McpServerStatus"
        invoked:
          type: boolean
          description: True once the agent has invoked at least one tool from this server during the stage.

    McpServerStatus:
      description: Projected MCP server readiness status.
      oneOf:
        - $ref: "#/components/schemas/McpServerStatusReady"
        - $ref: "#/components/schemas/McpServerStatusFailed"
      discriminator:
        propertyName: kind
        mapping:
          ready: "#/components/schemas/McpServerStatusReady"
          failed: "#/components/schemas/McpServerStatusFailed"

    McpServerStatusReady:
      type: object
      required:
        - kind
        - tools
      properties:
        kind:
          type: string
          enum: [ready]
        tools:
          type: array
          items:
            $ref: "#/components/schemas/AgentMcpToolSummary"

    McpServerStatusFailed:
      type: object
      required:
        - kind
        - error
      properties:
        kind:
          type: string
          enum: [failed]
        error:
          type: string

    AgentMcpToolSummary:
      description: Summary of one tool exposed by an MCP server.
      type: object
      required:
        - name
        - original_name
      properties:
        name:
          type: string
        original_name:
          type: string

    StageModelUsage:
      description: Provider, model, and request-control metadata recorded for a stage attempt.
      type: object
      required:
        - mode
      properties:
        mode:
          type: string
          description: Source of the stage's model usage metadata.
          example: agent
        provider:
          type: ["string", "null"]
          example: openai
        model:
          type: ["string", "null"]
          example: gpt-5.5
        reasoning_effort:
          oneOf:
            - $ref: "#/components/schemas/ReasoningEffort"
            - type: "null"
        speed:
          oneOf:
            - $ref: "#/components/schemas/BillingSpeed"
            - type: "null"

    InterviewOption:
      description: Option stored with an interview question in the event log.
      type: object
      required:
        - key
        - label
      properties:
        key:
          type: string
          description: Machine-readable option key used when submitting an answer.
        label:
          type: string
          description: Human-readable label displayed to the user.
        description:
          type: ["string", "null"]
          description: Optional untrusted model-authored option description for display.
        preview:
          type: ["string", "null"]
          description: Optional untrusted model-authored option preview captured for clients.

    ReviewTargetKind:
      description: The type of resource presented for human review.
      type: string
      enum:
        - document

    ReviewTarget:
      description: A validated external resource presented as the primary subject of a human review question.
      type: object
      required:
        - label
        - url
        - kind
      properties:
        label:
          type: string
          minLength: 1
          maxLength: 200
          description: Human-readable link label.
          example: Quarry review exercise
        url:
          type: string
          format: uri
          minLength: 1
          maxLength: 2048
          description: Absolute HTTP or HTTPS URL opened by the reviewer.
          example: https://quarry.lithos.computer/tmp/0123456789abcdef0123456789abcdef
        kind:
          $ref: "#/components/schemas/ReviewTargetKind"

    InterviewQuestionRecord:
      description: Storage shape of an interview question recorded in the event log.
      type: object
      required:
        - id
        - text
        - stage
        - question_type
        - allow_freeform
      properties:
        id:
          type: string
        text:
          type: string
        stage:
          type: string
        question_type:
          $ref: "#/components/schemas/QuestionType"
        options:
          type: array
          items:
            $ref: "#/components/schemas/InterviewOption"
        allow_freeform:
          type: boolean
        timeout_seconds:
          type: ["number", "null"]
          format: double
        context_display:
          type: ["string", "null"]
        review_target:
          oneOf:
            - $ref: "#/components/schemas/ReviewTarget"
            - type: "null"

    PendingInterviewRecord:
      description: Pending interview question plus the time it entered the unresolved set.
      type: object
      required:
        - question
        - started_at
      properties:
        question:
          $ref: "#/components/schemas/InterviewQuestionRecord"
        started_at:
          type: string
          format: date-time

    DirtyStatus:
      type: string
      enum:
        - clean
        - dirty
        - unknown

    ForkSourceRef:
      description: Source checkpoint used to initialize a forked or rewound run.
      type: object
      required:
        - source_run_id
        - checkpoint_sha
      properties:
        source_run_id:
          type: string
        checkpoint_sha:
          type: string

    RunSpec:
      description: Durable workflow run specification reconstructed from run.created events.
      type: object
      required:
        - run_id
        - settings
        - graph
        - provenance
      properties:
        run_id:
          type: string
        settings:
          $ref: "#/components/schemas/WorkflowSettings"
        graph:
          type: object
          additionalProperties: true
        graph_source:
          type: ["string", "null"]
        workflow_slug:
          type: ["string", "null"]
        automation:
          oneOf:
            - $ref: "#/components/schemas/AutomationRef"
            - type: "null"
        source_directory:
          type: ["string", "null"]
        labels:
          type: object
          additionalProperties:
            type: string
        provenance:
          $ref: "#/components/schemas/RunProvenance"
        manifest_blob:
          type: ["string", "null"]
        definition_blob:
          type: ["string", "null"]
        spec_blob:
          type: ["string", "null"]
        git:
          oneOf:
            - $ref: "#/components/schemas/GitContext"
            - type: "null"
        fork_source_ref:
          oneOf:
            - $ref: "#/components/schemas/ForkSourceRef"
            - type: "null"

    UpdateRunParentRequest:
      type: object
      required:
        - parent_id
      properties:
        parent_id:
          type: string
          description: Existing orchestration parent run ID.

    StartRecord:
      description: Metadata captured when execution starts.
      type: object
      required:
        - start_time
      properties:
        start_time:
          type: string
          format: date-time
        run_branch:
          type: ["string", "null"]
        base_sha:
          type: ["string", "null"]

    StageSummary:
      description: Terminal summary for one stage in a run conclusion.
      type: object
      required:
        - stage_id
        - stage_label
        - timing
        - retries
      properties:
        stage_id:
          type: string
        stage_label:
          type: string
        timing:
          $ref: "#/components/schemas/StageTiming"
        billing_usd_micros:
          type: ["integer", "null"]
          format: int64
        retries:
          type: integer
          format: uint32
          minimum: 0

    Conclusion:
      description: Terminal run conclusion derived from final workflow execution.
      type: object
      required:
        - timestamp
        - status
        - timing
        - stages
        - total_retries
        - diff
      properties:
        timestamp:
          type: string
          format: date-time
        status:
          $ref: "#/components/schemas/StageOutcome"
        timing:
          $ref: "#/components/schemas/RunTiming"
        failure:
          oneOf:
            - $ref: "#/components/schemas/RunFailure"
            - type: "null"
        final_git_commit_sha:
          type: ["string", "null"]
        stages:
          type: array
          items:
            $ref: "#/components/schemas/StageSummary"
        billing:
          oneOf:
            - $ref: "#/components/schemas/BilledTokenCounts"
            - type: "null"
        total_retries:
          type: integer
          format: uint32
          minimum: 0
        diff:
          $ref: "#/components/schemas/RunDiff"

    CheckpointRecord:
      description: Sequence-tagged checkpoint history entry with the diff observed at that checkpoint.
      type: object
      required:
        - seq
        - checkpoint
        - diff
      properties:
        seq:
          type: integer
          format: uint32
        checkpoint:
          $ref: "#/components/schemas/RunCheckpoint"
        diff:
          $ref: "#/components/schemas/RunDiff"

    RunProjection:
      description: Raw internal run projection derived from the event log.
      type: object
      required:
        - spec
        - status
        - status_updated_at
        - last_event_at
        - checkpoints
        - pending_interviews
        - stages
      properties:
        title:
          type: string
          description: Resolved run title from the event log.
        parent_id:
          type: ["string", "null"]
          description: Current orchestration parent run ID, if linked.
        spec:
          $ref: "#/components/schemas/RunSpec"
        web_url:
          type: ["string", "null"]
          description: Absolute web UI URL for this run when server web settings are configured.
        start:
          oneOf:
            - $ref: "#/components/schemas/StartRecord"
            - type: "null"
        status:
          $ref: "#/components/schemas/RunStatus"
        archived_at:
          type: ["string", "null"]
          format: date-time
        status_updated_at:
          type: string
          format: date-time
        last_event_at:
          type: string
          format: date-time
        pending_control:
          oneOf:
            - $ref: "#/components/schemas/RunControlAction"
            - type: "null"
        checkpoints:
          type: array
          description: Sequence-tagged checkpoint history entries.
          items:
            $ref: "#/components/schemas/CheckpointRecord"
        conclusion:
          oneOf:
            - $ref: "#/components/schemas/Conclusion"
            - type: "null"
        sandbox:
          oneOf:
            - $ref: "#/components/schemas/RunSandbox"
            - type: "null"
        pull_request:
          oneOf:
            - $ref: "#/components/schemas/PullRequestLink"
            - type: "null"
        pull_request_creation:
          oneOf:
            - $ref: "#/components/schemas/PullRequestCreation"
            - type: "null"
        superseded_by:
          type: ["string", "null"]
        retried_from:
          type: ["string", "null"]
          description: Source run ID when this run was created by manual retry.
        pending_interviews:
          type: object
          additionalProperties:
            $ref: "#/components/schemas/PendingInterviewRecord"
        stages:
          type: object
          description: Map from StageId (`node_id@visit`) to stage projection data.
          additionalProperties:
            $ref: "#/components/schemas/StageProjection"

    TodoStatus:
      type: string
      enum: [pending, in_progress, completed, deleted]
      description: |-
        Lifecycle for a todo / task. `deleted` is reachable for Anthropic
        tasks; once observed, the todo is removed from the projection.

    TodoListKind:
      type: string
      enum: [openai_plan, anthropic_tasks, kimi_todos]
      description: |-
        Tool surface a todo list belongs to. Determines the `list_id`
        prefix and scoping convention.

    TodoProjection:
      type: object
      description: One projected todo / task item.
      required:
        - id
        - status
        - order
        - subject
      properties:
        id:
          type: string
        status:
          $ref: "#/components/schemas/TodoStatus"
        order:
          type: integer
          format: uint32
          minimum: 0
        subject:
          type: string
        description:
          type: string
          default: ""
        active_form:
          type: ["string", "null"]
        owner:
          type: ["string", "null"]
        blocks:
          type: array
          items:
            type: string
        blocked_by:
          type: array
          items:
            type: string
        metadata:
          type: object
          additionalProperties: true

    TodoListProjection:
      type: object
      description: All currently-projected todos for one `list_id`.
      required:
        - kind
        - list_id
      properties:
        kind:
          $ref: "#/components/schemas/TodoListKind"
        list_id:
          type: string
        items:
          type: array
          items:
            $ref: "#/components/schemas/TodoProjection"

    Run:
      description: Canonical public run shape.
      type: object
      required:
        - id
        - title
        - goal
        - workflow
        - automation
        - repository
        - created_by
        - origin
        - labels
        - lifecycle
        - sandbox
        - models
        - source_directory
        - timestamps
        - timing
        - billing
        - size
        - ask_fabro
        - diff
        - pull_request
        - current_question
        - superseded_by
        - retried_from
        - links
        - children_count
      properties:
        id:
          type: string
        parent_id:
          type: ["string", "null"]
          description: Current orchestration parent run ID, if linked.
        children_count:
          type: integer
          format: uint64
          minimum: 0
          description: Number of runs currently linked to this run as their orchestration parent.
        title:
          type: string
        goal:
          type: string
        workflow:
          $ref: "#/components/schemas/WorkflowRef"
        automation:
          oneOf:
            - $ref: "#/components/schemas/AutomationRef"
            - type: "null"
        repository:
          oneOf:
            - $ref: "#/components/schemas/RepositoryRef"
            - type: "null"
        created_by:
          $ref: "#/components/schemas/Principal"
        origin:
          $ref: "#/components/schemas/RunOrigin"
        labels:
          type: object
          additionalProperties:
            type: string
        lifecycle:
          $ref: "#/components/schemas/RunLifecycle"
        sandbox:
          oneOf:
            - $ref: "#/components/schemas/RunSandbox"
            - type: "null"
        models:
          type: array
          items:
            $ref: "#/components/schemas/RunModel"
        source_directory:
          type: ["string", "null"]
        timestamps:
          $ref: "#/components/schemas/RunTimestamps"
        timing:
          oneOf:
            - $ref: "#/components/schemas/RunTiming"
            - type: "null"
          description: |
            Run-level timing rollup. Wall time is the run's clock duration;
            active timing sums work across stage visits.
        billing:
          oneOf:
            - $ref: "#/components/schemas/RunBillingSummary"
            - type: "null"
        size:
          $ref: "#/components/schemas/RunSize"
        ask_fabro:
          $ref: "#/components/schemas/AskFabro"
        diff:
          oneOf:
            - $ref: "#/components/schemas/DiffSummary"
            - type: "null"
        pull_request:
          oneOf:
            - $ref: "#/components/schemas/PullRequestLink"
            - type: "null"
        current_question:
          oneOf:
            - $ref: "#/components/schemas/RunQuestion"
            - type: "null"
        superseded_by:
          type: ["string", "null"]
          description: Run ID that superseded this run via rewind, if any.
        retried_from:
          type: ["string", "null"]
          description: Source run ID when this run was created by manual retry.
        links:
          $ref: "#/components/schemas/RunLinks"

    AskFabro:
      description: Readiness and defaults for starting an Ask Fabro session on this run.
      type: object
      required:
        - available
        - unavailable_reason
        - default_model
      properties:
        available:
          type: boolean
        unavailable_reason:
          type: ["string", "null"]
          enum:
            - no_sandbox
            - sandbox_not_ready
            - llm_unconfigured
            - null
        default_model:
          type: ["string", "null"]

    WorkflowRef:
      type: object
      required: [slug, name, graph_name, node_count, edge_count]
      properties:
        slug:
          type: ["string", "null"]
        name:
          type: ["string", "null"]
        graph_name:
          type: ["string", "null"]
        node_count:
          type: integer
          format: int64
          description: Number of nodes in the workflow graph.
        edge_count:
          type: integer
          format: int64
          description: Number of edges in the workflow graph.

    AutomationRef:
      type: object
      required: [id, name]
      properties:
        id:
          type: string
        name:
          type: ["string", "null"]
        trigger_id:
          type: ["string", "null"]

    RunOrigin:
      type: object
      required: [kind]
      properties:
        kind:
          type: string
          enum: [api]

    RunLifecycle:
      type: object
      required: [status, approval, pending_control, queue_position, error, archived, archived_at]
      properties:
        status:
          $ref: "#/components/schemas/RunStatus"
        approval:
          oneOf:
            - $ref: "#/components/schemas/RunApproval"
            - type: "null"
        pending_control:
          oneOf:
            - $ref: "#/components/schemas/RunControlAction"
            - type: "null"
        queue_position:
          type: ["integer", "null"]
        error:
          oneOf:
            - $ref: "#/components/schemas/RunError"
            - type: "null"
        archived:
          type: boolean
        archived_at:
          type: ["string", "null"]
          format: date-time

    RunApproval:
      description: Pre-execution approval state for runs that require one-time human approval.
      type: object
      required: [state, requested_at, decided_at, denial_reason]
      properties:
        state:
          $ref: "#/components/schemas/RunApprovalState"
        requested_at:
          type: string
          format: date-time
        decided_at:
          type: ["string", "null"]
          format: date-time
        denial_reason:
          type: ["string", "null"]

    RunApprovalState:
      description: State of a run's pre-execution approval request.
      type: string
      enum:
        - pending
        - approved
        - denied

    RunModel:
      type: object
      required: [provider, name]
      properties:
        provider:
          type: ["string", "null"]
        name:
          type: string

    RunTimestamps:
      type: object
      required: [created_at, started_at, last_event_at, completed_at]
      properties:
        created_at:
          type: string
          format: date-time
        started_at:
          type: ["string", "null"]
          format: date-time
        last_event_at:
          type: ["string", "null"]
          format: date-time
        completed_at:
          type: ["string", "null"]
          format: date-time

    RunBillingSummary:
      type: object
      required: [total_usd_micros]
      properties:
        total_usd_micros:
          type: ["integer", "null"]
          format: int64

    RunSize:
      type: string
      enum: [XS, S, M, L, XL]
      description: Run size bucket derived from current best-effort billed usage.

    RunLinks:
      type: object
      required: [web]
      properties:
        web:
          type: ["string", "null"]
          format: uri

    ForkRequest:
      description: Request body for creating a new run from a source run checkpoint.
      type: object
      properties:
        target:
          type: ["string", "null"]
          description: Optional checkpoint target such as `@2`, `build`, or `build@1`. Defaults to the latest checkpoint.

    ForkResponse:
      description: Response returned after creating a forked run.
      type: object
      required:
        - source_run_id
        - new_run_id
        - target
      properties:
        source_run_id:
          type: string
        new_run_id:
          type: string
        target:
          type: string

    RewindRequest:
      description: Request body for creating a replacement run from a source run checkpoint.
      type: object
      properties:
        target:
          type: ["string", "null"]
          description: Optional checkpoint target such as `@2`, `build`, or `build@1`. Defaults to the latest checkpoint.

    RewindResponse:
      description: Response returned after rewind creates a new run.
      type: object
      required:
        - source_run_id
        - new_run_id
        - target
        - archived
      properties:
        source_run_id:
          type: string
        new_run_id:
          type: string
        target:
          type: string
        archived:
          type: boolean
        archive_error:
          type: ["string", "null"]

    TimelineEntryResponse:
      description: Checkpoint timeline entry for a run.
      type: object
      required:
        - ordinal
        - node_name
        - visit
        - checkpoint_seq
      properties:
        ordinal:
          type: integer
          minimum: 1
        node_name:
          type: string
        visit:
          type: integer
          minimum: 1
        checkpoint_seq:
          type: integer
          minimum: 1
        run_commit_sha:
          type: ["string", "null"]

    # ── Run Board Schemas ────────────────────────────────────────────────

    BoardColumn:
      description: |
        Status bucket for a run, shared by list and kanban renderings and by
        the `status` query parameter on `GET /api/v1/runs`. The `archived`
        bucket is orthogonal to the `include_archived` flag — passing
        `status=archived` is equivalent to opting archived runs in.
      type: string
      enum:
        - pending
        - runnable
        - initializing
        - running
        - blocked
        - succeeded
        - failed
        - archived
        - removing

    CheckRunStatus:
      description: Status of a CI check run.
      type: string
      enum:
        - success
        - failure
        - skipped
        - pending
        - queued

    CheckRun:
      description: A CI check run result associated with a run's pull request.
      type: object
      required:
        - name
        - status
      properties:
        name:
          type: string
          description: Name of the CI check.
          example: unit-tests
        status:
          $ref: "#/components/schemas/CheckRunStatus"
        wall_time_ms:
          type: integer
          format: uint64
          minimum: 0
          description: Wall-clock duration of the check run in milliseconds.
          example: 154000

    # ── Reusable Sub-Schemas ───────────────────────────────────────────

    ModelReference:
      description: Reference to a model by its identifier.
      type: object
      required:
        - id
      properties:
        id:
          type: string
          description: Model identifier.
          example: claude-opus-4-6

    WorkflowReference:
      description: Reference to a workflow by its slug.
      type: object
      required:
        - slug
      properties:
        slug:
          type: string
          description: URL-safe workflow slug.
          example: implement

    RunReference:
      description: Reference to a run with its title.
      type: object
      required:
        - id
        - title
      properties:
        id:
          type: string
          description: Unique run identifier.
          example: run-047
        title:
          type: string
          description: Human-readable run title.
          example: "PR #312 — Add OAuth2 PKCE flow"

    RepositoryRef:
      description: Durable repository metadata for a run.
      type: object
      required:
        - name
        - origin_url
        - provider
      properties:
        name:
          type: string
          example: fabro-sh/fabro
        origin_url:
          type: ["string", "null"]
          example: https://github.com/fabro-sh/fabro.git
        provider:
          type: string
          enum: [github, git, unknown]

    BilledTokenCounts:
      description: Token counts with optional billed USD micros totals.
      type: object
      required:
        - input_tokens
        - output_tokens
        - total_tokens
        - reasoning_tokens
        - cache_read_tokens
        - cache_write_tokens
      properties:
        input_tokens:
          type: integer
          format: int64
          description: Number of input tokens consumed.
          example: 28640
        output_tokens:
          type: integer
          format: int64
          description: Number of output tokens generated.
          example: 8750
        total_tokens:
          type: integer
          format: int64
          description: Total billable tokens aggregated across categories.
          example: 37390
        reasoning_tokens:
          type: integer
          format: int64
          description: Number of reasoning tokens.
          example: 1200
        cache_read_tokens:
          type: integer
          format: int64
          description: Number of cache read tokens.
          example: 4800
        cache_write_tokens:
          type: integer
          format: int64
          description: Number of cache write tokens.
          example: 1500
        total_usd_micros:
          type: ["integer", "null"]
          format: int64
          description: Billed USD amount in micros.
          example: 720000

    BillingModelRef:
      description: Provider-qualified billing model identity used for cost estimates.
      type: object
      required:
        - provider
        - model_id
      properties:
        provider:
          $ref: "#/components/schemas/ProviderId"
        model_id:
          type: string
        speed:
          oneOf:
            - $ref: "#/components/schemas/BillingSpeed"
            - type: "null"

    BillingSpeed:
      description: Optional provider-specific model speed tier used for cost estimates.
      type: string
      enum:
        - standard
        - fast

    CodeLocation:
      description: A file and line location in the codebase.
      type: object
      required:
        - file
      properties:
        file:
          type: string
          description: File path.
          example: src/middleware/rate-limit.ts
        line:
          type: integer
          description: Line number in the file.
          example: 42

    RunError:
      description: Error information for a failed run.
      type: object
      required:
        - message
      properties:
        message:
          type: string
          description: Error message.
          example: "Stage 'apply-changes' exceeded maximum retries."

    PullRequestLink:
      description: Minimal GitHub pull request link associated with a run.
      type: object
      required:
        - owner
        - repo
        - number
        - html_url
      properties:
        owner:
          type: string
          example: fabro-sh
        repo:
          type: string
          example: fabro
        number:
          type: integer
          example: 123
        html_url:
          type: string
          format: uri
          description: Computed GitHub web URL for the pull request.
          example: https://github.com/fabro-sh/fabro/pull/123

    PullRequest:
      description: Stored pull request link plus optional live GitHub details.
      type: object
      required:
        - link
      properties:
        link:
          $ref: "#/components/schemas/PullRequestLink"
        details:
          oneOf:
            - $ref: "#/components/schemas/PullRequestDetails"
            - type: "null"

    PullRequestMeta:
      description: Metadata for live GitHub detail retrieval.
      type: object
      required:
        - details_status
      properties:
        details_status:
          $ref: "#/components/schemas/PullRequestDetailsStatus"
        details_unavailable_reason:
          oneOf:
            - $ref: "#/components/schemas/PullRequestDetailsUnavailableReason"
            - type: "null"

    PullRequestResponse:
      description: Pull request link and optional live GitHub details for a run.
      type: object
      required:
        - data
        - meta
      properties:
        data:
          $ref: "#/components/schemas/PullRequest"
        meta:
          $ref: "#/components/schemas/PullRequestMeta"

    PullRequestDetailsStatus:
      description: Whether live GitHub pull request details are present.
      type: string
      enum:
        - available
        - unavailable

    PullRequestDetailsUnavailableReason:
      description: Why live GitHub pull request details are unavailable.
      type: string
      enum:
        - integration_unavailable
        - not_found
        - fetch_failed

    PullRequestUser:
      description: GitHub user summary for a pull request.
      type: object
      required:
        - login
      properties:
        login:
          type: string
          example: octocat

    PullRequestRef:
      description: Git reference summary for a pull request.
      type: object
      required:
        - ref
      properties:
        ref:
          type: string
          example: fabro/run/demo

    PullRequestDetails:
      description: Live pull request fields retrieved successfully from GitHub.
      type: object
      required:
        - title
        - state
        - draft
        - merged
        - additions
        - deletions
        - changed_files
        - author
        - head_branch
        - base_branch
        - timestamps
      properties:
        title:
          type: string
          example: Move PR commands server-side
        body:
          type: ["string", "null"]
          example: Detailed description
        state:
          type: string
          example: open
        draft:
          type: boolean
          example: false
        merged:
          type: boolean
          example: false
        merged_at:
          type: ["string", "null"]
          format: date-time
          example: "2026-04-23T15:45:00Z"
        mergeable:
          type: ["boolean", "null"]
          example: true
        additions:
          type: integer
          example: 234
        deletions:
          type: integer
          example: 67
        changed_files:
          type: integer
          example: 5
        author:
          $ref: "#/components/schemas/PullRequestUser"
        head_branch:
          type: string
          example: fabro/run/demo
        base_branch:
          type: string
          example: main
        timestamps:
          type: object
          required: [created_at, updated_at]
          properties:
            created_at:
              type: string
              format: date-time
            updated_at:
              type: string
              format: date-time

    CreateRunPullRequestRequest:
      description: Request body for creating a run pull request.
      type: object
      required:
        - force
      properties:
        force:
          type: boolean
          description: Create the pull request even if the run did not finish with succeeded or partially_succeeded.
          example: false
        model:
          type: ["string", "null"]
          description: Optional model override for generating the pull request description.
          example: claude-sonnet-4-6

    PullRequestCreationId:
      description: Stable identifier for one explicit pull request creation request.
      type: string
      example: 01KYYK70WTZT2E551P3H5P0059

    PullRequestCreationStatus:
      description: Durable state of a pull request creation request.
      type: string
      enum:
        - pending
        - succeeded
        - failed

    PullRequestCreation:
      description: Durable status for the latest explicit pull request creation requested for a run.
      type: object
      required:
        - id
        - status
        - model
        - force
        - requested_at
        - updated_at
      properties:
        id:
          $ref: "#/components/schemas/PullRequestCreationId"
        status:
          $ref: "#/components/schemas/PullRequestCreationStatus"
        model:
          type: string
          description: Resolved model identifier used to generate the pull request content.
        force:
          type: boolean
          description: Whether creation was allowed for a run without a successful conclusion.
        requested_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        pull_request:
          oneOf:
            - $ref: "#/components/schemas/PullRequestLink"
            - type: "null"
        error:
          type: ["string", "null"]

    LinkRunPullRequestRequest:
      description: Request body for linking an existing GitHub pull request to a run.
      type: object
      required:
        - html_url
      properties:
        html_url:
          type: string
          format: uri
          description: GitHub pull request URL to associate with the run. Must use the form `https://github.com/{owner}/{repo}/pull/{number}`.
          example: https://github.com/fabro-sh/fabro/pull/123

    MergeMethod:
      description: GitHub merge method for a pull request.
      type: string
      enum:
        - merge
        - squash
        - rebase

    MergeRunPullRequestRequest:
      description: Request body for merging a run pull request.
      type: object
      required:
        - method
      properties:
        method:
          $ref: "#/components/schemas/MergeMethod"

    MergeRunPullRequestResponse:
      description: Response body for merging a run pull request.
      type: object
      required:
        - number
        - html_url
        - method
      properties:
        number:
          type: integer
          example: 123
        html_url:
          type: string
          format: uri
          example: https://github.com/fabro-sh/fabro/pull/123
        method:
          $ref: "#/components/schemas/MergeMethod"

    CloseRunPullRequestResponse:
      description: Response body for closing a run pull request.
      type: object
      required:
        - number
        - html_url
      properties:
        number:
          type: integer
          example: 123
        html_url:
          type: string
          format: uri
          example: https://github.com/fabro-sh/fabro/pull/123

    StageTiming:
      description: |
        Timing breakdown for one stage visit. Fields are all milliseconds.
        `wall_time_ms` is elapsed clock time; `inference_time_ms` is Fabro-
        observed LLM request/stream elapsed time; `tool_time_ms` is tool or
        command execution elapsed time; `active_time_ms` equals
        `inference_time_ms + tool_time_ms`.

        For a terminal stage these come from the worker's own stopwatch and are
        authoritative. For a stage still in flight they are a live estimate
        reconstructed from the event log, and `active_time_ms` is clamped to
        `wall_time_ms`. The estimate is replaced by the authoritative
        breakdown when the stage reaches a terminal event.
      type: object
      required:
        - wall_time_ms
        - inference_time_ms
        - tool_time_ms
        - active_time_ms
      properties:
        wall_time_ms:
          type: integer
          format: uint64
          minimum: 0
          example: 1500
        inference_time_ms:
          type: integer
          format: uint64
          minimum: 0
          default: 0
          example: 900
        tool_time_ms:
          type: integer
          format: uint64
          minimum: 0
          default: 0
          example: 200
        active_time_ms:
          type: integer
          format: uint64
          minimum: 0
          description: Equals `inference_time_ms + tool_time_ms`.
          example: 1100

    RunTiming:
      description: |
        Timing rollup for an entire run. Active fields sum work across stage
        visits, so `active_time_ms` can exceed `wall_time_ms` when parallel
        branches run concurrently.

        For a running run, stages still in flight contribute a live estimate
        rather than nothing, so wall and active both advance continuously.
        Unlike `StageTiming`, active is not clamped to wall here — concurrent
        branches can legitimately sum past run wall time.
      type: object
      required:
        - wall_time_ms
        - inference_time_ms
        - tool_time_ms
        - active_time_ms
      properties:
        wall_time_ms:
          type: integer
          format: uint64
          minimum: 0
          example: 420000
        inference_time_ms:
          type: integer
          format: uint64
          minimum: 0
          default: 0
          example: 120000
        tool_time_ms:
          type: integer
          format: uint64
          minimum: 0
          default: 0
          example: 60000
        active_time_ms:
          type: integer
          format: uint64
          minimum: 0
          description: Equals `inference_time_ms + tool_time_ms`.
          example: 180000

    SandboxProviderKind:
      description: Sandbox provider discriminator.
      type: string
      enum:
        - local
        - docker
        - daytona

    RunSandboxKind:
      description: Lifecycle state for a run sandbox request.
      type: string
      enum:
        - planned
        - initializing
        - ready
        - failed

    RunSandboxPlan:
      description: Requested sandbox provider and base image/snapshot from run settings.
      type: object
      required:
        - provider
      properties:
        provider:
          $ref: "#/components/schemas/SandboxProviderKind"
        image:
          type: ["string", "null"]
        snapshot:
          type: ["string", "null"]

    RunSandbox:
      description: Sandbox lifecycle record for a run. A run can have a requested sandbox plan before it has an initialized sandbox instance.
      type: object
      required:
        - kind
        - plan
      properties:
        kind:
          $ref: "#/components/schemas/RunSandboxKind"
        plan:
          $ref: "#/components/schemas/RunSandboxPlan"
        instance:
          oneOf:
            - $ref: "#/components/schemas/RunSandboxInstance"
            - type: "null"
          description: Present only when `kind` is `ready`.
        failure:
          oneOf:
            - $ref: "#/components/schemas/RunSandboxFailure"
            - type: "null"
          description: Present only when `kind` is `failed`.

    RunSandboxInstance:
      description: Initialized sandbox provider and runtime metadata.
      type: object
      required:
        - provider
        - runtime
      properties:
        provider:
          $ref: "#/components/schemas/SandboxProviderKind"
        image:
          type: ["string", "null"]
        snapshot:
          type: ["string", "null"]
        runtime:
          $ref: "#/components/schemas/RunSandboxRuntime"

    RunSandboxFailure:
      description: Sandbox initialization failure details.
      type: object
      required:
        - provider
        - error
        - causes
        - duration_ms
      properties:
        provider:
          type: string
          description: Provider reported by the sandbox initialization event.
        error:
          type: string
        causes:
          type: array
          items:
            type: string
        duration_ms:
          type: integer
          format: uint64
          minimum: 0

    RunSandboxRuntime:
      type: object
      required:
        - id
        - working_directory
        - repo_cloned
        - clone_origin_url
        - clone_branch
      properties:
        id:
          type: string
        working_directory:
          type: string
        repo_cloned:
          type: ["boolean", "null"]
        clone_origin_url:
          type: ["string", "null"]
        clone_branch:
          type: ["string", "null"]
        workspace_root:
          type: ["string", "null"]
        repos_root:
          type: ["string", "null"]
        primary_repo_path:
          type: ["string", "null"]
        primary_repo_link:
          type: ["string", "null"]

    RunQuestion:
      description: A pending human-in-the-loop question summary.
      type: object
      required:
        - text
      properties:
        text:
          type: string
          description: Question text.
          example: Accept or push for another round?

    AggregateBillingTotals:
      description: Aggregate billing totals across all runs.
      type: object
      required:
        - runs
        - input_tokens
        - output_tokens
        - total_tokens
        - reasoning_tokens
        - cache_read_tokens
        - cache_write_tokens
        - timing
      properties:
        runs:
          type: integer
          description: Total number of completed runs.
          example: 9
        input_tokens:
          type: integer
          description: Total input tokens.
          example: 643860
        output_tokens:
          type: integer
          description: Total output tokens.
          example: 189720
        total_tokens:
          type: integer
          description: Total tokens aggregated across all billing categories.
          example: 833580
        reasoning_tokens:
          type: integer
          description: Total reasoning tokens.
          example: 12040
        cache_read_tokens:
          type: integer
          description: Total cache read tokens.
          example: 85400
        cache_write_tokens:
          type: integer
          description: Total cache write tokens.
          example: 9200
        total_usd_micros:
          type: ["integer", "null"]
          format: int64
          description: Total billed USD amount in micros.
          example: 20340000
        timing:
          $ref: "#/components/schemas/RunTiming"
          description: |
            Aggregate timing rollup across every completed run. Active timing
            sums work across stage visits, so `active_time_ms` can exceed
            `wall_time_ms`.

    BillingStageRef:
      description: Reference to a workflow node in a billing stage row.
      type: object
      required:
        - id
        - name
      properties:
        id:
          type: string
          description: Stage identifier (slug).
          example: propose-changes
        name:
          type: string
          description: Human-readable stage name.
          example: Propose Changes

    RunCheckpoint:
      description: Serializable snapshot of execution state for crash recovery and resume.
      type: object
      required:
        - timestamp
        - current_node
        - completed_nodes
        - node_retries
        - context_values
      properties:
        timestamp:
          type: string
          format: date-time
          description: ISO 8601 timestamp when the checkpoint was created.
        current_node:
          type: string
          description: Identifier of the node being executed at checkpoint time.
        completed_nodes:
          type: array
          items:
            type: string
          description: Identifiers of nodes that have completed execution.
        node_retries:
          type: object
          additionalProperties:
            type: integer
          description: Map of node identifier to retry count.
        context_values:
          type: object
          additionalProperties: true
          description: Key-value context map accumulated during execution.
        node_outcomes:
          type: object
          additionalProperties: true
          description: Map of node identifier to outcome data for goal gate checks after resume.
        next_node_id:
          type: string
          description: The node to resume execution at after this checkpoint.
        git_commit_sha:
          type: string
          description: SHA of the git commit created at this checkpoint.
        loop_failure_signatures:
          type: object
          additionalProperties: true
          description: Failure signature counts within the main loop.
        restart_failure_signatures:
          type: object
          additionalProperties: true
          description: Failure signature counts across loop_restart edges.

    # ── Stage / Turn Schemas ─────────────────────────────────────────────

    AgentControlState:
      description: Control state of a live agent stage.
      type: string
      enum:
        - running
        - waiting_for_steer

    StageId:
      description: Canonical stage execution identifier in `node_id@visit` form.
      type: string
      example: verify@2

    ParallelBranchId:
      description: >-
        Durable identity of one branch within a parallel execution, in
        `{parallel_group_id}:{index}` form.
      type: string
      example: review_fork@3:1

    StageState:
      description: Lifecycle projection state of a workflow stage.
      type: string
      enum:
        - pending
        - running
        - retrying
        - succeeded
        - partially_succeeded
        - failed
        - skipped
        - cancelled

    StageHandler:
      description: Canonical workflow stage handler kind.
      type: string
      enum:
        - start
        - exit
        - agent
        - prompt
        - command
        - human
        - conditional
        - parallel
        - parallel.fan_in
        - stack.manager_loop
        - wait

    RunStage:
      description: A single stage in a run's workflow graph.
      type: object
      required:
        - id
        - name
        - handler
        - status
        - node_id
        - visit
        - billing
      properties:
        id:
          $ref: "#/components/schemas/StageId"
        name:
          type: string
          description: Human-readable stage name.
          example: Propose Changes
        handler:
          $ref: "#/components/schemas/StageHandler"
        status:
          $ref: "#/components/schemas/StageState"
        wall_time_ms:
          type: integer
          format: uint64
          minimum: 0
          description: Wall-clock time the latest attempt spent in this stage, in milliseconds.
          example: 154000
        node_id:
          type: string
          description: Node id in the workflow graph; multiple stages with different visits share the same node_id.
          example: verify
        visit:
          type: integer
          format: uint32
          minimum: 1
          description: >-
            1-based stage execution ordinal, the numeric component of `id`. It
            increments each time the node produces a new observable execution:
            graph re-entry (loops) and replay of post-checkpoint work after
            resume. Automatic in-place retries do not increment it.
          example: 2
        graph_visit:
          type: ["integer", "null"]
          format: uint32
          minimum: 1
          description: >-
            1-based count of how many times workflow control entered this node
            (drives `max_visits`). Differs from `visit` when a post-checkpoint
            execution is replayed after resume. Absent for stages recorded
            before execution identity was tracked.
          example: 1
        resumed_from_stage_id:
          oneOf:
            - $ref: "#/components/schemas/StageId"
            - type: "null"
          description: >-
            StageId of the prior post-checkpoint execution superseded by this
            replay after the run was resumed.
          example: verify@1
        parallel_group_id:
          allOf:
            - $ref: "#/components/schemas/StageId"
          description: >-
            Exact StageId of the parent parallel execution. Clients can compare
            this directly with the `id` of a parallel stage. Omitted for stages
            that are not parallel branches.
          example: review_fork@1
        parallel_branch_index:
          type: integer
          format: uint32
          minimum: 0
          description: >-
            Zero-based outgoing-edge index within the parent parallel
            execution. Omitted for stages that are not parallel branches.
          example: 1
        provider_used:
          oneOf:
            - $ref: "#/components/schemas/StageModelUsage"
            - type: "null"
          description: Provider, model, and request controls recorded for the latest stage attempt.
        started_at:
          type: ["string", "null"]
          format: date-time
          description: Wall-clock time the latest attempt of this stage started, if known.
          example: "2026-04-29T12:34:56Z"
        billing:
          $ref: "#/components/schemas/BilledTokenCounts"
          description: >-
            Token counts for this stage execution alone. `total_usd_micros` is
            the provider-reported cost when there is one, otherwise the server
            catalog's price for these tokens — the same pricing the
            `/runs/{id}/billing` rows use. All-zero counts mean the stage made
            no model calls. Unlike the billing rows, which sum every visit of a
            node, this covers only this visit.

    # ── File Diff Schemas ──────────────────────────────────────────────

    FileCheckpoint:
      description: A named checkpoint within a run, used to filter file diffs.
      type: object
      required:
        - id
        - label
      properties:
        id:
          type: string
          description: Checkpoint identifier.
          example: cp-3
        label:
          type: string
          description: Human-readable label for the checkpoint.
          example: "Checkpoint 3 — Review Changes"

    DiffFile:
      description: A file's contents at one side of a diff.
      type: object
      required:
        - name
        - contents
      properties:
        name:
          type: string
          description: File path relative to the repository root.
          example: src/commands/run.ts
        contents:
          type: ["string", "null"]
          description: "Full contents at this side. Live path: empty string for added/deleted/binary/sensitive/symlink/submodule/truncated entries (the placeholder flags drive rendering). Degraded path: null for every entry (including placeholder-flagged ones), because the server only holds a checkpoint patch and cannot reconstruct full file bytes — distinguish degraded from live by `meta.degraded`."
          example: 'import { parseArgs } from "node:util";'

    FileDiff:
      description: |
        A before/after pair showing changes to a single file.

        Contents conventions for non-modify cases:
        - Added: `old_file.contents` is empty string; `new_file` holds the added contents.
        - Deleted: `new_file.contents` is empty string; `old_file` holds the removed contents.
        - Renamed (no content change): both sides hold identical contents; `old_file.name != new_file.name`.
        - Symlink / submodule / binary / sensitive / truncated: contents are empty strings; consumers must render a placeholder based on the flag set.
        - Degraded responses: contents are null on every entry; regular text diffs include `unified_patch`.
      type: object
      required:
        - old_file
        - new_file
      properties:
        old_file:
          $ref: "#/components/schemas/DiffFile"
        new_file:
          $ref: "#/components/schemas/DiffFile"
        change_kind:
          type: string
          description: Optional classification of the change. Clients that don't recognize a value should fall back to inspecting the old/new contents.
          enum:
            - added
            - modified
            - deleted
            - renamed
            - symlink
            - submodule
          example: modified
        truncated:
          type: boolean
          description: When `true`, `new_file.contents` and `old_file.contents` are empty strings because the file exceeded a cap (see `truncation_reason`).
          example: false
        truncation_reason:
          type: string
          description: Reason this file's contents were omitted. Absent when `truncated` is `false` or omitted.
          enum:
            - file_too_large
            - budget_exhausted
        binary:
          type: boolean
          description: When `true`, the file is non-textual; `contents` on both sides are empty strings.
          example: false
        sensitive:
          type: boolean
          description: When `true`, the file path matched the server's sensitive-path denylist; `contents` on both sides are empty strings regardless of truncation or binary flags.
          example: false
        unified_patch:
          type: ["string", "null"]
          description: Per-file unified-patch text (the `diff --git` section verbatim). Populated only for regular non-flagged text-diff entries in degraded mode. Absent for sensitive, binary, symlink, submodule, truncated, and live-path entries.

    DiffStats:
      description: |
        Aggregate `+/-` line counts across all files in a diff. Binary, sensitive, symlink, and submodule
        files contribute 0/0 since they have no line-level diff. Both fields
        are 0 for empty / pre-start envelopes.
      type: object
      required:
        - additions
        - deletions
      properties:
        additions:
          type: integer
          description: Total lines added.
          example: 567
        deletions:
          type: integer
          description: Total lines deleted.
          example: 234

    DiffSummary:
      description: Cheap aggregate file and line counts for a run diff.
      type: object
      required:
        - files_changed
        - additions
        - deletions
      properties:
        files_changed:
          type: integer
          description: Total number of changed files, including binary files.
          example: 42
        additions:
          type: integer
          description: Total lines added across text files.
          example: 567
        deletions:
          type: integer
          description: Total lines deleted across text files.
          example: 234

    RunDiff:
      description: Patch text and aggregate counts captured for a run-level diff.
      type: object
      properties:
        patch:
          type: ["string", "null"]
        summary:
          oneOf:
            - $ref: "#/components/schemas/DiffSummary"
            - type: "null"

    RunFilesMeta:
      description: |
        Metadata for a `PaginatedRunFileList` response.

        Replaces `PaginationMeta` on the files endpoint — the naturally-bounded list does not use cursor pagination but exposes caps and a degraded-response path instead.
      type: object
      required:
        - source
        - scope
        - truncated
        - total_changed
        - stats
      properties:
        source:
          type: string
          description: Source used to materialize this response. `sandbox` honors the requested scope from the run-owned sandbox; `final_patch` is fallback committed/final diff data from stored run state.
          enum:
            - sandbox
            - final_patch
        scope:
          type: string
          description: Diff scope materialized for this response.
          enum:
            - committed
            - uncommitted
            - all
            - range
        stats:
          $ref: "#/components/schemas/DiffStats"
        truncated:
          type: boolean
          description: True when any cap (file count, per-file size, or aggregate size) was hit for this response.
          example: false
        files_omitted_by_budget:
          type: integer
          description: Number of files dropped because the aggregate 5 MiB budget was exhausted. Zero or absent when no files were dropped for budget reasons.
          example: 0
        total_changed:
          type: integer
          description: Total files changed in the run (before caps were applied). May exceed `data.length` when truncation occurred.
          example: 3
        to_sha:
          type: string
          description: Head SHA the diff (or patch) was resolved against.
          pattern: "^[0-9a-f]{7,40}$"
          example: "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0"
        to_sha_committed_at:
          type: string
          format: date-time
          description: Commit time of `to_sha`, used by the UI for the "Checkpoint Xm ago" freshness label on Running runs.
        degraded:
          type: boolean
          description: When `true`, every entry in `data` has null `contents` on both sides; non-sensitive non-flagged entries also include `unified_patch`. Entries flagged `sensitive` / `binary` / `symlink` / `submodule` / `truncated` render via the same placeholders used in the live path (the flags drive rendering; contents are null in degraded mode regardless). The data shape is otherwise identical to the live path.
          example: false
        degraded_reason:
          type: string
          description: Why the response degraded. Absent when `degraded` is `false` or omitted.
          enum:
            - sandbox_unreachable
            - sandbox_gone
            - provider_unsupported

    PaginatedRunFileList:
      description: |
        List of file diffs produced by a run, with metadata describing truncation and degraded-response state.

        Naturally bounded: at most 200 files per response. Consumers should inspect `meta.truncated` rather than assuming `data.length` equals the run's total change count.
      type: object
      required:
        - data
        - meta
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/FileDiff"
        meta:
          $ref: "#/components/schemas/RunFilesMeta"

    RunCommitParent:
      description: Parent commit pointer.
      type: object
      required:
        - sha
        - short_sha
      properties:
        sha:
          type: string
          pattern: "^[0-9a-f]{7,40}$"
        short_sha:
          type: string
          pattern: "^[0-9a-f]{7,12}$"

    RunCommitPerson:
      description: Git author or committer identity.
      type: object
      required:
        - name
        - email
        - date
      properties:
        name:
          type: string
        email:
          type: string
        date:
          type: ["string", "null"]
          format: date-time

    RunCommit:
      description: A Git commit on a run branch.
      type: object
      required:
        - sha
        - short_sha
        - parents
        - author
        - committer
        - subject
        - message
        - trailers
        - tree_sha
      properties:
        sha:
          type: string
          pattern: "^[0-9a-f]{7,40}$"
        short_sha:
          type: string
          pattern: "^[0-9a-f]{7,12}$"
        parents:
          type: array
          items:
            $ref: "#/components/schemas/RunCommitParent"
        author:
          $ref: "#/components/schemas/RunCommitPerson"
        committer:
          $ref: "#/components/schemas/RunCommitPerson"
        subject:
          type: string
        body:
          type: ["string", "null"]
        message:
          type: string
        trailers:
          type: object
          additionalProperties:
            type: string
        tree_sha:
          type: ["string", "null"]
          pattern: "^[0-9a-f]{7,40}$"

    RunCommitsMeta:
      description: Metadata for a `PaginatedRunCommitList` response.
      type: object
      required:
        - source
        - base_sha
        - head_sha
        - limit
        - total_returned
        - truncated
      properties:
        source:
          type: string
          enum:
            - sandbox
        base_sha:
          type: string
          pattern: "^[0-9a-f]{7,40}$"
        head_sha:
          type: string
          pattern: "^[0-9a-f]{7,40}$"
        limit:
          type: integer
          minimum: 1
          maximum: 100
        total_returned:
          type: integer
          minimum: 0
        truncated:
          type: boolean

    PaginatedRunCommitList:
      description: Git commits on a run branch since the run base.
      type: object
      required:
        - data
        - meta
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/RunCommit"
        meta:
          $ref: "#/components/schemas/RunCommitsMeta"

    # ── Billing Schemas ──────────────────────────────────────────────────

    RunBillingStage:
      description: Token counts and billed totals for one workflow node within a run. Rows are grouped by node; billing and timing sum every visit of that node.
      type: object
      required:
        - stage
        - model
        - billing
        - timing
      properties:
        stage:
          $ref: "#/components/schemas/BillingStageRef"
        model:
          description: Latest usage-bearing visit model for this node; null when no visit used an LLM model.
          oneOf:
            - $ref: "#/components/schemas/BillingModelRef"
            - type: "null"
        billing:
          $ref: "#/components/schemas/BilledTokenCounts"
        timing:
          $ref: "#/components/schemas/StageTiming"
          description: |
            Per-node timing summed across every visit. `wall_time_ms` is the
            sum of visit wall times; the active breakdown sums work timing.
        started_at:
          type: ["string", "null"]
          format: date-time
          description: Wall-clock time the latest attempt of this stage started, if known.
          example: "2026-04-29T12:34:56Z"
        state:
          oneOf:
            - $ref: "#/components/schemas/StageState"
            - type: "null"
          description: Lifecycle state of the stage. Use to detect in-flight rows for client-side runtime ticking.

    RunBillingTotals:
      description: Aggregate billing totals across all stages of a run.
      type: object
      required:
        - timing
        - input_tokens
        - output_tokens
        - total_tokens
        - reasoning_tokens
        - cache_read_tokens
        - cache_write_tokens
      properties:
        timing:
          $ref: "#/components/schemas/RunTiming"
          description: |
            Run-level timing rollup. `wall_time_ms` is summed across stage
            visits; active timing sums work across visits.
        input_tokens:
          type: integer
          description: Total input tokens consumed.
          example: 71540
        output_tokens:
          type: integer
          description: Total output tokens generated.
          example: 21080
        total_tokens:
          type: integer
          description: Total tokens aggregated across all billing categories.
          example: 92620
        reasoning_tokens:
          type: integer
          description: Total reasoning tokens.
          example: 3400
        cache_read_tokens:
          type: integer
          description: Total cache read tokens.
          example: 22000
        cache_write_tokens:
          type: integer
          description: Total cache write tokens.
          example: 4500
        total_usd_micros:
          type: ["integer", "null"]
          format: int64
          description: Total billed USD amount in micros.
          example: 2260000

    BillingByModel:
      description: Billing statistics grouped by model.
      type: object
      required:
        - model
        - stages
        - billing
      properties:
        model:
          $ref: "#/components/schemas/BillingModelRef"
        stages:
          type: integer
          description: Number of usage-bearing stage visits that used this model.
          example: 2
        billing:
          $ref: "#/components/schemas/BilledTokenCounts"

    RunBilling:
      description: Complete billing breakdown for a single run.
      type: object
      required:
        - stages
        - totals
        - by_model
      properties:
        stages:
          type: array
          description: Per-node billing breakdown. Each row sums billing and runtime across all visits of that node.
          items:
            $ref: "#/components/schemas/RunBillingStage"
        totals:
          $ref: "#/components/schemas/RunBillingTotals"
        by_model:
          type: array
          description: Billing grouped by model.
          items:
            $ref: "#/components/schemas/BillingByModel"

    AggregateBilling:
      description: Aggregate token counts and billed totals across all runs since server start.
      type: object
      required:
        - totals
        - by_model
      properties:
        totals:
          $ref: "#/components/schemas/AggregateBillingTotals"
        by_model:
          type: array
          description: Billing grouped by model.
          items:
            $ref: "#/components/schemas/BillingByModel"

    PreviewUrlRequest:
      description: Request body for generating a preview URL from a sandbox port.
      type: object
      required:
        - port
        - expires_in_secs
      properties:
        port:
          type: integer
          description: Port number exposed by the sandbox.
          example: 3000
        expires_in_secs:
          type: integer
          description: Time-to-live for the preview URL in seconds.
          minimum: 1
          maximum: 86400
          example: 3600
        signed:
          type: boolean
          description: When true, return a signed URL that does not require a preview token header.
          default: false

    PreviewUrlResponse:
      description: Response containing the generated preview URL.
      type: object
      required:
        - url
      properties:
        url:
          type: string
          description: Preview URL.
          example: "https://preview.example.com/sb-a1b2c3d4/3000"
        token:
          type: string
          description: Preview token header value for unsigned preview URLs.
          example: "preview-token-123"

    SshAccessRequest:
      description: Request body for creating sandbox access for a sandbox-backed run.
      type: object
      required:
        - ttl_minutes
      properties:
        ttl_minutes:
          type: number
          description: Time-to-live for time-limited access commands in minutes. Ignored by providers whose commands are not time-limited.
          minimum: 1
          maximum: 1440
          example: 60

    SshAccessResponse:
      description: Response containing a command for connecting to the sandbox.
      type: object
      required:
        - command
      properties:
        command:
          type: string
          description: Command to connect to the sandbox.
          example: docker exec -it fabro-run-01HY0000000000000000000000 sh -lc 'cd /workspace/fabro && exec sh -l'

    SandboxState:
      description: Normalized sandbox lifecycle state used by the control plane and UI. The original provider-specific state string is preserved in `native_state`.
      type: string
      enum:
        - unknown
        - provisioning
        - starting
        - running
        - stopping
        - stopped
        - paused
        - deleting
        - deleted
        - archived
        - restoring
        - resizing
        - error

    SandboxResources:
      description: Resource configuration for a sandbox. Fields are nullable when the provider does not surface a value or no limit is configured.
      type: object
      properties:
        cpu_cores:
          type: number
          format: double
          description: Configured CPU cores. Null when unavailable.
        memory_bytes:
          type: integer
          format: int64
          minimum: 0
          description: Memory limit in bytes. Null when unavailable or unlimited.
        disk_bytes:
          type: integer
          format: int64
          minimum: 0
          description: Disk size in bytes. Null when unavailable.

    SandboxNetworkPolicyMode:
      description: Provider-neutral public-network policy for one direction.
      type: string
      enum:
        - unknown
        - open
        - blocked
        - cidr_allow_list
        - essentials_only

    SandboxNetworkPolicy:
      description: Public-network policy for one direction.
      type: object
      required:
        - mode
        - cidrs
      properties:
        mode:
          $ref: "#/components/schemas/SandboxNetworkPolicyMode"
        cidrs:
          type: array
          items:
            type: string
          description: CIDR entries when `mode` is `cidr_allow_list`; empty for other modes.

    SandboxNetwork:
      description: Provider-neutral public-network policy for sandbox egress and ingress.
      type: object
      required:
        - egress
        - ingress
      properties:
        egress:
          $ref: "#/components/schemas/SandboxNetworkPolicy"
        ingress:
          $ref: "#/components/schemas/SandboxNetworkPolicy"

    SandboxTimestamps:
      description: Lifecycle timestamps for a sandbox. Fields are nullable when the provider does not surface a value.
      type: object
      properties:
        created_at:
          type: string
          format: date-time
          description: When the sandbox was created.
        last_activity_at:
          type: string
          format: date-time
          description: Most recent activity timestamp reported by the provider.

    SandboxDetails:
      description: Provider-neutral details about the sandbox owned by a run.
      type: object
      required:
        - sandbox
        - state
        - resources
        - network
        - labels
        - timestamps
      properties:
        sandbox:
          $ref: "#/components/schemas/RunSandboxInstance"
        state:
          $ref: "#/components/schemas/SandboxState"
        native_state:
          type: ["string", "null"]
          description: Original provider state string before normalization. Display/debugging only; UI behavior keys off `state`.
        region:
          type: ["string", "null"]
          description: Provider region or target. Null for local-style providers.
        web_url:
          type: ["string", "null"]
          description: Provider dashboard URL for this sandbox when available.
        resources:
          $ref: "#/components/schemas/SandboxResources"
        network:
          $ref: "#/components/schemas/SandboxNetwork"
        labels:
          type: object
          additionalProperties:
            type: string
          description: Provider-reported labels.
        timestamps:
          $ref: "#/components/schemas/SandboxTimestamps"

    SandboxInfo:
      description: Provider-backed inventory record for a Fabro-managed sandbox.
      type: object
      required:
        - provider
        - id
        - state
        - resources
        - network
        - labels
        - timestamps
      properties:
        provider:
          $ref: "#/components/schemas/SandboxProviderKind"
        id:
          type: string
          description: Provider-native sandbox id.
        display_name:
          type: ["string", "null"]
          description: Provider display name when distinct from the native id.
        state:
          $ref: "#/components/schemas/SandboxState"
        native_state:
          type: ["string", "null"]
          description: Original provider state string before normalization. Display/debugging only; UI behavior keys off `state`.
        image:
          type: ["string", "null"]
          description: Provider image when surfaced by the sandbox provider.
        snapshot:
          type: ["string", "null"]
          description: Provider snapshot when surfaced by the sandbox provider.
        region:
          type: ["string", "null"]
          description: Provider region or target. Null for local-style providers.
        web_url:
          type: ["string", "null"]
          description: Provider dashboard URL for this sandbox when available.
        working_directory:
          type: ["string", "null"]
          description: Provider-reported or Fabro-default working directory when available.
        resources:
          $ref: "#/components/schemas/SandboxResources"
        network:
          $ref: "#/components/schemas/SandboxNetwork"
        labels:
          type: object
          additionalProperties:
            type: string
          description: Provider-reported labels.
        timestamps:
          $ref: "#/components/schemas/SandboxTimestamps"

    SandboxProviderLookupError:
      description: Provider error captured during fail-soft sandbox inventory lookup.
      type: object
      required:
        - provider
        - message
      properties:
        provider:
          $ref: "#/components/schemas/SandboxProviderKind"
        message:
          type: string

    SandboxListMeta:
      description: Metadata for provider-backed sandbox inventory.
      type: object
      required:
        - provider_errors
      properties:
        provider_errors:
          type: array
          items:
            $ref: "#/components/schemas/SandboxProviderLookupError"

    SandboxListResponse:
      description: Non-paginated provider-backed sandbox inventory response.
      type: object
      required:
        - data
        - meta
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/SandboxInfo"
        meta:
          $ref: "#/components/schemas/SandboxListMeta"

    SandboxFileEntry:
      description: A directory entry in a run sandbox.
      type: object
      required:
        - name
        - is_dir
      properties:
        name:
          type: string
          description: Basename of the entry.
        is_dir:
          type: boolean
          description: Whether the entry is a directory.
        size:
          type: integer
          format: int64
          description: File size in bytes when known.

    SandboxFileListResponse:
      description: Non-paginated list of sandbox directory entries.
      type: object
      required:
        - data
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/SandboxFileEntry"

    SandboxService:
      description: A listening TCP service discovered inside a run sandbox.
      type: object
      required:
        - port
        - addresses
        - processes
        - preview_supported
      properties:
        port:
          type: integer
          minimum: 1
          maximum: 65535
          description: Listening TCP port.
          example: 3000
        addresses:
          type: array
          description: Local bind addresses discovered from `ss` or `/proc/net/tcp*`.
          items:
            type: string
          example: ["127.0.0.1:3000", "[::]:3000"]
        processes:
          type: array
          description: Visible process summaries when available. Empty when the sandbox only supports `/proc/net/tcp*` discovery.
          items:
            type: string
          example: ['users:(("node",pid=42,fd=23))']
        preview_supported:
          type: boolean
          description: Whether the provider supports an external preview URL for this port.
          example: true

    SandboxServiceListResponse:
      description: Non-paginated list of listening TCP services in a run sandbox.
      type: object
      required:
        - data
        - meta
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/SandboxService"
        meta:
          $ref: "#/components/schemas/SandboxServiceListMeta"

    SandboxServiceListMeta:
      description: Metadata about sandbox service discovery.
      type: object
      required:
        - source
      properties:
        source:
          $ref: "#/components/schemas/SandboxServiceDiscoverySource"

    SandboxServiceDiscoverySource:
      description: Tool or kernel interface used to discover sandbox services.
      type: string
      enum:
        - ss
        - procfs

    VncPreviewResponse:
      description: Response containing a signed noVNC preview URL for a Daytona sandbox.
      type: object
      required:
        - url
        - provider
        - port
        - expires_in_secs
      properties:
        url:
          type: string
          description: Signed noVNC preview URL.
          example: "https://preview.example.com/sb-a1b2c3d4/6080?token=..."
        provider:
          type: string
          description: Sandbox provider that produced the VNC preview.
          example: daytona
        port:
          type: integer
          minimum: 1
          maximum: 65535
          description: noVNC port exposed by the sandbox.
          example: 6080
        expires_in_secs:
          type: integer
          minimum: 1
          description: Signed URL time-to-live in seconds.
          example: 3600

    # ── Insights Schemas ─────────────────────────────────────────────────

    SavedQuery:
      description: A saved SQL query for the insights editor.
      type: object
      required:
        - id
        - name
        - sql
        - created_at
        - updated_at
      properties:
        id:
          type: string
          description: Unique query identifier.
          example: "1"
        name:
          type: string
          description: Human-readable query name.
          example: Run duration by workflow
        sql:
          type: string
          description: SQL query text.
          example: "SELECT workflow_name, AVG(duration_seconds) FROM runs GROUP BY 1"
        created_at:
          type: string
          format: date-time
          description: Timestamp when the query was saved.
          example: "2026-03-01T10:00:00Z"
        updated_at:
          type: string
          format: date-time
          description: Timestamp when the query was last modified.
          example: "2026-03-05T14:30:00Z"

    SaveQueryRequest:
      description: Request body for creating or updating a saved query.
      type: object
      required:
        - name
        - sql
      properties:
        name:
          type: string
          description: Human-readable query name.
          example: Run duration by workflow
        sql:
          type: string
          description: SQL query text.
          example: "SELECT workflow_name, AVG(duration_seconds) FROM runs GROUP BY 1"

    ExecuteQueryRequest:
      description: Request body for executing an ad-hoc SQL query.
      type: object
      required:
        - sql
      properties:
        sql:
          type: string
          description: SQL query to execute.
          example: "SELECT workflow_name, COUNT(*) FROM runs GROUP BY 1"

    ExecuteQueryResponse:
      description: Columnar result set from an executed query.
      type: object
      required:
        - columns
        - rows
        - elapsed
        - row_count
      properties:
        columns:
          type: array
          description: Column names in the result set.
          items:
            type: string
          example: ["workflow_name", "count"]
        rows:
          type: array
          description: Result rows, each an array of values matching the column order.
          items:
            type: array
            items:
              oneOf:
                - type: string
                - type: number
                - type: boolean
                - type: "null"
        elapsed:
          type: number
          description: Query execution time in seconds.
          example: 0.342
        row_count:
          type: integer
          description: Number of rows returned.
          example: 3

    HistoryEntry:
      description: A previously executed query in the history log.
      type: object
      required:
        - id
        - sql
        - timestamp
        - elapsed
        - row_count
      properties:
        id:
          type: string
          description: Unique history entry identifier.
          example: h1
        sql:
          type: string
          description: SQL query that was executed.
          example: "SELECT workflow_name, COUNT(*) FROM runs GROUP BY 1"
        timestamp:
          type: string
          format: date-time
          description: ISO 8601 timestamp of execution.
          example: "2025-09-15T14:00:00Z"
        elapsed:
          type: number
          description: Query execution time in seconds.
          example: 0.342
        row_count:
          type: integer
          description: Number of rows returned.
          example: 6

    # ── Settings Schemas ─────────────────────────────────────────────────

    ServerSettings:
      description: Current in-memory server settings view.
      type: object
      required: [server]
      properties:
        server:
          $ref: "#/components/schemas/ServerNamespace"

    ServerNamespace:
      type: object
      required:
        - listen
        - api
        - web
        - auth
        - sandbox
        - storage
        - artifacts
        - slatedb
        - scheduler
        - logging
        - integrations
      properties:
        listen:
          $ref: "#/components/schemas/ServerListenSettings"
        api:
          $ref: "#/components/schemas/ServerApiSettings"
        web:
          $ref: "#/components/schemas/ServerWebSettings"
        auth:
          $ref: "#/components/schemas/ServerAuthSettings"
        sandbox:
          $ref: "#/components/schemas/ServerSandboxSettings"
        storage:
          $ref: "#/components/schemas/ServerStorageSettings"
        artifacts:
          $ref: "#/components/schemas/ServerArtifactsSettings"
        slatedb:
          $ref: "#/components/schemas/ServerSlateDbSettings"
        scheduler:
          $ref: "#/components/schemas/ServerSchedulerSettings"
        logging:
          $ref: "#/components/schemas/ServerLoggingSettings"
        integrations:
          $ref: "#/components/schemas/ServerIntegrationsSettings"

    ServerListenSettings:
      oneOf:
        - $ref: "#/components/schemas/ServerListenTcpSettings"
        - $ref: "#/components/schemas/ServerListenUnixSettings"

    ServerListenTcpSettings:
      type: object
      required: [type, address]
      properties:
        type:
          type: string
          enum: [tcp]
        address:
          type: string

    ServerListenUnixSettings:
      type: object
      required: [type, path]
      properties:
        type:
          type: string
          enum: [unix]
        path:
          type: string

    ServerApiSettings:
      type: object
      required: [url]
      properties:
        url:
          type: ["string", "null"]

    ServerWebSettings:
      type: object
      required: [enabled, url]
      properties:
        enabled:
          type: boolean
        url:
          type: string

    ServerAuthSettings:
      type: object
      required: [methods, github]
      properties:
        methods:
          type: array
          items:
            $ref: "#/components/schemas/ServerAuthMethod"
        github:
          $ref: "#/components/schemas/ServerAuthGithubSettings"

    ServerAuthMethod:
      type: string
      enum: [dev-token, github]

    ServerAuthGithubSettings:
      type: object
      required: [allowed_usernames]
      properties:
        allowed_usernames:
          type: array
          items:
            type: string

    ServerSandboxSettings:
      type: object
      required: [providers]
      properties:
        providers:
          $ref: "#/components/schemas/ServerSandboxProvidersSettings"

    ServerSandboxProvidersSettings:
      type: object
      required: [local, docker, daytona]
      properties:
        local:
          $ref: "#/components/schemas/ServerSandboxProviderSettings"
        docker:
          $ref: "#/components/schemas/ServerSandboxProviderSettings"
        daytona:
          $ref: "#/components/schemas/ServerSandboxProviderSettings"

    ServerSandboxProviderSettings:
      type: object
      required: [enabled]
      properties:
        enabled:
          type: boolean

    ServerStorageSettings:
      type: object
      required: [root]
      properties:
        root:
          type: string

    ServerArtifactsSettings:
      type: object
      required: [prefix, store]
      properties:
        prefix:
          type: string
        store:
          $ref: "#/components/schemas/ObjectStoreSettings"

    ServerSlateDbSettings:
      type: object
      required: [prefix, store, flush_interval, disk_cache]
      properties:
        prefix:
          type: string
        store:
          $ref: "#/components/schemas/ObjectStoreSettings"
        flush_interval:
          type: string
        disk_cache:
          type: boolean

    ObjectStoreSettings:
      oneOf:
        - $ref: "#/components/schemas/ObjectStoreLocalSettings"
        - $ref: "#/components/schemas/ObjectStoreS3Settings"

    ObjectStoreLocalSettings:
      type: object
      required: [type, root]
      properties:
        type:
          type: string
          enum: [local]
        root:
          type: string

    ObjectStoreS3Settings:
      type: object
      required: [type, bucket, region, endpoint, path_style]
      properties:
        type:
          type: string
          enum: [s3]
        bucket:
          type: string
        region:
          type: string
        endpoint:
          type: ["string", "null"]
        path_style:
          type: boolean

    ServerSchedulerSettings:
      type: object
      required: [max_concurrent_runs]
      properties:
        max_concurrent_runs:
          type: integer

    ServerLoggingSettings:
      type: object
      required: [level, destination]
      properties:
        level:
          type: ["string", "null"]
        destination:
          $ref: "#/components/schemas/LogDestination"

    LogDestination:
      type: string
      enum: [file, stdout]

    ServerIntegrationsSettings:
      type: object
      required: [github, slack]
      properties:
        github:
          $ref: "#/components/schemas/GithubIntegrationSettings"
        slack:
          $ref: "#/components/schemas/SlackIntegrationSettings"

    GithubIntegrationSettings:
      type: object
      required:
        - enabled
        - strategy
        - app_id
        - client_id
        - slug
        - webhooks
      properties:
        enabled:
          type: boolean
        strategy:
          $ref: "#/components/schemas/GithubIntegrationStrategy"
        app_id:
          type: ["string", "null"]
        client_id:
          type: ["string", "null"]
        slug:
          type: ["string", "null"]
        webhooks:
          oneOf:
            - $ref: "#/components/schemas/IntegrationWebhooksSettings"
            - type: "null"

    GithubIntegrationStrategy:
      type: string
      enum: [token, app]

    SlackIntegrationSettings:
      type: object
      required: [enabled, default_channel]
      properties:
        enabled:
          type: boolean
        default_channel:
          type: ["string", "null"]

    IntegrationWebhooksSettings:
      type: object
      required: [strategy]
      properties:
        strategy:
          oneOf:
            - $ref: "#/components/schemas/WebhookStrategy"
            - type: "null"

    WebhookStrategy:
      type: string
      enum: [tailscale_funnel, server_url]

    WorkflowSettings:
      description: |
        The persisted dense `WorkflowSettings` snapshot used for a specific run.
        This matches the resolved run settings recorded at launch time.
      type: object
      required:
        - project
        - workflow
        - environments
        - run
      properties:
        project:
          $ref: "#/components/schemas/ProjectNamespace"
        workflow:
          $ref: "#/components/schemas/WorkflowNamespace"
        environments:
          type: object
          additionalProperties:
            $ref: "#/components/schemas/EnvironmentSettings"
        run:
          $ref: "#/components/schemas/RunNamespace"

    InterpString:
      description: Config string that can contain typed interpolation tokens.
      type: string

    StringMap:
      type: object
      additionalProperties:
        type: string

    TomlValue:
      description: Arbitrary TOML-compatible value.

    ProjectNamespace:
      type: object
      required: [name, description, metadata]
      properties:
        name:
          type: ["string", "null"]
        description:
          type: ["string", "null"]
        metadata:
          $ref: "#/components/schemas/StringMap"

    WorkflowNamespace:
      type: object
      required: [name, description, graph, metadata]
      properties:
        name:
          type: ["string", "null"]
        description:
          type: ["string", "null"]
        graph:
          type: string
        metadata:
          $ref: "#/components/schemas/StringMap"

    RunNamespace:
      type: object
      required:
        - goal
        - working_dir
        - metadata
        - inputs
        - model
        - git
        - prepare
        - execution
        - checkpoint
        - clone
        - run_branch
        - meta_branch
        - environment
        - notifications
        - interviews
        - agent
        - hooks
        - scm
        - pull_request
        - artifacts
        - integrations
      properties:
        goal:
          oneOf:
            - $ref: "#/components/schemas/RunGoal"
            - type: "null"
        working_dir:
          oneOf:
            - $ref: "#/components/schemas/InterpString"
            - type: "null"
        metadata:
          $ref: "#/components/schemas/StringMap"
        inputs:
          type: object
          additionalProperties:
            $ref: "#/components/schemas/TomlValue"
        model:
          $ref: "#/components/schemas/RunModelSettings"
        git:
          $ref: "#/components/schemas/RunGitSettings"
        prepare:
          $ref: "#/components/schemas/RunPrepareSettings"
        execution:
          $ref: "#/components/schemas/RunExecutionSettings"
        checkpoint:
          $ref: "#/components/schemas/RunCheckpointSettings"
        clone:
          $ref: "#/components/schemas/RunCloneSettings"
        run_branch:
          $ref: "#/components/schemas/RunBranchSettings"
        meta_branch:
          $ref: "#/components/schemas/RunMetaBranchSettings"
        environment:
          $ref: "#/components/schemas/RunEnvironmentSettings"
        notifications:
          type: object
          additionalProperties:
            $ref: "#/components/schemas/NotificationRouteSettings"
        interviews:
          $ref: "#/components/schemas/RunInterviewsSettings"
        agent:
          $ref: "#/components/schemas/RunAgentSettings"
        hooks:
          type: array
          items:
            $ref: "#/components/schemas/HookDefinition"
        scm:
          $ref: "#/components/schemas/RunScmSettings"
        pull_request:
          oneOf:
            - $ref: "#/components/schemas/PullRequestSettings"
            - type: "null"
        artifacts:
          $ref: "#/components/schemas/ArtifactsSettings"
        integrations:
          $ref: "#/components/schemas/RunIntegrationsSettings"

    RunIntegrationsSettings:
      type: object
      required: [github]
      properties:
        github:
          $ref: "#/components/schemas/RunIntegrationsGithubSettings"

    RunIntegrationsGithubSettings:
      type: object
      required: [permissions]
      properties:
        permissions:
          type: object
          additionalProperties:
            type: string

    RunGoal:
      oneOf:
        - $ref: "#/components/schemas/RunGoalInline"
        - $ref: "#/components/schemas/RunGoalFile"

    RunGoalInline:
      type: object
      required: [type, value]
      properties:
        type:
          type: string
          enum: [inline]
        value:
          $ref: "#/components/schemas/InterpString"

    RunGoalFile:
      type: object
      required: [type, value]
      properties:
        type:
          type: string
          enum: [file]
        value:
          $ref: "#/components/schemas/InterpString"

    ModelRef:
      type: string
      description: |
        A fallback model reference. Bare values name a provider, canonical
        model ID, or alias. Provider-qualified values use
        `provider:selector`; the selector may be a canonical model ID, alias,
        or provider API ID and may contain `/` or additional colons. A value
        is treated as qualified only when the text before the first `:` names
        a known provider, so model IDs that contain a colon — ollama
        `name:tag` values, Bedrock inference-profile IDs — stay whole. Legacy
        `provider/model` references remain accepted.
      example: openrouter:moonshotai/kimi-k3

    RunModelSettings:
      type: object
      required: [provider, name, fallbacks]
      properties:
        provider:
          type: ["string", "null"]
        name:
          type: ["string", "null"]
        fallbacks:
          type: object
          description: >
            Ordered fallback targets keyed by the originally requested model.
            Each chain is independent; selecting a fallback target does not
            activate that target model's own chain.
          additionalProperties:
            type: array
            items:
              $ref: "#/components/schemas/ModelRef"
        controls:
          $ref: "#/components/schemas/RunModelControls"

    RunModelControls:
      type: object
      description: >
        Run-level default values for typed model controls. Node and style
        attributes still win over these defaults.
      required: [reasoning_effort, speed]
      properties:
        reasoning_effort:
          type: ["string", "null"]
        speed:
          type: ["string", "null"]

    RunGitSettings:
      type: object
      required: [author]
      properties:
        author:
          oneOf:
            - $ref: "#/components/schemas/GitAuthorSettings"
            - type: "null"

    GitAuthorSettings:
      type: object
      required: [name, email]
      properties:
        name:
          type: ["string", "null"]
        email:
          type: ["string", "null"]

    RunPrepareSettings:
      type: object
      required: [steps, timeout_ms]
      properties:
        steps:
          type: array
          items:
            $ref: "#/components/schemas/PreparedStep"
        timeout_ms:
          type: integer
          format: int64

    PreparedStep:
      description: |
        A single resolved prepare step. The runnable part preserves the
        script-vs-argv distinction via the `type` discriminator: a `script`
        is a raw shell snippet kept verbatim, while a `command` is an argv
        whose elements are shell-quoted and joined at the run boundary (after
        `{{ secrets.* }}` resolution) so an interpolated value cannot inject shell
        syntax. Optional per-step `env` is shared by both shapes.
      type: object
      required: [type]
      oneOf:
        - $ref: "#/components/schemas/PreparedScriptStep"
        - $ref: "#/components/schemas/PreparedCommandStep"
      discriminator:
        propertyName: type
        mapping:
          script: "#/components/schemas/PreparedScriptStep"
          command: "#/components/schemas/PreparedCommandStep"

    PreparedScriptStep:
      type: object
      required: [type, script]
      properties:
        type:
          type: string
          enum: [script]
        script:
          type: string
        env:
          $ref: "#/components/schemas/StringMap"

    PreparedCommandStep:
      type: object
      required: [type, command]
      properties:
        type:
          type: string
          enum: [command]
        command:
          type: array
          items:
            type: string
        env:
          $ref: "#/components/schemas/StringMap"

    RunExecutionSettings:
      type: object
      required: [mode, approval]
      properties:
        mode:
          $ref: "#/components/schemas/RunMode"
        approval:
          $ref: "#/components/schemas/ApprovalMode"

    RunMode:
      type: string
      enum: [normal, dry_run]

    ApprovalMode:
      type: string
      enum: [prompt, auto]

    RunCheckpointSettings:
      type: object
      required: [exclude_globs, skip_git_hooks]
      properties:
        exclude_globs:
          type: array
          items:
            type: string
        skip_git_hooks:
          type: boolean
          default: false
          description: |
            When true, Fabro-managed run-branch checkpoint commits bypass
            local Git commit hooks. Does not affect Fabro `[[run.hooks]]`
            or metadata-branch snapshots. Defaults to false.

    RunCloneSettings:
      type: object
      required: [enabled]
      properties:
        enabled:
          type: boolean

    RunBranchSettings:
      type: object
      required: [enabled, push]
      properties:
        enabled:
          type: boolean
        push:
          type: boolean

    RunMetaBranchSettings:
      type: object
      required: [enabled, push]
      properties:
        enabled:
          type: boolean
        push:
          type: boolean

    RunEnvironmentSettings:
      type: object
      required: [id, provider, image, resources, network, lifecycle, labels, env]
      properties:
        id:
          type: string
        provider:
          $ref: "#/components/schemas/EnvironmentProvider"
        cwd:
          type: ["string", "null"]
          description: Local-provider command working directory for this environment. Docker and Daytona ignore this value.
          example: /srv/fabro/workspaces/team-a
        image:
          $ref: "#/components/schemas/EnvironmentImageSettings"
        resources:
          $ref: "#/components/schemas/EnvironmentResourcesSettings"
        network:
          $ref: "#/components/schemas/EnvironmentNetworkSettings"
        lifecycle:
          $ref: "#/components/schemas/EnvironmentLifecycleSettings"
        labels:
          $ref: "#/components/schemas/StringMap"
        env:
          type: object
          additionalProperties:
            $ref: "#/components/schemas/InterpString"

    EnvironmentSettings:
      type: object
      required: [provider, image, resources, network, lifecycle, labels, env]
      properties:
        provider:
          $ref: "#/components/schemas/EnvironmentProvider"
        cwd:
          type: ["string", "null"]
          description: Local-provider command working directory for this environment. Docker and Daytona ignore this value.
          example: /srv/fabro/workspaces/team-a
        image:
          $ref: "#/components/schemas/EnvironmentImageSettings"
        resources:
          $ref: "#/components/schemas/EnvironmentResourcesSettings"
        network:
          $ref: "#/components/schemas/EnvironmentNetworkSettings"
        lifecycle:
          $ref: "#/components/schemas/EnvironmentLifecycleSettings"
        labels:
          $ref: "#/components/schemas/StringMap"
        env:
          type: object
          additionalProperties:
            $ref: "#/components/schemas/InterpString"

    EnvironmentProvider:
      description: Desired environment provider.
      type: string
      enum: [local, docker, daytona]

    EnvironmentImageSettings:
      type: object
      required: [docker, dockerfile]
      properties:
        docker:
          type: ["string", "null"]
        dockerfile:
          oneOf:
            - $ref: "#/components/schemas/DockerfileSource"
            - type: "null"

    EnvironmentResourcesSettings:
      type: object
      required: [cpu, memory, disk]
      properties:
        cpu:
          type: ["integer", "null"]
          format: int32
        memory:
          type: ["string", "null"]
        disk:
          type: ["string", "null"]

    EnvironmentNetworkSettings:
      type: object
      required: [mode, allow]
      properties:
        mode:
          $ref: "#/components/schemas/EnvironmentNetworkMode"
        allow:
          type: array
          items:
            type: string

    EnvironmentNetworkMode:
      type: string
      enum: [allow_all, block, cidr_allow_list]

    EnvironmentLifecycleSettings:
      type: object
      required: [preserve, stop_on_terminal, auto_stop]
      properties:
        preserve:
          type: boolean
        stop_on_terminal:
          type: boolean
        auto_stop:
          type: ["string", "null"]

    DockerfileSource:
      oneOf:
        - $ref: "#/components/schemas/DockerfileSourceInline"
        - $ref: "#/components/schemas/DockerfileSourcePath"

    DockerfileSourceInline:
      type: object
      required: [type, value]
      properties:
        type:
          type: string
          enum: [inline]
        value:
          type: string

    DockerfileSourcePath:
      type: object
      required: [type, path]
      properties:
        type:
          type: string
          enum: [path]
        path:
          type: string

    NotificationRouteSettings:
      type: object
      required: [enabled, provider, events, slack]
      properties:
        enabled:
          type: boolean
        provider:
          type: ["string", "null"]
        events:
          type: array
          items:
            type: string
        slack:
          oneOf:
            - $ref: "#/components/schemas/NotificationProviderSettings"
            - type: "null"

    NotificationProviderSettings:
      type: object
      required: [channel]
      properties:
        channel:
          type: ["string", "null"]

    RunInterviewsSettings:
      type: object
      required: [provider, slack]
      properties:
        provider:
          type: ["string", "null"]
        slack:
          oneOf:
            - $ref: "#/components/schemas/InterviewProviderSettings"
            - type: "null"

    InterviewProviderSettings:
      type: object
      required: [channel]
      properties:
        channel:
          type: ["string", "null"]

    RunAgentSettings:
      type: object
      required: [fabro_tools, mcps]
      properties:
        fabro_tools:
          type: boolean
        mcps:
          type: object
          additionalProperties:
            $ref: "#/components/schemas/McpServerSettings"

    McpServerSettings:
      type: object
      required: [name, transport, startup_timeout_secs, tool_timeout_secs]
      properties:
        name:
          type: string
        transport:
          $ref: "#/components/schemas/McpTransport"
        startup_timeout_secs:
          type: integer
          format: int64
        tool_timeout_secs:
          type: integer
          format: int64

    McpTransport:
      description: |
        MCP server transport configuration. The `type` field selects stdio,
        HTTP, or sandbox transport. Unknown `type` discriminator values are
        reported as domain validation errors with HTTP 422.
      oneOf:
        - $ref: "#/components/schemas/McpTransportStdio"
        - $ref: "#/components/schemas/McpTransportHttp"
        - $ref: "#/components/schemas/McpTransportSandbox"
      discriminator:
        propertyName: type
        mapping:
          stdio: "#/components/schemas/McpTransportStdio"
          http: "#/components/schemas/McpTransportHttp"
          sandbox: "#/components/schemas/McpTransportSandbox"

    McpTransportStdio:
      description: Stdio transport that launches a local MCP server subprocess.
      type: object
      additionalProperties: false
      required: [type, command, env]
      properties:
        type:
          type: string
          enum: [stdio]
        command:
          type: array
          minItems: 1
          description: Command and arguments used to launch the MCP server.
          items:
            type: string
        env:
          $ref: "#/components/schemas/StringMap"

    McpTransportHttp:
      description: HTTP transport that connects to a remote MCP server URL.
      type: object
      additionalProperties: false
      required: [type, url, headers]
      properties:
        type:
          type: string
          enum: [http]
        protocol:
          $ref: "#/components/schemas/McpHttpProtocol"
        url:
          type: string
          format: uri
        headers:
          $ref: "#/components/schemas/StringMap"

    McpTransportSandbox:
      description: Sandbox transport that launches the MCP server inside the run sandbox and connects over HTTP.
      type: object
      additionalProperties: false
      required: [type, command, port, env]
      properties:
        type:
          type: string
          enum: [sandbox]
        protocol:
          $ref: "#/components/schemas/McpHttpProtocol"
        command:
          type: array
          minItems: 1
          description: Command and arguments used to launch the in-sandbox MCP server.
          items:
            type: string
        port:
          type: integer
          format: int32
          minimum: 1
          maximum: 65535
        env:
          $ref: "#/components/schemas/StringMap"

    McpHttpProtocol:
      description: Wire protocol used by HTTP and sandbox MCP transports.
      type: string
      enum: [streamable_http, sse]
      default: streamable_http

    HookDefinition:
      type: object
      required: [name, event, command, matcher, blocking, timeout_ms, sandbox]
      properties:
        name:
          type: ["string", "null"]
        event:
          $ref: "#/components/schemas/HookEvent"
        command:
          type: ["string", "null"]
        type:
          type: ["string", "null"]
          enum: [command, http, prompt, agent, null]
        url:
          type: ["string", "null"]
        headers:
          oneOf:
            - $ref: "#/components/schemas/StringMap"
            - type: "null"
          description: >-
            Optional HTTP headers for an http hook. Values support
            `{{ vars.NAME }}` interpolation, substituted when the run is
            created; a token left unresolved at fire time blocks the hook
            (fail-closed).
        tls:
          $ref: "#/components/schemas/TlsMode"
        prompt:
          type: ["string", "null"]
        model:
          type: ["string", "null"]
        max_tool_rounds:
          type: ["integer", "null"]
          format: int32
        matcher:
          type: ["string", "null"]
        blocking:
          type: ["boolean", "null"]
        timeout_ms:
          type: ["integer", "null"]
          format: int64
        sandbox:
          type: ["boolean", "null"]

    HookEvent:
      type: string
      enum:
        - run_start
        - run_complete
        - run_failed
        - stage_start
        - stage_complete
        - stage_failed
        - stage_retrying
        - edge_selected
        - parallel_start
        - parallel_complete
        - sandbox_ready
        - sandbox_cleanup
        - checkpoint_saved
        - pre_tool_use
        - post_tool_use
        - post_tool_use_failure

    TlsMode:
      type: string
      enum: [verify, no_verify, off]

    RunScmSettings:
      type: object
      required: [provider, owner, repository, github]
      properties:
        provider:
          type: ["string", "null"]
        owner:
          type: ["string", "null"]
        repository:
          type: ["string", "null"]
        github:
          oneOf:
            - $ref: "#/components/schemas/ScmGitHubSettings"
            - type: "null"

    ScmGitHubSettings:
      type: object

    PullRequestSettings:
      type: object
      required: [enabled, draft, auto_merge, merge_strategy]
      properties:
        enabled:
          type: boolean
        draft:
          type: boolean
        auto_merge:
          type: boolean
        merge_strategy:
          $ref: "#/components/schemas/MergeMethod"

    ArtifactsSettings:
      type: object
      required: [include]
      properties:
        include:
          type: array
          items:
            type: string

    SystemInfoResponse:
      description: Runtime information for the active Fabro server process.
      type: object
      properties:
        version:
          type: string
          description: Server version string.
        server_url:
          type: string
          description: Configured public server URL for browser and CLI authentication flows.
        git_sha:
          type: ["string", "null"]
          description: Build git SHA when available.
        build_date:
          type: ["string", "null"]
          description: Build date when available.
        profile:
          type: ["string", "null"]
          description: Cargo build profile (e.g. `release`, `debug`) when available.
        os:
          type: string
          description: Target operating system.
        arch:
          type: string
          description: Target CPU architecture.
        storage_engine:
          type: string
          description: Backing run storage engine.
        storage_dir:
          type: string
          description: Configured storage directory.
        uptime_secs:
          type: integer
          format: int64
          description: Seconds since this server process started.
        runs:
          $ref: "#/components/schemas/SystemRunCounts"
        sandbox_provider:
          type: string
          description: Effective sandbox provider for launched runs.

    SystemRunCounts:
      description: Counts of known runs in the active server process.
      type: object
      properties:
        total:
          type: integer
          format: int64
          description: Total runs tracked by the server process.
        active:
          type: integer
          format: int64
          description: Runs currently pending, runnable, or executing.
        scheduler_slots_used:
          type: integer
          format: int64
          description: Runs currently occupying scheduler concurrency slots.

    SystemIntegrationsResponse:
      description: Runtime status for server integrations.
      type: object
      required: [data]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/SystemIntegrationStatus"

    SystemIntegrationStatus:
      description: Runtime status for one server integration.
      type: object
      required:
        - provider
        - enabled
        - configured
        - status
        - missing_credentials
        - connection
        - metadata
      properties:
        provider:
          $ref: "#/components/schemas/IntegrationProvider"
        enabled:
          type: boolean
          description: Whether configuration allows this integration to run.
        configured:
          type: boolean
          description: Whether the integration has required non-secret settings and vault credentials.
        status:
          $ref: "#/components/schemas/IntegrationStatus"
        missing_credentials:
          type: array
          description: Missing setting or vault key names required before the integration can run.
          items:
            type: string
        connection:
          oneOf:
            - $ref: "#/components/schemas/IntegrationConnectionStatus"
            - type: "null"
        metadata:
          type: object
          description: Non-secret display hints for the integration.
          additionalProperties:
            type: string

    IntegrationProvider:
      description: Server integration provider.
      type: string
      enum: [github, slack]

    IntegrationStatus:
      description: Computed runtime integration status.
      type: string
      enum:
        - disabled
        - missing_credentials
        - configured
        - connecting
        - connected
        - error

    IntegrationConnectionStatus:
      description: Live connection status for integrations with persistent connections.
      type: object
      required: [kind, status, last_connected_at, last_error]
      properties:
        kind:
          $ref: "#/components/schemas/IntegrationConnectionKind"
        status:
          $ref: "#/components/schemas/IntegrationConnectionState"
        last_connected_at:
          type: ["string", "null"]
          format: date-time
          description: Last time this integration established its live connection.
        last_error:
          type: ["string", "null"]
          description: Sanitized last connection error, if any.

    IntegrationConnectionKind:
      description: Live connection mechanism.
      type: string
      enum: [socket_mode]

    IntegrationConnectionState:
      description: Live connection state.
      type: string
      enum: [connecting, connected, error]

    SystemResourcesResponse:
      description: Server-visible runtime resource usage for the active Fabro process environment.
      type: object
      required: [sampled_at, cpu, memory, disk, notes]
      properties:
        sampled_at:
          type: string
          format: date-time
          description: Timestamp when the sample was collected.
        cpu:
          $ref: "#/components/schemas/SystemCpuResources"
        memory:
          $ref: "#/components/schemas/SystemMemoryResources"
        disk:
          $ref: "#/components/schemas/SystemDiskResources"
        notes:
          type: array
          description: Human-readable caveats about unavailable or scoped metrics.
          items:
            type: string

    SystemCpuResources:
      description: CPU resources visible to the Fabro server process.
      type: object
      required:
        - supported
        - scope
        - unavailable_reason
        - logical_cpus
        - usage_percent
        - sample_window_ms
      properties:
        supported:
          type: boolean
          description: Whether CPU metrics are available on this platform.
        scope:
          $ref: "#/components/schemas/SystemCpuResourceScope"
        unavailable_reason:
          type: ["string", "null"]
          description: Reason metrics are unavailable when unsupported.
        logical_cpus:
          type: ["integer", "null"]
          format: int64
          description: Logical CPUs visible to the server process.
        usage_percent:
          type: ["number", "null"]
          format: double
          description: CPU usage percentage, null until a delta sample is available.
        sample_window_ms:
          type: ["integer", "null"]
          format: int64
          description: Elapsed milliseconds since the previous CPU sample, null until a delta sample is available.

    SystemCpuResourceScope:
      description: Scope for CPU resource metrics.
      type: string
      enum: [server_environment]

    SystemMemoryResources:
      description: Memory resources visible to the Fabro server process.
      type: object
      required:
        - supported
        - scope
        - unavailable_reason
        - total_bytes
        - used_bytes
        - available_bytes
        - used_percent
        - host_total_bytes
      properties:
        supported:
          type: boolean
          description: Whether memory metrics are available on this platform.
        scope:
          $ref: "#/components/schemas/SystemMemoryResourceScope"
        unavailable_reason:
          type: ["string", "null"]
          description: Reason metrics are unavailable when unsupported.
        total_bytes:
          type: ["integer", "null"]
          format: int64
          description: Total memory for the reported scope.
        used_bytes:
          type: ["integer", "null"]
          format: int64
          description: Used memory for the reported scope.
        available_bytes:
          type: ["integer", "null"]
          format: int64
          description: Available memory for the reported scope.
        used_percent:
          type: ["number", "null"]
          format: double
          description: Used memory percentage for the reported scope.
        host_total_bytes:
          type: ["integer", "null"]
          format: int64
          description: Host total memory, included even when reporting a cgroup scope.

    SystemMemoryResourceScope:
      description: "`cgroup` when Linux cgroup limits are available, otherwise `host`."
      type: string
      enum: [host, cgroup]

    SystemDiskResources:
      description: Filesystem resources for the configured Fabro storage root.
      type: object
      required:
        - supported
        - scope
        - unavailable_reason
        - storage_path
        - mount_point
        - filesystem
        - total_bytes
        - used_bytes
        - available_bytes
        - used_percent
        - fabro_managed_bytes
        - fabro_reclaimable_bytes
      properties:
        supported:
          type: boolean
          description: Whether storage filesystem metrics are available.
        scope:
          $ref: "#/components/schemas/SystemDiskResourceScope"
        unavailable_reason:
          type: ["string", "null"]
          description: Reason metrics are unavailable when unsupported.
        storage_path:
          type: string
          description: Configured Fabro storage directory.
        mount_point:
          type: ["string", "null"]
          description: Mount point for the filesystem containing the storage directory.
        filesystem:
          type: ["string", "null"]
          description: Filesystem name reported by the operating system.
        total_bytes:
          type: ["integer", "null"]
          format: int64
          description: Total bytes on the storage filesystem.
        used_bytes:
          type: ["integer", "null"]
          format: int64
          description: Used bytes on the storage filesystem.
        available_bytes:
          type: ["integer", "null"]
          format: int64
          description: Available bytes on the storage filesystem.
        used_percent:
          type: ["number", "null"]
          format: double
          description: Used percentage on the storage filesystem.
        fabro_managed_bytes:
          type: integer
          format: int64
          description: Bytes managed by Fabro under the storage root.
        fabro_reclaimable_bytes:
          type: integer
          format: int64
          description: Bytes reclaimable through Fabro pruning.

    SystemDiskResourceScope:
      description: Scope for disk resource metrics.
      type: string
      enum: [storage_filesystem]

    SystemRepairRunsResponse:
      description: Runs that need manual repair or deletion because they cannot be loaded.
      type: object
      required: [runs, total_count]
      properties:
        runs:
          type: array
          items:
            $ref: "#/components/schemas/SystemRepairRunIssue"
        total_count:
          type: integer
          format: int64
          description: Count of run repair issues.

    SystemRepairRunIssue:
      description: One cataloged run that cannot be loaded from durable storage.
      type: object
      required: [run_id, created_at, error]
      properties:
        run_id:
          type: string
          description: Run identifier.
        created_at:
          type: string
          format: date-time
          description: Timestamp encoded in the run identifier.
        error:
          type: string
          description: Error produced while loading the run projection.

    DiskUsageResponse:
      description: Disk usage summary for server-managed data.
      type: object
      properties:
        summary:
          type: array
          items:
            $ref: "#/components/schemas/DiskUsageSummaryRow"
        total_size_bytes:
          type: integer
          format: int64
          description: Total size of all tracked system data.
        total_reclaimable_bytes:
          type: integer
          format: int64
          description: Total bytes reclaimable by deleting inactive runs and logs.
        runs:
          type: ["array", "null"]
          description: Per-run usage rows when verbose output is requested.
          items:
            $ref: "#/components/schemas/DiskUsageRunRow"

    DiskUsageSummaryRow:
      description: One top-level disk usage category.
      type: object
      properties:
        type:
          type: string
          description: Category name, such as runs or logs.
        count:
          type: integer
          format: int64
          description: Number of items in the category.
        active:
          type: ["integer", "null"]
          format: int64
          description: Number of active items when applicable.
        size_bytes:
          type: integer
          format: int64
          description: Total bytes used by the category.
        reclaimable_bytes:
          type: ["integer", "null"]
          format: int64
          description: Bytes reclaimable by pruning the category.

    DiskUsageRunRow:
      description: Per-run disk usage information.
      type: object
      properties:
        run_id:
          type: string
          description: Run identifier.
        workflow_name:
          type: string
          description: Workflow display name.
        status:
          type: string
          description: Current run status.
        start_time:
          type: string
          description: Human-readable start timestamp.
        size_bytes:
          type: integer
          format: int64
          description: Size used by the run scratch directory.
        reclaimable:
          type: boolean
          description: Whether the run is inactive and reclaimable.

    PruneRunsRequest:
      description: Filters for system run pruning.
      type: object
      properties:
        dry_run:
          type: boolean
          description: Preview matching runs without deleting them.
          default: true
        before:
          type: string
          description: Include runs started before this YYYY-MM-DD prefix.
        workflow:
          type: string
          description: Filter by workflow name substring.
        labels:
          type: object
          additionalProperties:
            type: string
          description: Label filters applied with AND semantics.
        orphans:
          type: boolean
          description: Include orphan run directories without run metadata.
          default: false
        older_than:
          type: string
          description: Include only runs older than this duration, such as 24h or 7d.

    PruneRunsResponse:
      description: Result of a prune preview or deletion.
      type: object
      properties:
        dry_run:
          type: boolean
          description: Whether this response is a dry-run preview.
        runs:
          type: ["array", "null"]
          description: Matched runs when dry-run is enabled.
          items:
            $ref: "#/components/schemas/PruneRunEntry"
        total_count:
          type: integer
          format: int64
          description: Count of runs matching the prune filters.
        total_size_bytes:
          type: integer
          format: int64
          description: Total bytes of the matching runs.
        deleted_count:
          type: integer
          format: int64
          description: Number of runs deleted when dry-run is false.
        freed_bytes:
          type: integer
          format: int64
          description: Estimated freed bytes when deletion occurs.

    PruneRunEntry:
      description: One run matched by a prune preview.
      type: object
      properties:
        run_id:
          type: string
          description: Run identifier.
        dir_name:
          type: string
          description: Scratch directory name for the run.
        workflow_name:
          type: string
          description: Workflow display name.
        size_bytes:
          type: integer
          format: int64
          description: Bytes used by the run scratch directory.

    # ── Discovery Schemas ────────────────────────────────────────────────

    RootResponseUrls:
      description: Collection of API discovery URLs.
      type: object
      required:
        - openapi_url
        - current_user_url
        - health_url
      properties:
        openapi_url:
          type: string
          description: URL of the OpenAPI JSON specification.
          example: /api/v1/openapi.json
        current_user_url:
          type: string
          description: URL of the current user endpoint.
          example: /api/v1/user
        health_url:
          type: string
          description: URL of the health check endpoint.
          example: /health

    RootResponse:
      description: API discovery response with navigation URLs.
      type: object
      required:
        - urls
      properties:
        urls:
          $ref: "#/components/schemas/RootResponseUrls"

    HealthResponse:
      description: Service health check response.
      type: object
      required:
        - status
      properties:
        status:
          type: string
          description: Health status indicator.
          example: ok

    SecretType:
      description: Schema of a stored secret.
      type: string
      enum:
        - token
        - oauth
        - file

    CreateSecretRequest:
      description: Request to store or update a secret.
      type: object
      required:
        - name
        - value
        - type
      properties:
        name:
          type: string
          description: Secret name or destination path for file secrets.
        value:
          type: string
          description: The secret value to store.
        type:
          $ref: "#/components/schemas/SecretType"
        description:
          type: string
          description: Optional operator-facing description of the secret.

    DeleteSecretRequest:
      description: Request to delete a secret by name.
      type: object
      required:
        - name
      properties:
        name:
          type: string
          description: Secret name or destination path for file secrets.

    SecretMetadata:
      description: Metadata for a stored secret (value is never exposed).
      type: object
      required:
        - name
        - type
        - created_at
        - updated_at
      properties:
        name:
          type: string
          description: Secret key name or destination path.
          example: ANTHROPIC_API_KEY
        type:
          $ref: "#/components/schemas/SecretType"
        description:
          type: string
          description: Optional operator-facing description of the secret.
        created_at:
          type: string
          format: date-time
          description: When the secret was first stored.
        updated_at:
          type: string
          format: date-time
          description: When the secret was last updated.

    SecretListResponse:
      description: List of stored secret metadata.
      type: object
      required:
        - data
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/SecretMetadata"

    Variable:
      description: Non-sensitive variable available for run config interpolation.
      type: object
      required:
        - name
        - value
        - created_at
        - updated_at
      properties:
        name:
          type: string
          pattern: "^[A-Za-z_][A-Za-z0-9_]*$"
          description: Env-style variable name.
          example: DEPLOY_ENV
        value:
          type: string
          description: Variable value.
          example: production
        description:
          type: string
          description: Optional operator-facing description of the variable.
        created_at:
          type: string
          format: date-time
          description: When the variable was first stored.
        updated_at:
          type: string
          format: date-time
          description: When the variable was last updated.

    VariableListResponse:
      description: List of stored variables.
      type: object
      required:
        - data
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Variable"

    CreateVariableRequest:
      description: Request to store or update a variable.
      type: object
      required:
        - name
        - value
      properties:
        name:
          type: string
          pattern: "^[A-Za-z_][A-Za-z0-9_]*$"
          description: Env-style variable name.
        value:
          type: string
          description: Variable value. Empty values are allowed.
        description:
          type: string
          description: Optional operator-facing description of the variable.

    UpdateVariableRequest:
      description: Request to update a variable.
      type: object
      required:
        - value
      properties:
        value:
          type: string
          description: Replacement value. Empty values are allowed.
        description:
          type: string
          description: Optional operator-facing description. Omitted descriptions preserve the existing value.

    RepoCheckResponse:
      description: Repository access check result.
      type: object
      required:
        - owner
        - name
        - accessible
      properties:
        owner:
          type: string
          description: GitHub repository owner.
          example: acme-corp
        name:
          type: string
          description: GitHub repository name.
          example: my-app
        accessible:
          type: boolean
          description: Whether the server has read-write access to this repository.
        default_branch:
          type: ["string", "null"]
          description: Default branch name, if accessible.
          example: main
        private:
          type: ["boolean", "null"]
          description: Whether the repository is private, if accessible.
        permissions:
          type: ["object", "null"]
          description: Detected permission levels.
          properties:
            pull:
              type: boolean
            push:
              type: boolean
            admin:
              type: boolean
        install_url:
          type: ["string", "null"]
          description: GitHub App installation URL when the repo is not yet accessible.

    DiagnosticsReport:
      description: Server health diagnostics report.
      type: object
      required:
        - version
        - sections
      properties:
        version:
          type: string
          description: Server version.
        sections:
          type: array
          items:
            $ref: "#/components/schemas/DiagnosticsSection"

    DiagnosticsSection:
      type: object
      required:
        - title
        - checks
      properties:
        title:
          type: string
        checks:
          type: array
          items:
            $ref: "#/components/schemas/DiagnosticsCheck"

    DiagnosticsCheck:
      type: object
      required:
        - name
        - status
        - summary
      properties:
        name:
          type: string
        status:
          type: string
          enum:
            - pass
            - warning
            - error
        summary:
          type: string
        details:
          type: array
          items:
            $ref: "#/components/schemas/DiagnosticsDetail"
        remediation:
          type: ["string", "null"]

    DiagnosticsDetail:
      type: object
      required:
        - text
        - warn
      properties:
        text:
          type: string
        warn:
          type: boolean

    UserResponse:
      description: Information about the authenticated user.
      type: object
      required:
        - login
      properties:
        login:
          type: string
          description: User's login identifier (e.g. GitHub username).
          example: octocat
