openapi: 3.0.3
info:
  title: Hill90 API
  description: Hill90 platform API — agent management and lifecycle operations.
  version: 0.1.0
servers:
  - url: /
    description: Relative (behind Traefik reverse proxy)
components:
  parameters:
    CorrelationId:
      name: X-Correlation-ID
      in: header
      required: false
      schema:
        type: string
        format: uuid
      description: >-
        Request-scoped tracing ID. Generated by API if absent. Echoed in
        response.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
  schemas:
    AgentTask:
      type: object
      properties:
        id:
          type: string
          format: uuid
        agent_id:
          type: string
        title:
          type: string
        description:
          type: string
        status:
          type: string
          enum:
            - backlog
            - todo
            - in_progress
            - review
            - done
            - cancelled
        priority:
          type: integer
          minimum: 1
          maximum: 4
        sort_order:
          type: integer
        tags:
          type: array
          items:
            type: string
        created_by:
          type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    Agent:
      type: object
      properties:
        id:
          type: string
          format: uuid
        agent_id:
          type: string
          description: Unique slug identifier (lowercase, alphanumeric, hyphens)
          pattern: ^[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$
        name:
          type: string
        description:
          type: string
        tools_config:
          type: object
        cpus:
          type: string
          default: '1.0'
        mem_limit:
          type: string
          default: 1g
        pids_limit:
          type: integer
          default: 200
        soul_md:
          type: string
        rules_md:
          type: string
        autonomy_level:
          type: string
          enum:
            - ask_before_acting
            - act_within_scope
            - full_autonomy
          default: act_within_scope
          description: Controls how much freedom the agent has when executing commands.
        status:
          type: string
          enum:
            - stopped
            - running
            - error
        container_id:
          type: string
          nullable: true
        model_policy_id:
          type: string
          format: uuid
          nullable: true
          description: Internal policy reference used by backend model-router plumbing.
        model_policy_name:
          type: string
          nullable: true
          description: Human-readable name of the model policy (resolved via JOIN).
        models:
          type: array
          items:
            type: string
          description: User-facing model names assigned to this agent.
        skills:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                format: uuid
              name:
                type: string
              scope:
                type: string
                enum:
                  - container_local
                  - host_docker
                  - vps_system
              tools:
                type: array
                items:
                  type: object
                  properties:
                    id:
                      type: string
                      format: uuid
                    name:
                      type: string
                    description:
                      type: string
                    install_method:
                      type: string
                      enum:
                        - builtin
                        - apt
                        - binary
          description: Skills assigned to this agent.
        container_profile_id:
          type: string
          format: uuid
          nullable: true
          description: >-
            FK to container_profiles table. Determines Docker image for the
            agent container.
        container_profile:
          type: object
          nullable: true
          description: Resolved container profile object (from LEFT JOIN).
          properties:
            id:
              type: string
              format: uuid
            name:
              type: string
            docker_image:
              type: string
        error_message:
          type: string
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        created_by:
          type: string
        principal_id:
          type: string
          format: uuid
          description: >-
            Stable principal identifier (equals agent UUID). AI-115 workload
            principal model.
        principal_type:
          type: string
          enum:
            - agent
          description: Formal principal type. Always 'agent' for workload principals.
    ContainerProfile:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
          description: Unique profile name (e.g. "standard", "gpu")
        description:
          type: string
        docker_image:
          type: string
          description: Docker image used when starting agent containers with this profile
        default_cpus:
          type: string
        default_mem_limit:
          type: string
        default_pids_limit:
          type: integer
        is_platform:
          type: boolean
          description: >-
            Platform profiles are managed by admins and cannot be deleted by
            users.
        metadata:
          type: object
          description: Profile-specific container configuration (extra_env, shm_size)
          properties:
            extra_env:
              type: array
              items:
                type: string
              description: Additional environment variables injected into the container
            shm_size:
              type: string
              description: Shared memory size for the container (e.g. "256m")
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    ContainerProfileCreate:
      type: object
      required:
        - name
        - docker_image
      properties:
        name:
          type: string
          description: Unique profile name
        description:
          type: string
        docker_image:
          type: string
          description: Docker image for agent containers
        default_cpus:
          type: string
        default_mem_limit:
          type: string
        default_pids_limit:
          type: integer
        metadata:
          type: object
          description: Profile-specific container configuration
    ContainerProfileUpdate:
      type: object
      properties:
        name:
          type: string
        description:
          type: string
        docker_image:
          type: string
        default_cpus:
          type: string
        default_mem_limit:
          type: string
        default_pids_limit:
          type: integer
        metadata:
          type: object
          description: Profile-specific container configuration
    AgentStatus:
      type: object
      properties:
        db_status:
          type: string
        container:
          type: object
          nullable: true
        error_message:
          type: string
          nullable: true
    AgentToolInstallStatus:
      type: object
      properties:
        tool_id:
          type: string
          format: uuid
        tool_name:
          type: string
        tool_description:
          type: string
        status:
          type: string
          enum:
            - pending
            - installing
            - installed
            - failed
        install_message:
          type: string
        installed_at:
          type: string
          format: date-time
          nullable: true
        updated_at:
          type: string
          format: date-time
    UserProfile:
      type: object
      properties:
        username:
          type: string
        firstName:
          type: string
        lastName:
          type: string
        email:
          type: string
        emailVerified:
          type: boolean
        hasAvatar:
          type: boolean
    KnowledgeEntry:
      type: object
      properties:
        id:
          type: string
          format: uuid
        agent_id:
          type: string
        path:
          type: string
        title:
          type: string
        entry_type:
          type: string
          enum:
            - note
            - plan
            - decision
            - journal
            - research
            - notebook
        content:
          type: string
          description: Full markdown content (only included in single-entry reads)
        content_hash:
          type: string
        tags:
          type: array
          items:
            type: string
        status:
          type: string
          enum:
            - active
            - archived
        sync_status:
          type: string
          enum:
            - pending
            - synced
            - quarantined
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    KnowledgeAgent:
      type: object
      properties:
        agent_id:
          type: string
        entry_count:
          type: integer
        last_updated:
          type: string
          format: date-time
    KnowledgeSearchResult:
      type: object
      properties:
        query:
          type: string
        results:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                format: uuid
              agent_id:
                type: string
              path:
                type: string
              title:
                type: string
              entry_type:
                type: string
              tags:
                type: array
                items:
                  type: string
              score:
                type: number
              headline:
                type: string
                description: Search result snippet with highlights
              created_at:
                type: string
                format: date-time
              updated_at:
                type: string
                format: date-time
        count:
          type: integer
        search_type:
          type: string
          example: fts
        score_type:
          type: string
          example: ts_rank
    ModelPolicy:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        description:
          type: string
        allowed_models:
          type: array
          items:
            type: string
        max_requests_per_minute:
          type: integer
          nullable: true
          description: Rate limit (requests per minute). NULL means unlimited.
        max_tokens_per_day:
          type: integer
          nullable: true
          description: Daily token budget. NULL means unlimited.
        model_aliases:
          type: object
          additionalProperties:
            type: string
          nullable: true
          description: Alias map (alias name → real model name). Single-pass resolution.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        updated_by:
          type: string
          nullable: true
        created_by:
          type: string
          nullable: true
          description: Owner (user sub). NULL for platform policies.
    Skill:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        description:
          type: string
        tools_config:
          type: object
          description: JSONB tool configuration (shell, filesystem, health settings)
        instructions_md:
          type: string
          description: >-
            Behavioral guidance merged into agent RULES.md at start time
            (fresh-at-start).
        scope:
          type: string
          enum:
            - container_local
            - host_docker
            - vps_system
          default: container_local
          description: >-
            Scope determines assignment authority. container_local — any user.
            host_docker/vps_system — admin only.
        tools:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                format: uuid
              name:
                type: string
              description:
                type: string
              install_method:
                type: string
                enum:
                  - builtin
                  - apt
                  - binary
        is_platform:
          type: boolean
          description: Platform skills are immutable and undeletable.
        created_by:
          type: string
          nullable: true
          description: Always NULL (admin-created shared resources).
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    Tool:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        description:
          type: string
        install_method:
          type: string
          enum:
            - builtin
            - apt
            - binary
        install_ref:
          type: string
        is_platform:
          type: boolean
        created_at:
          type: string
          format: date-time
    ToolCreate:
      type: object
      required:
        - name
      properties:
        name:
          type: string
        description:
          type: string
        install_method:
          type: string
          enum:
            - builtin
            - apt
            - binary
        install_ref:
          type: string
    UsageSummary:
      type: object
      properties:
        total_requests:
          type: integer
        successful_requests:
          type: integer
        total_input_tokens:
          type: integer
        total_output_tokens:
          type: integer
        total_tokens:
          type: integer
        total_cost_usd:
          type: number
          format: double
    SharedCollection:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        description:
          type: string
        visibility:
          type: string
          enum:
            - private
            - shared
        created_by:
          type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    SharedSource:
      type: object
      properties:
        id:
          type: string
          format: uuid
        collection_id:
          type: string
          format: uuid
        title:
          type: string
        source_type:
          type: string
          enum:
            - text
            - markdown
            - web_page
        source_url:
          type: string
          nullable: true
        content_hash:
          type: string
        status:
          type: string
          enum:
            - pending
            - active
            - error
            - archived
        error_message:
          type: string
          nullable: true
        created_by:
          type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    SharedSearchResult:
      type: object
      properties:
        query:
          type: string
        results:
          type: array
          items:
            type: object
            properties:
              chunk_id:
                type: string
                format: uuid
              content:
                type: string
              chunk_index:
                type: integer
              score:
                type: number
              headline:
                type: string
              source_id:
                type: string
                format: uuid
              source_title:
                type: string
              source_url:
                type: string
                nullable: true
              document_id:
                type: string
                format: uuid
              document_title:
                type: string
              collection_id:
                type: string
                format: uuid
              collection_name:
                type: string
              quality_score:
                type: number
                description: Normalized quality score (0–1)
              quality_label:
                type: string
                enum:
                  - high
                  - medium
                  - low
                description: Quality classification
        count:
          type: integer
        search_type:
          type: string
          example: fts
        score_type:
          type: string
          example: ts_rank
        quality_summary:
          type: object
          properties:
            avg_score:
              type: number
            min_score:
              type: number
            max_score:
              type: number
            distribution:
              type: object
              properties:
                high:
                  type: integer
                medium:
                  type: integer
                low:
                  type: integer
    SharedKnowledgeStats:
      type: object
      description: >-
        Aggregate-only quality and operations metrics. No raw query text, no
        requester identifiers.
      properties:
        search:
          type: object
          properties:
            total:
              type: integer
            zero_result_count:
              type: integer
            zero_result_rate:
              type: number
            avg_duration_ms:
              type: integer
              nullable: true
            by_requester_type:
              type: array
              items:
                type: object
                properties:
                  requester_type:
                    type: string
                  total:
                    type: integer
                  zero_result_count:
                    type: integer
                  zero_result_rate:
                    type: number
        ingest:
          type: object
          properties:
            total_jobs:
              type: integer
            completed:
              type: integer
            failed:
              type: integer
            running:
              type: integer
            pending:
              type: integer
            error_rate:
              type: number
            avg_processing_ms:
              type: integer
              nullable: true
        sources:
          type: object
          properties:
            by_status:
              type: object
              additionalProperties:
                type: integer
            by_type:
              type: object
              additionalProperties:
                type: integer
        corpus:
          type: object
          properties:
            total_collections:
              type: integer
            total_sources:
              type: integer
            total_chunks:
              type: integer
            total_tokens:
              type: integer
        usage:
          type: object
          properties:
            top_collections:
              type: array
              items:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                  name:
                    type: string
                  retrieval_count:
                    type: integer
            top_sources:
              type: array
              items:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                  title:
                    type: string
                  collection_name:
                    type: string
                  retrieval_count:
                    type: integer
        since:
          type: string
          nullable: true
    IngestResult:
      type: object
      properties:
        source:
          $ref: '#/components/schemas/SharedSource'
        ingest_job:
          type: object
          properties:
            id:
              type: string
              format: uuid
            status:
              type: string
            chunk_count:
              type: integer
        document:
          type: object
          properties:
            id:
              type: string
              format: uuid
            title:
              type: string
            chunk_count:
              type: integer
    AgentEvent:
      type: object
      properties:
        id:
          type: string
          description: >
            Event ID. Container events use UUID format. Inference events use
            "inference-<uuid>" prefix to avoid collision with container event
            IDs.
        timestamp:
          type: string
          format: date-time
        type:
          type: string
          enum:
            - command_start
            - command_complete
            - file_read
            - file_write
            - directory_list
            - identity_read
            - health_check
            - inference_complete
            - inference_error
            - inference_rate_limited
            - inference_budget_exceeded
            - inference_client_disconnect
          description: >
            Event type. Container events use command_start/complete, file_*,
            etc. Inference events use inference_complete for success, or
            inference_<status> for non-success statuses (error, rate_limited,
            budget_exceeded, client_disconnect).
        tool:
          type: string
          enum:
            - shell
            - filesystem
            - runtime
            - identity
            - health
            - inference
          description: |
            Tool category. Inference events always have tool=inference.
        input_summary:
          type: string
          description: >
            Truncated human-readable input (max 200 chars). For inference
            events, formatted as "model_name (request_type)".
        output_summary:
          type: string
          nullable: true
          description: >
            Metadata-only result summary. For container events: exit codes, byte
            counts. For inference events: "input_tokens+output_tokens tokens,
            $cost, latency_ms". Never contains raw output or prompt/completion
            content.
        duration_ms:
          type: integer
          nullable: true
        success:
          type: boolean
          nullable: true
        metadata:
          type: object
          nullable: true
          description: >
            Additional structured data. For inference events, contains:
            model_name (string), request_type (string), status (string),
            input_tokens (integer), output_tokens (integer), cost_usd (number).
            No prompt or completion content is included.
    Error:
      type: object
      properties:
        error:
          type: string
    Message:
      type: object
      properties:
        message:
          type: string
    ChatMessage:
      type: object
      properties:
        id:
          type: string
          format: uuid
        seq:
          type: integer
          description: Monotonic sequence number for cursor-based streaming
        author_id:
          type: string
        author_type:
          type: string
          enum:
            - human
            - agent
        role:
          type: string
          enum:
            - user
            - assistant
        content:
          type: string
        status:
          type: string
          enum:
            - pending
            - complete
            - error
        model:
          type: string
          nullable: true
        input_tokens:
          type: integer
          nullable: true
        output_tokens:
          type: integer
          nullable: true
        duration_ms:
          type: integer
          nullable: true
        error_message:
          type: string
          nullable: true
        chain_id:
          type: string
          format: uuid
          nullable: true
          description: Shared identifier for agent-to-agent @mention chains
        chain_hop:
          type: integer
          nullable: true
          description: Hop number within a chain (0 = originator)
        triggered_by:
          type: string
          format: uuid
          nullable: true
          description: ID of the message that triggered this chain dispatch
        created_at:
          type: string
          format: date-time
paths:
  /health:
    get:
      summary: Health check
      description: Returns service health status. Public — no authentication required.
      security: []
      responses:
        '200':
          description: Healthy
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: healthy
                  service:
                    type: string
                    example: api
  /health/detailed:
    get:
      summary: Detailed health check
      description: >
        Returns service health with database connectivity, uptime, memory usage,
        and Node.js version. Public — no authentication required.
      security: []
      responses:
        '200':
          description: Detailed health status
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    enum:
                      - healthy
                      - degraded
                  service:
                    type: string
                    example: api
                  uptime_seconds:
                    type: integer
                  node_version:
                    type: string
                    example: v20.18.0
                  database:
                    type: object
                    properties:
                      status:
                        type: string
                        enum:
                          - connected
                          - error
                      latency_ms:
                        type: integer
                        nullable: true
                  memory:
                    type: object
                    properties:
                      rss_mb:
                        type: integer
                      heap_used_mb:
                        type: integer
                      heap_total_mb:
                        type: integer
  /me:
    get:
      summary: Get current user claims
      description: Returns the JWT claims of the authenticated user.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: JWT claims
          content:
            application/json:
              schema:
                type: object
        '401':
          description: Missing or invalid token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /agents:
    get:
      summary: List agents
      description: Returns agents scoped to the authenticated user (all agents for admin).
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Agent list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Agent'
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
    post:
      summary: Create agent
      description: Creates a new agent definition.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - agent_id
                - name
              properties:
                agent_id:
                  type: string
                  pattern: ^[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$
                name:
                  type: string
                description:
                  type: string
                tools_config:
                  type: object
                cpus:
                  type: string
                mem_limit:
                  type: string
                pids_limit:
                  type: integer
                soul_md:
                  type: string
                rules_md:
                  type: string
                model_policy_id:
                  type: string
                  format: uuid
                  nullable: true
                  description: >-
                    Internal-only override. Prefer model_names for user-facing
                    model assignment.
                model_names:
                  type: array
                  items:
                    type: string
                  description: >-
                    User-facing model names to assign directly to this agent
                    (one or many).
                skill_ids:
                  type: array
                  items:
                    type: string
                    format: uuid
                  description: >-
                    Skill IDs to assign. Config is merged from all skills at
                    save time (resolve-on-save).
                container_profile_id:
                  type: string
                  format: uuid
                  nullable: true
                  description: >-
                    Container profile to assign. Determines Docker image. Omit
                    for default.
                autonomy_level:
                  type: string
                  enum:
                    - ask_before_acting
                    - act_within_scope
                    - full_autonomy
                  description: Controls agent autonomy. Defaults to act_within_scope.
      responses:
        '201':
          description: Agent created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Agent'
        '400':
          description: Invalid input
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
        '409':
          description: Duplicate agent_id
  /agents/templates:
    get:
      summary: List agent templates
      security:
        - bearerAuth: []
      responses:
        '200':
          description: List of predefined agent templates
  /agents/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    get:
      summary: Get agent detail
      description: Returns a single agent, scoped to ownership.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Agent detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Agent'
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
        '404':
          description: Agent not found
    put:
      summary: Update agent
      description: >-
        Updates agent configuration. Agent must be stopped. Users can assign own
        or platform policies to own agents.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                description:
                  type: string
                tools_config:
                  type: object
                cpus:
                  type: string
                mem_limit:
                  type: string
                pids_limit:
                  type: integer
                soul_md:
                  type: string
                rules_md:
                  type: string
                model_policy_id:
                  type: string
                  format: uuid
                  nullable: true
                  description: >-
                    Internal-only override. Prefer model_names for user-facing
                    model assignment.
                model_names:
                  type: array
                  items:
                    type: string
                  description: >-
                    User-facing model names to assign directly to this agent
                    (one or many).
                skill_ids:
                  type: array
                  items:
                    type: string
                    format: uuid
                  description: >-
                    Skill IDs to assign. Empty array to detach all skills
                    (Custom mode). Config is merged from all skills at save
                    time.
                container_profile_id:
                  type: string
                  format: uuid
                  nullable: true
                  description: >-
                    Container profile to assign. Omit for no change; explicit
                    null to clear.
                autonomy_level:
                  type: string
                  enum:
                    - ask_before_acting
                    - act_within_scope
                    - full_autonomy
                  description: Controls agent autonomy. Omit for no change.
      responses:
        '200':
          description: Agent updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Agent'
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
        '404':
          description: Agent not found
        '409':
          description: Agent is running
    delete:
      summary: Delete agent
      description: Deletes an agent. Requires admin role. Optionally purges volumes.
      security:
        - bearerAuth: []
      parameters:
        - name: purge
          in: query
          schema:
            type: string
            enum:
              - 'true'
              - 'false'
          description: If "true", also remove agent volumes
      responses:
        '200':
          description: Agent deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  deleted:
                    type: boolean
        '401':
          description: Not authenticated
        '403':
          description: Requires admin role
        '404':
          description: Agent not found
  /agents/{id}/start:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    post:
      summary: Start agent
      description: Creates and starts the agent container. Requires admin role.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Agent started
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: running
                  container_id:
                    type: string
        '401':
          description: Not authenticated
        '403':
          description: Requires admin role
        '404':
          description: Agent not found
        '503':
          description: AGENTBOX_CONFIG_HOST_PATH not configured
  /agents/{id}/stop:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    post:
      summary: Stop agent
      description: Stops and removes the agent container. Requires admin role.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Agent stopped
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: stopped
        '401':
          description: Not authenticated
        '403':
          description: Requires admin role
        '404':
          description: Agent not found
  /agents/{id}/status:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    get:
      summary: Get agent status
      description: >-
        Returns agent status from DB and live container inspection. Scoped to
        ownership.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Agent status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentStatus'
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
        '404':
          description: Agent not found
  /agents/{id}/tool-installs:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    get:
      summary: Get agent tool install statuses
      description: Returns per-tool installation status for the agent. Scoped to ownership.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Tool install status rows
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/AgentToolInstallStatus'
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
        '404':
          description: Agent not found
  /agents/{id}/reconcile-tools:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    post:
      summary: Reconcile tool installations
      description: >
        Re-evaluates required tools from assigned skills and installs any
        missing tools in the running agent container. Already-installed tools
        are skipped. Stale install records for unassigned tools are cleaned up.
        Requires admin role. Agent must be running.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Reconcile result
          content:
            application/json:
              schema:
                type: object
                properties:
                  installed:
                    type: array
                    items:
                      type: string
                    description: Tools newly installed during this reconcile
                  alreadyInstalled:
                    type: array
                    items:
                      type: string
                    description: Tools that were already installed
                  failed:
                    type: array
                    items:
                      type: string
                    description: Tools that failed to install
        '401':
          description: Not authenticated
        '403':
          description: Requires admin role
        '404':
          description: Agent not found
        '409':
          description: Agent is not running
  /agents/{id}/events:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    get:
      summary: Get agent events
      description: >
        Returns structured tool invocation and inference events for a running
        agent. Container events (shell, filesystem, runtime) come from the JSONL
        event log. Inference events (model_usage) are merged from the database
        with owner scoping. With follow=true, returns an SSE stream with initial
        inference backfill and 3-second polling for new inference rows alongside
        real-time container events. Without follow, returns a merged JSON array
        sorted by (timestamp, id). Scoped to ownership — users see own agents
        and own inference rows only.
      security:
        - bearerAuth: []
      parameters:
        - name: tail
          in: query
          schema:
            type: integer
            default: 100
          description: Number of tail events
        - name: follow
          in: query
          schema:
            type: string
            enum:
              - 'true'
              - 'false'
          description: If "true", stream events via SSE
      responses:
        '200':
          description: Event output (JSON array or SSE stream)
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/AgentEvent'
            text/event-stream:
              schema:
                type: string
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
        '404':
          description: Agent not found
        '409':
          description: Agent is not running
  /agents/{id}/stats:
    get:
      summary: Get agent progression stats
      tags:
        - Agents
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Aggregate stats for agent progression
        '404':
          description: Agent not found
  /agents/{id}/metrics:
    get:
      summary: Get agent operational metrics
      tags:
        - Agents
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Computed operational metrics for the agent
        '404':
          description: Agent not found
  /agents/{id}/artifacts:
    get:
      summary: Get agent artifacts (badges)
      tags:
        - Agents
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: List of artifacts with earned/unearned status
        '404':
          description: Agent not found
  /agents/{id}/status-history:
    get:
      summary: List agent status transitions
      tags:
        - Agents
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
            maximum: 200
      responses:
        '200':
          description: Status history entries ordered by changed_at desc
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    id:
                      type: string
                      format: uuid
                    agent_id:
                      type: string
                      format: uuid
                    old_status:
                      type: string
                    new_status:
                      type: string
                    changed_at:
                      type: string
                      format: date-time
                    changed_by:
                      type: string
                      nullable: true
        '404':
          description: Agent not found
  /agents/{id}/clone:
    post:
      summary: Clone an agent
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '201':
          description: Cloned agent
        '404':
          description: Agent not found
  /agents/{id}/webhooks:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    get:
      summary: List agent webhooks
      security:
        - bearerAuth: []
      responses:
        '200':
          description: List of webhooks
    post:
      summary: Register agent webhook
      security:
        - bearerAuth: []
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required:
                - url
              properties:
                url:
                  type: string
                  format: uri
                events:
                  type: array
                  items:
                    type: string
      responses:
        '201':
          description: Webhook created
  /agents/{id}/webhooks/{webhookId}:
    delete:
      summary: Delete agent webhook
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: webhookId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Webhook deleted
        '404':
          description: Webhook not found
  /agents/{id}/export:
    get:
      summary: Export agent configuration
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Agent configuration JSON
        '404':
          description: Agent not found
  /agents/import:
    post:
      summary: Import agent from exported config
      security:
        - bearerAuth: []
      requestBody:
        content:
          application/json:
            schema:
              type: object
      responses:
        '201':
          description: Agent created from import
  /agents/{id}/avatar:
    post:
      summary: Upload agent avatar
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                avatar:
                  type: string
                  format: binary
      responses:
        '200':
          description: Avatar updated
        '404':
          description: Agent not found
  /agents/{id}/schedule:
    put:
      summary: Set agent auto-start schedule
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Schedule updated
  /agents/{id}/runtime-metrics:
    get:
      summary: Agent runtime CPU/memory metrics
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Runtime metrics
        '404':
          description: Agent not running
  /agents/{id}/workspace:
    get:
      summary: List agent workspace files
      tags:
        - Agents
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: path
          in: query
          schema:
            type: string
          description: Directory path to list (default /home/agentuser)
      responses:
        '200':
          description: Directory listing
          content:
            application/json:
              schema:
                type: object
                properties:
                  path:
                    type: string
                  entries:
                    type: array
                    items:
                      type: object
                      properties:
                        name:
                          type: string
                        type:
                          type: string
                          enum:
                            - file
                            - directory
                            - unknown
                        size:
                          type: integer
        '404':
          description: Agent not found
        '409':
          description: Agent is not running
        '502':
          description: Agentbox unreachable
  /agents/{id}/events/export:
    get:
      summary: Export agent events as CSV
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: format
          in: query
          schema:
            type: string
            enum:
              - csv
      responses:
        '200':
          description: CSV download
  /agents/{id}/journal:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    get:
      summary: List agent journal entries
      description: Returns memory journal entries for context persistence, newest first.
      security:
        - bearerAuth: []
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            default: 100
            maximum: 500
      responses:
        '200':
          description: Journal entries
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    id:
                      type: string
                      format: uuid
                    agent_id:
                      type: string
                      format: uuid
                    entry_type:
                      type: string
                      enum:
                        - observation
                        - decision
                        - plan
                        - note
                        - error
                    content:
                      type: string
                    created_at:
                      type: string
                      format: date-time
        '404':
          description: Agent not found
    post:
      summary: Append journal entry
      description: Add a memory journal entry to an agent for context persistence.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - content
              properties:
                entry_type:
                  type: string
                  enum:
                    - observation
                    - decision
                    - plan
                    - note
                    - error
                  default: observation
                content:
                  type: string
      responses:
        '201':
          description: Entry created
        '400':
          description: Missing content
        '404':
          description: Agent not found
  /agents/{id}/logs:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    get:
      summary: Get agent logs
      description: >
        Returns container logs. Requires admin role. With follow=true, returns
        an SSE stream (text/event-stream). Without follow, returns JSON with log
        text.
      security:
        - bearerAuth: []
      parameters:
        - name: tail
          in: query
          schema:
            type: integer
            default: 200
          description: Number of tail lines
        - name: follow
          in: query
          schema:
            type: string
            enum:
              - 'true'
              - 'false'
          description: If "true", stream logs via SSE
      responses:
        '200':
          description: Log output (JSON or SSE stream)
          content:
            application/json:
              schema:
                type: object
                properties:
                  logs:
                    type: string
            text/event-stream:
              schema:
                type: string
        '401':
          description: Not authenticated
        '403':
          description: Requires admin role
        '404':
          description: Agent not found
  /profile:
    get:
      summary: Get user profile
      description: Returns the authenticated user's Keycloak profile and avatar status.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: User profile
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserProfile'
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
    patch:
      summary: Update display name
      description: Updates firstName and/or lastName via Keycloak Account API.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                firstName:
                  type: string
                  maxLength: 100
                lastName:
                  type: string
                  maxLength: 100
      responses:
        '200':
          description: Updated name
          content:
            application/json:
              schema:
                type: object
                properties:
                  firstName:
                    type: string
                  lastName:
                    type: string
        '400':
          description: Invalid input
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
  /profile/avatar:
    post:
      summary: Upload avatar
      description: Upload or replace avatar image. Resized to 256x256 WebP.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - avatar
              properties:
                avatar:
                  type: string
                  format: binary
      responses:
        '200':
          description: Avatar uploaded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Message'
        '400':
          description: Invalid file
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
    delete:
      summary: Delete avatar
      description: Removes the user's avatar from S3 and clears the DB reference.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Avatar deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Message'
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
        '404':
          description: No avatar found
    get:
      summary: Get avatar image
      description: Streams the user's avatar as image/webp. Supports ETag/If-None-Match.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Avatar image
          content:
            image/webp:
              schema:
                type: string
                format: binary
        '304':
          description: Not modified
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
        '404':
          description: No avatar found
  /profile/password:
    post:
      summary: Change password
      description: >
        Password change endpoint. Currently returns 501 because Keycloak 12+
        removed the /account/credentials/password REST endpoint. A browser-based
        flow via kc_action=UPDATE_PASSWORD will be implemented in a future PR.
      security:
        - bearerAuth: []
      responses:
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
        '501':
          description: Not yet implemented
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Password change not yet available
                  code:
                    type: string
                    example: NOT_IMPLEMENTED
  /profile/preferences:
    get:
      summary: Get user preferences
      tags:
        - Profile
      security:
        - bearerAuth: []
      responses:
        '200':
          description: User preferences object
          content:
            application/json:
              schema:
                type: object
                properties:
                  theme:
                    type: string
                    example: dark
                  notifications_enabled:
                    type: boolean
                  sidebar_collapsed:
                    type: boolean
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
    put:
      summary: Update user preferences (shallow merge)
      tags:
        - Profile
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                theme:
                  type: string
                notifications_enabled:
                  type: boolean
                sidebar_collapsed:
                  type: boolean
      responses:
        '200':
          description: Merged preferences
          content:
            application/json:
              schema:
                type: object
                properties:
                  theme:
                    type: string
                  notifications_enabled:
                    type: boolean
                  sidebar_collapsed:
                    type: boolean
        '400':
          description: Invalid request body
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
  /knowledge/agents:
    get:
      summary: List agents with knowledge
      description: >
        Returns agents that have knowledge entries, with entry counts and last
        updated timestamps. Scoped to ownership — users see their own agents,
        admins see all.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Agent knowledge stats
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/KnowledgeAgent'
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
  /knowledge/entries:
    get:
      summary: List knowledge entries
      description: >
        Returns knowledge entries for a specific agent. Requires agent_id query
        parameter. Users can only list entries for agents they own. Admins can
        list any agent's entries.
      security:
        - bearerAuth: []
      parameters:
        - name: agent_id
          in: query
          required: true
          schema:
            type: string
          description: Agent ID to list entries for
        - name: type
          in: query
          schema:
            type: string
            enum:
              - note
              - plan
              - decision
              - journal
              - research
              - notebook
          description: Optional entry type filter
      responses:
        '200':
          description: Entry list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/KnowledgeEntry'
        '400':
          description: Missing agent_id parameter
        '401':
          description: Not authenticated
        '403':
          description: Not authorized to view this agent's knowledge
  /knowledge/entries/{agentId}/{path}:
    parameters:
      - name: agentId
        in: path
        required: true
        schema:
          type: string
        description: Agent ID
      - name: path
        in: path
        required: true
        schema:
          type: string
        description: Entry path (e.g. notes/test.md)
    get:
      summary: Read knowledge entry
      description: >
        Returns a single knowledge entry with full content. Users can only read
        entries from agents they own. Admins can read any entry.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Entry detail with content
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/KnowledgeEntry'
        '401':
          description: Not authenticated
        '403':
          description: Not authorized to view this agent's knowledge
        '404':
          description: Entry not found
  /knowledge/search:
    get:
      summary: Search knowledge entries
      description: >
        Full-text search across knowledge entries. Without agent_id, searches
        across all owned agents (or all agents for admins). With agent_id,
        searches only that agent's entries (ownership enforced for users).
      security:
        - bearerAuth: []
      parameters:
        - name: q
          in: query
          required: true
          schema:
            type: string
            minLength: 1
          description: Search query
        - name: agent_id
          in: query
          schema:
            type: string
          description: Optional agent ID to scope search
      responses:
        '200':
          description: Search results
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/KnowledgeSearchResult'
        '400':
          description: Missing q parameter
        '401':
          description: Not authenticated
        '403':
          description: Not authorized to search this agent's knowledge
  /shared-knowledge/graph:
    get:
      summary: Knowledge graph — nodes and edges for visualization
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Graph data with collections, sources, and agent nodes
  /shared-knowledge/stats:
    get:
      summary: Shared knowledge quality stats
      description: >
        Returns aggregate quality and operations metrics for shared knowledge.
        Response contains only counts, rates, averages, and status
        distributions. No raw query text, no requester identifiers, no
        cross-user query logs.
      security:
        - bearerAuth: []
      parameters:
        - name: since
          in: query
          schema:
            type: string
            format: date-time
          description: ISO timestamp to scope time-based stats (search, ingest)
      responses:
        '200':
          description: Aggregate stats
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SharedKnowledgeStats'
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
  /shared-knowledge/collections:
    get:
      summary: List shared knowledge collections
      description: >
        Returns shared knowledge collections visible to the authenticated user.
        Users see own private + all shared collections. Admins see all.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Collection list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/SharedCollection'
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
    post:
      summary: Create shared knowledge collection
      description: >-
        Creates a new shared knowledge collection owned by the authenticated
        user.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
              properties:
                name:
                  type: string
                description:
                  type: string
                visibility:
                  type: string
                  enum:
                    - private
                    - shared
                  default: private
      responses:
        '200':
          description: Collection created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SharedCollection'
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
        '409':
          description: Duplicate collection name
  /shared-knowledge/collections/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    get:
      summary: Get shared knowledge collection
      description: >-
        Returns a single collection. Users can see own private + shared. Admins
        see all.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Collection detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SharedCollection'
        '401':
          description: Not authenticated
        '404':
          description: Collection not found
    put:
      summary: Update shared knowledge collection
      description: Updates a collection. Users can only update own collections.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                description:
                  type: string
                visibility:
                  type: string
                  enum:
                    - private
                    - shared
      responses:
        '200':
          description: Collection updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SharedCollection'
        '401':
          description: Not authenticated
        '403':
          description: Not authorized to update this collection
        '404':
          description: Collection not found
    delete:
      summary: Delete shared knowledge collection
      description: >-
        Deletes a collection and all its sources, documents, and chunks
        (cascade).
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Collection deleted
        '401':
          description: Not authenticated
        '403':
          description: Not authorized to delete this collection
        '404':
          description: Collection not found
  /shared-knowledge/sources:
    get:
      summary: List sources in a collection
      description: Returns sources for a given collection. Visibility-scoped.
      security:
        - bearerAuth: []
      parameters:
        - name: collection_id
          in: query
          required: true
          schema:
            type: string
            format: uuid
          description: Collection ID to list sources from
      responses:
        '200':
          description: Source list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/SharedSource'
        '400':
          description: Missing collection_id
        '401':
          description: Not authenticated
        '404':
          description: Collection not found
    post:
      summary: Ingest a source into a collection
      description: >
        Creates a source, runs ingestion (chunking + indexing), and returns the
        source with ingest job summary. V1 supports text and markdown only.
        web_page source type returns 422.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - collection_id
                - title
                - source_type
                - raw_content
              properties:
                collection_id:
                  type: string
                  format: uuid
                title:
                  type: string
                source_type:
                  type: string
                  enum:
                    - text
                    - markdown
                  description: Content type. web_page reserved for Phase 2.
                raw_content:
                  type: string
                  description: The text or markdown content to ingest (max 100KB)
                source_url:
                  type: string
                  description: Optional source URL for attribution
      responses:
        '200':
          description: Source ingested
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IngestResult'
        '401':
          description: Not authenticated
        '403':
          description: Not authorized to add sources to this collection
        '422':
          description: Unsupported source type or invalid content
  /shared-knowledge/sources/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    get:
      summary: Get source by ID
      description: Returns a single source with its metadata, status, and chunk count.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Source details
        '401':
          description: Not authenticated
        '404':
          description: Source not found
    delete:
      summary: Delete source
      description: Deletes a source and its documents/chunks (cascade).
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Source deleted
        '401':
          description: Not authenticated
        '403':
          description: Not authorized to delete this source
        '404':
          description: Source not found
  /shared-knowledge/search:
    get:
      summary: Search shared knowledge
      description: >
        Full-text search across shared knowledge chunks. Results include
        provenance (source, document, collection metadata) and relevance scores.
        Scoped to user's visible collections.
      security:
        - bearerAuth: []
      parameters:
        - name: q
          in: query
          required: true
          schema:
            type: string
            minLength: 1
          description: Search query
        - name: collection_id
          in: query
          schema:
            type: string
            format: uuid
          description: Optional collection filter
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
          description: Max results
      responses:
        '200':
          description: Search results with provenance
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SharedSearchResult'
        '400':
          description: Missing q parameter
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
  /provider-connections:
    get:
      summary: List provider connections
      description: >-
        Returns provider connections owned by the authenticated user. API keys
        are never exposed.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Connection list
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    id:
                      type: string
                      format: uuid
                    name:
                      type: string
                    provider:
                      type: string
                    api_base_url:
                      type: string
                      nullable: true
                    is_valid:
                      type: boolean
                      nullable: true
                    last_validated_at:
                      type: string
                      format: date-time
                      nullable: true
                    last_validation_error:
                      type: string
                      nullable: true
                    validation_latency_ms:
                      type: integer
                      nullable: true
                    created_at:
                      type: string
                      format: date-time
                    updated_at:
                      type: string
                      format: date-time
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
    post:
      summary: Create provider connection
      description: >
        Creates a new provider connection with an encrypted API key. The key is
        encrypted at rest and never returned in responses. Admins can set
        platform=true to create a platform connection (created_by=NULL).
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
                - provider
                - api_key
              properties:
                name:
                  type: string
                provider:
                  type: string
                  description: Provider identifier (e.g., openai, anthropic)
                api_key:
                  type: string
                  description: Provider API key (encrypted at rest, never returned)
                api_base_url:
                  type: string
                  description: Custom API base URL (optional)
                platform:
                  type: boolean
                  description: >-
                    Admin-only. If true, creates a platform connection
                    (created_by=NULL) accessible to all platform models.
      responses:
        '201':
          description: Connection created
        '400':
          description: Invalid input
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
        '409':
          description: Duplicate connection name
  /provider-connections/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    put:
      summary: Update provider connection
      description: >-
        Updates a provider connection. If api_key is provided, it is
        re-encrypted and is_valid is reset.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                provider:
                  type: string
                api_key:
                  type: string
                api_base_url:
                  type: string
                  nullable: true
      responses:
        '200':
          description: Connection updated
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
        '404':
          description: Connection not found
        '409':
          description: Duplicate connection name
    delete:
      summary: Delete provider connection
      description: >-
        Deletes a provider connection and atomically cascades to router models
        referencing it (JSONB route cleanup). Single models are cascaded via FK
        ON DELETE CASCADE.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Connection deleted
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
        '404':
          description: Connection not found
        '500':
          description: Cascade cleanup failed — connection delete rolled back
  /provider-connections/{id}/validate:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    post:
      summary: Validate provider connection
      description: >
        Tests whether the provider key is valid by sending a lightweight request
        through the AI service. Updates is_valid on the connection.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Validation result
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                  is_valid:
                    type: boolean
                  validation_latency_ms:
                    type: integer
                    description: Round-trip validation time in milliseconds
                  error:
                    type: string
                    description: Error message if validation failed
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
        '404':
          description: Connection not found
  /provider-connections/health:
    get:
      summary: Connection health stats
      description: >
        Returns aggregate health statistics for the authenticated user's
        provider connections, including overall counts and per-provider
        breakdown with average latency.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Health statistics
          content:
            application/json:
              schema:
                type: object
                properties:
                  total:
                    type: integer
                  valid:
                    type: integer
                  invalid:
                    type: integer
                  untested:
                    type: integer
                  avg_latency_ms:
                    type: integer
                    nullable: true
                  by_provider:
                    type: array
                    items:
                      type: object
                      properties:
                        provider:
                          type: string
                        total:
                          type: integer
                        valid:
                          type: integer
                        invalid:
                          type: integer
                        untested:
                          type: integer
                        avg_latency_ms:
                          type: integer
                          nullable: true
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
  /provider-connections/validate-all:
    post:
      summary: Validate all connections
      description: >
        Validates all provider connections owned by the authenticated user
        sequentially. Returns per-connection results with latency and error
        details.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Validation results
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          format: uuid
                        name:
                          type: string
                        is_valid:
                          type: boolean
                        validation_latency_ms:
                          type: integer
                        error:
                          type: string
                          description: Error message if validation failed
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
  /provider-connections/{id}/models:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    get:
      summary: List provider models
      description: >
        Returns available models from the provider API for a given connection.
        Each model includes auto-detected type and capabilities.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Provider model list (may include error field on failure)
          content:
            application/json:
              schema:
                type: object
                properties:
                  models:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          description: Provider-prefixed model ID (e.g., openai/gpt-4o)
                        display_name:
                          type: string
                        detected_type:
                          type: string
                          enum:
                            - chat
                            - embedding
                            - audio
                            - image
                            - transcription
                        capabilities:
                          type: array
                          items:
                            type: string
                  provider:
                    type: string
                  error:
                    type: string
                    description: Error message if model listing failed
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
        '404':
          description: Connection not found
  /eligible-models:
    get:
      summary: List eligible models
      description: >
        Returns the caller's own user_models plus platform models (created_by IS
        NULL) that are eligible for use in policies and agent assignments. Only
        active models are returned by default.
      security:
        - bearerAuth: []
      parameters:
        - name: include_inactive
          in: query
          required: false
          schema:
            type: string
            enum:
              - 'true'
              - 'false'
          description: Include inactive user models in the response.
      responses:
        '200':
          description: Eligible models list
          content:
            application/json:
              schema:
                type: object
                properties:
                  models:
                    type: array
                    items:
                      type: object
                      properties:
                        name:
                          type: string
                        description:
                          type: string
                        connection_id:
                          type: string
                          format: uuid
                          nullable: true
                        is_active:
                          type: boolean
                        is_platform:
                          type: boolean
                          description: True if this is a platform-managed model
                        model_type:
                          type: string
                          enum:
                            - single
                            - router
                        detected_type:
                          type: string
        '401':
          description: Unauthorized
        '403':
          description: Forbidden — requires user role
        '503':
          description: Database not configured
  /user-models:
    get:
      summary: List user models
      description: >-
        Returns models owned by the authenticated user plus platform models
        (created_by IS NULL), including single and router models.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Model list
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    id:
                      type: string
                      format: uuid
                    name:
                      type: string
                    connection_id:
                      type: string
                      format: uuid
                      nullable: true
                      description: Null for router models
                    litellm_model:
                      type: string
                      nullable: true
                      description: Null for router models
                    description:
                      type: string
                    is_active:
                      type: boolean
                    is_platform:
                      type: boolean
                      description: >-
                        True if this is a platform-managed model (created_by IS
                        NULL)
                    model_type:
                      type: string
                      enum:
                        - single
                        - router
                    detected_type:
                      type: string
                      description: >-
                        Auto-detected model type (chat, embedding, audio, image,
                        transcription)
                    capabilities:
                      type: array
                      items:
                        type: string
                    routing_config:
                      type: object
                      nullable: true
                      description: Router configuration (null for single models)
                      properties:
                        strategy:
                          type: string
                          enum:
                            - fallback
                            - task_routing
                        default_route:
                          type: string
                        routes:
                          type: array
                          items:
                            type: object
                            properties:
                              key:
                                type: string
                              connection_id:
                                type: string
                                format: uuid
                              litellm_model:
                                type: string
                              detected_type:
                                type: string
                              capabilities:
                                type: array
                                items:
                                  type: string
                              task_types:
                                type: array
                                items:
                                  type: string
                              priority:
                                type: integer
                    icon_emoji:
                      type: string
                      nullable: true
                      deprecated: true
                      description: >-
                        Deprecated — always null on new writes. Use icon_url
                        instead.
                    icon_url:
                      type: string
                      nullable: true
                    created_at:
                      type: string
                      format: date-time
                    updated_at:
                      type: string
                      format: date-time
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
    post:
      summary: Create user model
      description: >
        Creates a user-defined model. For single models, provide connection_id
        and litellm_model. For router models, provide model_type='router' and
        routing_config. Name must not conflict with active platform models.
        Admins can set platform=true to create a platform model
        (created_by=NULL).
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
              properties:
                name:
                  type: string
                  description: Friendly model name (unique per user)
                model_type:
                  type: string
                  enum:
                    - single
                    - router
                  default: single
                connection_id:
                  type: string
                  format: uuid
                  description: >-
                    Required for single models (must be owned by user, or
                    platform connection for platform models)
                litellm_model:
                  type: string
                  description: Required for single models (e.g., openai/gpt-4o)
                routing_config:
                  type: object
                  description: Required for router models
                description:
                  type: string
                detected_type:
                  type: string
                  description: Manual override for auto-detected type
                capabilities:
                  type: array
                  items:
                    type: string
                  description: Manual override for auto-detected capabilities
                platform:
                  type: boolean
                  description: >-
                    Admin-only. If true, creates a platform model
                    (created_by=NULL) using a platform connection.
                icon_emoji:
                  type: string
                  deprecated: true
                  description: Deprecated — ignored on writes. Use icon_url instead.
                icon_url:
                  type: string
      responses:
        '201':
          description: Model created
        '400':
          description: Invalid input or unowned connection
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
        '409':
          description: Name conflict with platform model or duplicate name
  /user-models/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    put:
      summary: Update user model
      description: >
        Updates a user-defined model. Supports type transitions (single to
        router and vice versa). Name collision checks apply on rename.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                model_type:
                  type: string
                  enum:
                    - single
                    - router
                connection_id:
                  type: string
                  format: uuid
                litellm_model:
                  type: string
                routing_config:
                  type: object
                description:
                  type: string
                is_active:
                  type: boolean
                detected_type:
                  type: string
                capabilities:
                  type: array
                  items:
                    type: string
                icon_emoji:
                  type: string
                  deprecated: true
                  description: Deprecated — ignored on writes. Use icon_url instead.
                icon_url:
                  type: string
      responses:
        '200':
          description: Model updated
        '400':
          description: Invalid input or unowned connection
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
        '404':
          description: Model not found
        '409':
          description: Name conflict
    delete:
      summary: Delete user model
      description: Deletes a user-defined model.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Model deleted
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
        '404':
          description: Model not found
  /model-policies:
    get:
      summary: List model policies
      description: Returns model policies. Admins see all; users see own + platform.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Policy list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ModelPolicy'
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
    post:
      summary: Create model policy
      description: >-
        Creates a new model policy. Admin policies are platform-scoped; user
        policies are owner-scoped. model_aliases can only be set by admins.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
                - allowed_models
              properties:
                name:
                  type: string
                description:
                  type: string
                allowed_models:
                  type: array
                  items:
                    type: string
                max_requests_per_minute:
                  type: integer
                  nullable: true
                max_tokens_per_day:
                  type: integer
                  nullable: true
                model_aliases:
                  type: object
                  additionalProperties:
                    type: string
                  description: >-
                    Alias map (admin-only). Each key maps to an allowed_models
                    entry.
      responses:
        '201':
          description: Policy created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ModelPolicy'
        '400':
          description: Invalid input
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
        '409':
          description: Duplicate policy name
  /model-policies/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    get:
      summary: Get model policy
      description: Returns a single model policy. Users can see own + platform policies.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Policy detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ModelPolicy'
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
        '404':
          description: Policy not found
    put:
      summary: Update model policy
      description: >-
        Updates a model policy. Users can update own policies; admins can update
        any. model_aliases can only be set by admins.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                description:
                  type: string
                allowed_models:
                  type: array
                  items:
                    type: string
                max_requests_per_minute:
                  type: integer
                  nullable: true
                max_tokens_per_day:
                  type: integer
                  nullable: true
                model_aliases:
                  type: object
                  additionalProperties:
                    type: string
                  description: >-
                    Alias map (admin-only). Each key maps to an allowed_models
                    entry.
      responses:
        '200':
          description: Policy updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ModelPolicy'
        '401':
          description: Not authenticated
        '403':
          description: Requires user role (model_aliases requires admin)
        '404':
          description: Policy not found
        '409':
          description: Duplicate policy name
    delete:
      summary: Delete model policy
      description: >-
        Deletes a model policy. Fails if agents are assigned. Users can delete
        own; admins can delete any.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Policy deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  deleted:
                    type: boolean
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
        '404':
          description: Policy not found
        '409':
          description: Agents are assigned to this policy
  /usage:
    get:
      summary: Query usage
      description: >
        Query model usage with optional filtering by agent, model, request type,
        and date range. Supports grouping by agent, model, day, or request type.
        Users see own usage only (via owner column); admins see all.
      security:
        - bearerAuth: []
      parameters:
        - name: agent_id
          in: query
          schema:
            type: string
          description: Filter by agent ID
        - name: model_name
          in: query
          schema:
            type: string
          description: Filter by model name
        - name: request_type
          in: query
          schema:
            type: string
            enum:
              - chat.completion
              - embedding
          description: Filter by request type
        - name: from
          in: query
          schema:
            type: string
            format: date
          description: Start date (defaults to today)
        - name: to
          in: query
          schema:
            type: string
            format: date
          description: End date (inclusive)
        - name: group_by
          in: query
          schema:
            type: string
            enum:
              - agent
              - model
              - day
              - request_type
          description: Group results by dimension
      responses:
        '200':
          description: Usage data
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/UsageSummary'
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: '#/components/schemas/UsageSummary'
                      group_by:
                        type: string
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
  /skills:
    get:
      summary: List skills
      description: Returns all skills. All authenticated users see all skills.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Skill list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Skill'
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
    post:
      summary: Create skill
      description: Creates a new skill. Admin only.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
              properties:
                name:
                  type: string
                description:
                  type: string
                tools_config:
                  type: object
                  description: >-
                    Tool configuration (shell, filesystem, health settings).
                    Optional — when omitted, auto-derived from scope.
                instructions_md:
                  type: string
                  description: >-
                    Behavioral guidance merged into agent RULES.md at start
                    time.
                scope:
                  type: string
                  enum:
                    - container_local
                    - host_docker
                    - vps_system
                  default: container_local
                  description: Scope determines assignment authority.
                tool_ids:
                  type: array
                  items:
                    type: string
                    format: uuid
                  description: Tool IDs to associate with this skill
      responses:
        '201':
          description: Skill created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Skill'
        '400':
          description: Invalid input
        '401':
          description: Not authenticated
        '403':
          description: Requires admin role
        '409':
          description: Duplicate skill name
  /skills/graph:
    get:
      summary: Skill-tool dependency graph
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Nodes and edges
          content:
            application/json:
              schema:
                type: object
                properties:
                  nodes:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                        name:
                          type: string
                        type:
                          type: string
                          enum:
                            - skill
                            - tool
                        scope:
                          type: string
                        is_platform:
                          type: boolean
                  edges:
                    type: array
                    items:
                      type: object
                      properties:
                        source:
                          type: string
                        target:
                          type: string
  /skills/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    get:
      summary: Get skill
      description: Returns a single skill. All authenticated users can read.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Skill detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Skill'
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
        '404':
          description: Skill not found
    put:
      summary: Update skill
      description: Updates a skill. Admin only. Platform skills are immutable (403).
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                description:
                  type: string
                tools_config:
                  type: object
                instructions_md:
                  type: string
                scope:
                  type: string
                  enum:
                    - container_local
                    - host_docker
                    - vps_system
                tool_ids:
                  type: array
                  items:
                    type: string
                    format: uuid
                  description: Tool IDs to associate with this skill
      responses:
        '200':
          description: Skill updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Skill'
        '401':
          description: Not authenticated
        '403':
          description: Requires admin role or platform skill is immutable
        '404':
          description: Skill not found
        '409':
          description: Duplicate skill name
    delete:
      summary: Delete skill
      description: >-
        Deletes a skill. Admin only. Platform skills cannot be deleted. Skills
        assigned to agents cannot be deleted.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Skill deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  deleted:
                    type: boolean
        '401':
          description: Not authenticated
        '403':
          description: Requires admin role or platform skill is undeletable
        '404':
          description: Skill not found
        '409':
          description: Skill is assigned to agents
  /container-profiles:
    get:
      summary: List container profiles
      description: >-
        Returns all container profiles. Used by agent form to populate the
        profile selector.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Container profile list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ContainerProfile'
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
    post:
      summary: Create container profile
      description: >-
        Creates a new container profile. Admin only. API-created profiles always
        have is_platform=false.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ContainerProfileCreate'
      responses:
        '201':
          description: Container profile created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContainerProfile'
        '400':
          description: Missing required fields (name or docker_image)
        '401':
          description: Not authenticated
        '403':
          description: Requires admin role
        '409':
          description: Duplicate profile name
  /container-profiles/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    get:
      summary: Get container profile
      description: Returns a single container profile by ID.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Container profile detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContainerProfile'
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
        '404':
          description: Container profile not found
    put:
      summary: Update container profile
      description: >-
        Updates a container profile. Admin only. is_platform cannot be changed
        via API.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ContainerProfileUpdate'
      responses:
        '200':
          description: Container profile updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContainerProfile'
        '401':
          description: Not authenticated
        '403':
          description: Requires admin role
        '404':
          description: Container profile not found
        '409':
          description: Duplicate profile name
    delete:
      summary: Delete container profile
      description: >-
        Deletes a container profile. Admin only. Platform profiles cannot be
        deleted. Profiles assigned to agents cannot be deleted.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Container profile deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  deleted:
                    type: boolean
        '401':
          description: Not authenticated
        '403':
          description: Requires admin role or platform profile is undeletable
        '404':
          description: Container profile not found
        '409':
          description: Profile is assigned to agents
  /tools:
    get:
      summary: List tools
      description: Returns all tools in the catalog.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Tool list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Tool'
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
    post:
      summary: Create tool
      description: Creates a new tool. Admin only.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ToolCreate'
      responses:
        '201':
          description: Tool created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Tool'
        '400':
          description: Invalid input
        '401':
          description: Not authenticated
        '403':
          description: Requires admin role
        '409':
          description: Duplicate tool name
  /tools/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    get:
      summary: Get tool
      description: Returns a single tool. All authenticated users can read.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Tool detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Tool'
        '401':
          description: Not authenticated
        '403':
          description: Requires user role
        '404':
          description: Tool not found
    put:
      summary: Update tool
      description: Updates a tool. Admin only. Platform tools are immutable (403).
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ToolCreate'
      responses:
        '200':
          description: Tool updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Tool'
        '401':
          description: Not authenticated
        '403':
          description: Requires admin role or platform tool is immutable
        '404':
          description: Tool not found
    delete:
      summary: Delete tool
      description: >-
        Deletes a tool. Admin only. Platform tools cannot be deleted. Tools
        referenced by skills cannot be deleted.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Tool deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  deleted:
                    type: boolean
        '401':
          description: Not authenticated
        '403':
          description: Requires admin role or platform tool is undeletable
        '404':
          description: Tool not found
        '409':
          description: Tool is referenced by a skill
  /agents/{id}/skills:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    post:
      summary: Assign skill to agent
      description: >
        Assigns a skill from the catalog to an agent. Agent must be stopped.

        RBAC on scope: container_local — any user (own agents).
        host_docker/vps_system — admin only.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - skill_id
              properties:
                skill_id:
                  type: string
                  format: uuid
                  description: ID of the skill (tool preset) to assign
      responses:
        '201':
          description: Skill assigned
          content:
            application/json:
              schema:
                type: object
                properties:
                  agent_id:
                    type: string
                    format: uuid
                  skill_id:
                    type: string
                    format: uuid
                  assigned_at:
                    type: string
                    format: date-time
                  assigned_by:
                    type: string
        '400':
          description: Missing skill_id
        '401':
          description: Not authenticated
        '403':
          description: Elevated scope requires admin role
        '404':
          description: Agent or skill not found
        '409':
          description: Agent is running or skill already assigned
  /agents/{id}/skills/{skillId}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      - name: skillId
        in: path
        required: true
        schema:
          type: string
          format: uuid
    delete:
      summary: Remove skill from agent
      description: >
        Removes a skill assignment from an agent. Agent must be stopped.

        RBAC on scope: container_local — any user (own agents).
        host_docker/vps_system — admin only.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Skill removed
          content:
            application/json:
              schema:
                type: object
                properties:
                  removed:
                    type: boolean
        '401':
          description: Not authenticated
        '403':
          description: Elevated scope requires admin role
        '404':
          description: Agent, skill, or assignment not found
        '409':
          description: Agent is running
  /chat/threads:
    get:
      summary: List chat threads
      description: |
        Lists threads the caller participates in. Admin sees all threads.
        Returns last message preview (truncated to 100 chars).
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Thread list
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    id:
                      type: string
                      format: uuid
                    type:
                      type: string
                      enum:
                        - direct
                        - group
                    title:
                      type: string
                      nullable: true
                    created_by:
                      type: string
                    lead_agent_id:
                      type: string
                      format: uuid
                      nullable: true
                      description: Lead agent for collaborative dispatch (null = broadcast)
                    last_message:
                      type: string
                      nullable: true
                    last_author_type:
                      type: string
                      nullable: true
                    created_at:
                      type: string
                      format: date-time
                    updated_at:
                      type: string
                      format: date-time
        '401':
          description: Not authenticated
    post:
      summary: Create thread
      description: >
        Creates a new thread and sends the first message. Supports both direct

        (single agent_id) and group (agent_ids array) creation. For group
        threads,

        dispatches to all running agents and returns three-array response

        (dispatched/skipped/failed). Max 8 agents per group.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                agent_id:
                  type: string
                  format: uuid
                  description: Single agent UUID (creates direct thread)
                agent_ids:
                  type: array
                  items:
                    type: string
                    format: uuid
                  description: Multiple agent UUIDs (creates group thread)
                  maxItems: 8
                message:
                  type: string
                title:
                  type: string
                  nullable: true
                lead_agent_id:
                  type: string
                  format: uuid
                  nullable: true
                  description: |
                    Designate a lead agent for collaborative group threads.
                    Must be one of the agent_ids. When set, only the lead agent
                    receives dispatch with collaborator context. Other agents
                    serve as queryable collaborators.
                idempotency_key:
                  type: string
                  nullable: true
              required:
                - message
      responses:
        '201':
          description: Thread created and message dispatched
          content:
            application/json:
              schema:
                type: object
                properties:
                  thread:
                    type: object
                  message_id:
                    type: string
                    format: uuid
                    description: Placeholder ID (direct thread)
                  user_message:
                    type: object
                    description: User message info (group thread)
                  dispatched:
                    type: array
                    description: Agents dispatched (group thread)
                  skipped:
                    type: array
                    description: Agents skipped (group thread)
                  failed:
                    type: array
                    description: Agents failed (group thread)
        '400':
          description: Missing or invalid fields, or exceeds max agents
        '403':
          description: Elevated scope requires admin role
        '404':
          description: Agent not found
        '409':
          description: Agent not running or duplicate idempotency key
  /chat/threads/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    get:
      summary: Get thread detail with messages
      description: |
        Returns thread metadata, participants, and all messages.
        Requires participant membership or admin role.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Thread with messages
        '404':
          description: Thread not found or not a participant
    put:
      summary: Update thread
      description: >
        Update thread title and/or lead agent. Requires thread owner or admin
        role.

        Set lead_agent_id to enable collaborative mode (lead agent produces
        single

        combined response). Set to null to revert to broadcast dispatch.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                title:
                  type: string
                  nullable: true
                lead_agent_id:
                  type: string
                  format: uuid
                  nullable: true
                  description: Set lead agent for collaborative dispatch (null to clear)
      responses:
        '200':
          description: Thread updated
        '400':
          description: lead_agent_id not an active participant
        '404':
          description: Thread not found or not owner
    delete:
      summary: Delete thread
      description: Deletes thread and cascades to messages and participants.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Thread deleted
        '404':
          description: Thread not found or not owner
  /chat/threads/{id}/messages:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    post:
      summary: Send message in thread
      description: |
        Sends a user message and dispatches work to the agent.
        Pre-checks: agent running, concurrency guard, elevated scope check.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - message
              properties:
                message:
                  type: string
                idempotency_key:
                  type: string
                  nullable: true
      responses:
        '201':
          description: Message sent
          content:
            application/json:
              schema:
                type: object
                properties:
                  message_id:
                    type: string
                    format: uuid
        '400':
          description: Missing or invalid fields
        '403':
          description: Elevated scope requires admin role
        '404':
          description: Thread not found or not a participant
        '409':
          description: Agent not running, concurrency guard, or duplicate
  /chat/threads/{id}/participants:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    put:
      summary: Add or remove agent participants
      description: >
        Add or remove agent participants from a group thread. Only the thread
        owner

        or admin can modify participants. Removing an agent sets left_at and
        marks

        any pending messages from that agent as error.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                add:
                  type: array
                  items:
                    type: string
                    format: uuid
                  description: Agent UUIDs to add
                remove:
                  type: array
                  items:
                    type: string
                    format: uuid
                  description: Agent UUIDs to remove
      responses:
        '200':
          description: Updated participant list
          content:
            application/json:
              schema:
                type: object
                properties:
                  participants:
                    type: array
                    items:
                      type: object
        '400':
          description: Exceeds max agents per group
        '403':
          description: Elevated scope requires admin role
        '404':
          description: Thread not found or not owner
  /chat/threads/{id}/cancel:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    post:
      summary: Cancel all pending messages
      description: |
        Marks all pending messages in the thread as error with message
        'Cancelled by user'. Bumps seq for SSE visibility.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Cancellation result
          content:
            application/json:
              schema:
                type: object
                properties:
                  cancelled:
                    type: integer
                    description: Number of messages cancelled
        '404':
          description: Thread not found or not a participant
  /chat/threads/{id}/stream:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    get:
      summary: SSE message stream
      description: >
        Server-Sent Events stream with DB-backed cursor replay via
        Last-Event-ID.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: SSE event stream
          content:
            text/event-stream:
              schema:
                type: string
        '404':
          description: Thread not found or not a participant
  /chat/threads/{id}/events:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    get:
      summary: Thread-scoped agent event stream
      description: |
        Server-side validated, correlation-filtered event stream for all running
        agents in the thread. Validates participant membership, resolves active
        agents, proxies their event streams, and filters to events whose
        correlation_id matches a message_id in this thread. Returns SSE when
        follow=true, JSON array otherwise.
      security:
        - bearerAuth: []
      parameters:
        - name: follow
          in: query
          schema:
            type: string
            enum:
              - 'true'
          description: Enable SSE streaming mode
        - name: tail
          in: query
          schema:
            type: integer
            default: 20
          description: Number of historical events to include
      responses:
        '200':
          description: Event stream (SSE) or event array (JSON)
        '404':
          description: Thread not found or not a participant
        '409':
          description: No running agents in thread
  /chat/threads/{id}/screenshot:
    get:
      summary: Latest browser screenshot for thread agent
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Screenshot data
          content:
            application/json:
              schema:
                type: object
                properties:
                  screenshot:
                    type: string
                  url:
                    type: string
                    nullable: true
        '404':
          description: No running agent or no browser active
  /chat/threads/{id}/browser-click:
    post:
      summary: Forward a user click to the agent browser
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required:
                - x_percent
                - y_percent
              properties:
                x_percent:
                  type: number
                y_percent:
                  type: number
      responses:
        '200':
          description: Click executed
        '404':
          description: Browser not active
  /chat/threads/{id}/browser-element:
    post:
      summary: Get DOM element at coordinates (Describe mode)
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Element info
        '404':
          description: Browser not active
  /chat/threads/{id}/browser-navigate:
    post:
      summary: Navigate browser to URL
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Navigated
        '404':
          description: Browser not active
  /chat/threads/{id}/browser-history:
    post:
      summary: Navigate browser history (back/forward/reload)
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: History navigation complete
        '404':
          description: Browser not active
  /chat/threads/{id}/browser-scroll:
    post:
      summary: Scroll the browser page
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Scrolled
        '404':
          description: Browser not active
  /chat/threads/{id}/browser-type:
    post:
      summary: Type text into the focused element
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Text typed
        '404':
          description: Browser not active
  /chat/threads/{id}/browser-keypress:
    post:
      summary: Press a keyboard key (Enter, Tab, Escape, etc.)
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Key pressed
        '404':
          description: Browser not active
  /chat/threads/{id}/search:
    get:
      summary: Search messages in a thread
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: q
          in: query
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Matching messages
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          format: uuid
                        seq:
                          type: integer
                        author_id:
                          type: string
                        author_type:
                          type: string
                          enum:
                            - human
                            - agent
                        content:
                          type: string
                        headline:
                          type: string
                          description: FTS headline with **highlights**
                        created_at:
                          type: string
                          format: date-time
                  query:
                    type: string
                  total:
                    type: integer
        '400':
          description: Missing q parameter
        '404':
          description: Thread not found
  /tasks:
    get:
      summary: List tasks
      tags:
        - Tasks
      security:
        - bearerAuth: []
      parameters:
        - name: agent_id
          in: query
          schema:
            type: string
          description: Optional agent ID filter
        - name: status
          in: query
          schema:
            type: string
            enum:
              - backlog
              - todo
              - in_progress
              - review
              - done
              - cancelled
          description: Optional status filter
      responses:
        '200':
          description: Task list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/AgentTask'
    post:
      summary: Create task
      tags:
        - Tasks
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - agent_id
                - title
              properties:
                agent_id:
                  type: string
                title:
                  type: string
                description:
                  type: string
                status:
                  type: string
                  enum:
                    - backlog
                    - todo
                    - in_progress
                    - review
                    - done
                    - cancelled
                priority:
                  type: integer
                  minimum: 1
                  maximum: 4
                tags:
                  type: array
                  items:
                    type: string
      responses:
        '200':
          description: Created task
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentTask'
  /tasks/{id}:
    get:
      summary: Get task
      tags:
        - Tasks
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Task detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentTask'
        '404':
          description: Task not found
    put:
      summary: Update task
      tags:
        - Tasks
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                title:
                  type: string
                description:
                  type: string
                status:
                  type: string
                  enum:
                    - backlog
                    - todo
                    - in_progress
                    - review
                    - done
                    - cancelled
                priority:
                  type: integer
                  minimum: 1
                  maximum: 4
                tags:
                  type: array
                  items:
                    type: string
      responses:
        '200':
          description: Updated task
        '404':
          description: Task not found
    delete:
      summary: Cancel task
      tags:
        - Tasks
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Cancelled task
        '404':
          description: Task not found
  /tasks/{id}/transition:
    patch:
      summary: Transition task status
      tags:
        - Tasks
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - status
              properties:
                status:
                  type: string
                  enum:
                    - backlog
                    - todo
                    - in_progress
                    - review
                    - done
                    - cancelled
      responses:
        '200':
          description: Transitioned task
        '404':
          description: Task not found
  /notifications:
    get:
      summary: List notifications for current user
      security:
        - bearerAuth: []
      responses:
        '200':
          description: List of notifications
  /notifications/{id}/read:
    put:
      summary: Mark notification as read
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Marked as read
  /notifications/read-all:
    put:
      summary: Mark all notifications as read
      security:
        - bearerAuth: []
      responses:
        '200':
          description: All marked as read
  /admin/secrets:
    get:
      summary: List vault secrets inventory
      description: >
        Returns secrets schema grouped by vault path with key names and consumer
        service mappings. Admin only. NEVER returns secret values.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Grouped secrets inventory
          content:
            application/json:
              schema:
                type: object
                properties:
                  paths:
                    type: array
                    items:
                      type: object
                      properties:
                        path:
                          type: string
                          example: secret/shared/database
                        keys:
                          type: array
                          items:
                            type: object
                            properties:
                              key:
                                type: string
                                example: DB_PASSWORD
                              consumers:
                                type: array
                                items:
                                  type: string
                                example:
                                  - db
                                  - api
                                  - ai
                        keyCount:
                          type: integer
                          example: 3
                  totalPaths:
                    type: integer
                  totalKeys:
                    type: integer
                  approleServices:
                    type: array
                    items:
                      type: string
        '401':
          description: Not authenticated
        '403':
          description: Requires admin role
        '503':
          description: Failed to load secrets schema
  /admin/secrets/status:
    get:
      summary: Vault seal status
      description: >
        Returns vault health status. Degrades gracefully if vault is
        unreachable. Admin only.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Vault status
          content:
            application/json:
              schema:
                type: object
                properties:
                  available:
                    type: boolean
                  sealed:
                    type: boolean
                    nullable: true
                  initialized:
                    type: boolean
                    nullable: true
                  version:
                    type: string
                    nullable: true
                  cluster_name:
                    type: string
                    nullable: true
        '401':
          description: Not authenticated
        '403':
          description: Requires admin role
  /admin/secrets/kv:
    put:
      summary: Create or update a vault secret key
      description: >
        Write a key-value pair to a vault KV v2 path. Merges with existing keys.
        Requires BAO_TOKEN configured on API service. Admin only.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - path
                - key
                - value
              properties:
                path:
                  type: string
                  description: Vault path (e.g. secret/shared/database)
                key:
                  type: string
                  description: Secret key name
                value:
                  type: string
                  description: Secret value
      responses:
        '200':
          description: Secret saved
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                  path:
                    type: string
                  key:
                    type: string
        '400':
          description: Missing required fields
        '401':
          description: Not authenticated
        '403':
          description: Requires admin role
        '502':
          description: Vault write failed
        '503':
          description: Vault token not configured
    delete:
      summary: Delete a vault secret key
      description: >
        Remove a key from a vault KV v2 path. If the last key is removed, the
        entire path is deleted. Admin only.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - path
                - key
              properties:
                path:
                  type: string
                  description: Vault path (e.g. secret/shared/database)
                key:
                  type: string
                  description: Secret key name to delete
      responses:
        '200':
          description: Secret deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                  path:
                    type: string
                  key:
                    type: string
        '400':
          description: Missing required fields
        '401':
          description: Not authenticated
        '403':
          description: Requires admin role
        '502':
          description: Vault delete failed
        '503':
          description: Vault token not configured
  /storage/buckets:
    get:
      summary: List MinIO buckets
      tags:
        - Storage
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Array of buckets
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    name:
                      type: string
                    created_at:
                      type: string
                      nullable: true
        '401':
          description: Not authenticated
        '403':
          description: Requires admin role
        '502':
          description: Storage service unavailable
  /storage/buckets/{name}/objects:
    get:
      summary: List objects in a bucket
      tags:
        - Storage
      security:
        - bearerAuth: []
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
        - name: prefix
          in: query
          schema:
            type: string
        - name: max_keys
          in: query
          schema:
            type: integer
            default: 100
        - name: continuation_token
          in: query
          schema:
            type: string
      responses:
        '200':
          description: Objects and common prefixes
          content:
            application/json:
              schema:
                type: object
                properties:
                  objects:
                    type: array
                    items:
                      type: object
                      properties:
                        key:
                          type: string
                        size:
                          type: integer
                        last_modified:
                          type: string
                          nullable: true
                        etag:
                          type: string
                  prefixes:
                    type: array
                    items:
                      type: string
                  is_truncated:
                    type: boolean
                  next_continuation_token:
                    type: string
                    nullable: true
                  key_count:
                    type: integer
        '401':
          description: Not authenticated
        '403':
          description: Requires admin role
        '404':
          description: Bucket not found
        '502':
          description: Storage service unavailable
  /storage/buckets/{name}/upload:
    post:
      summary: Upload a file to a bucket
      tags:
        - Storage
      security:
        - bearerAuth: []
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - file
              properties:
                file:
                  type: string
                  format: binary
                key:
                  type: string
                  description: Object key (defaults to filename)
      responses:
        '200':
          description: Upload successful
          content:
            application/json:
              schema:
                type: object
                properties:
                  key:
                    type: string
                  size:
                    type: integer
                  content_type:
                    type: string
        '400':
          description: No file provided
        '401':
          description: Not authenticated
        '403':
          description: Requires admin role
        '404':
          description: Bucket not found
        '502':
          description: Storage service unavailable
  /storage/buckets/{name}/objects/{key}:
    delete:
      summary: Delete an object from a bucket
      tags:
        - Storage
      security:
        - bearerAuth: []
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
        - name: key
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Object deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  deleted:
                    type: string
        '401':
          description: Not authenticated
        '403':
          description: Requires admin role
        '404':
          description: Bucket not found
        '502':
          description: Storage service unavailable
  /workflows:
    get:
      summary: List workflows
      security:
        - bearerAuth: []
      responses:
        '200':
          description: List of workflows
    post:
      summary: Create a workflow
      security:
        - bearerAuth: []
      responses:
        '201':
          description: Workflow created
  /workflows/{id}:
    get:
      summary: Get workflow detail
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Workflow detail
        '404':
          description: Workflow not found
    put:
      summary: Update a workflow
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Workflow updated
    delete:
      summary: Delete a workflow
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Workflow deleted
  /workflows/{id}/run:
    post:
      summary: Manually trigger a workflow
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Workflow triggered
        '409':
          description: Agent not running
  /workflows/{id}/runs:
    get:
      summary: Get workflow run history
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: List of runs
  /mcp-servers:
    get:
      summary: List MCP servers
      security:
        - bearerAuth: []
      responses:
        '200':
          description: List of MCP servers
    post:
      summary: Create an MCP server
      security:
        - bearerAuth: []
      responses:
        '201':
          description: MCP server created
  /mcp-servers/{id}:
    get:
      summary: Get MCP server detail
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: MCP server detail
    put:
      summary: Update an MCP server
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: MCP server updated
    delete:
      summary: Delete an MCP server
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: MCP server deleted
  /workflows/{id}/steps:
    get:
      summary: List workflow steps
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: List of steps
    post:
      summary: Add a step to workflow
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '201':
          description: Step created
  /workflows/{id}/steps/{stepId}:
    delete:
      summary: Remove a step from workflow
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: stepId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Step deleted
  /workflows/webhook/{token}:
    post:
      summary: Inbound webhook trigger (public, no auth)
      security: []
      parameters:
        - name: token
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Workflow triggered
        '404':
          description: Webhook not found
        '409':
          description: Agent not running
