openapi: 3.1.0
info:
  title: Hermes Hub Control API
  version: 0.1.2
  description: |
    External control API for managing Hermes tenants and their cold-started
    Hermes A2A agents.
servers:
  - url: http://localhost:8080
security:
  - ControlBearer: []
paths:
  /v1/healthz:
    get:
      security: []
      operationId: getHealth
      summary: Liveness check.
      responses:
        "200":
          description: Service is alive.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"
  /v1/readyz:
    get:
      operationId: getReadiness
      summary: Readiness check.
      responses:
        "200":
          description: Service is ready to accept control requests.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"
  /v1/version:
    get:
      operationId: getVersion
      summary: Return API and runtime version metadata.
      responses:
        "200":
          description: Version metadata.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VersionResponse"
  /openapi.yaml:
    get:
      operationId: getOpenAPISchema
      summary: Return the OpenAPI schema in YAML format.
      responses:
        "200":
          description: OpenAPI schema.
          content:
            application/yaml:
              schema:
                type: string
  /swagger.yaml:
    get:
      operationId: getSwaggerSchema
      summary: Return the Swagger-compatible OpenAPI schema in YAML format.
      responses:
        "200":
          description: OpenAPI schema.
          content:
            application/yaml:
              schema:
                type: string
  /v1/reconcile:
    post:
      operationId: reconcile
      summary: Reconcile registered tenants against the configured runtime.
      responses:
        "200":
          description: Reconcile result.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReconcileResult"
  /v1/agent-specialization:
    get:
      operationId: getAgentSpecialization
      summary: Return hub-wide default tenant agent specialization.
      description: |
        This is hub-owned behavior guidance projected into tenant Hermes
        runtimes as `agent.system_prompt`. Tenant memory remains per tenant
        under that tenant's `$HERMES_HOME/memories`.
      responses:
        "200":
          description: Current default specialization.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentSpecialization"
    put:
      operationId: updateAgentSpecialization
      summary: Replace hub-wide default tenant agent specialization.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateAgentSpecializationRequest"
      responses:
        "200":
          description: Updated default specialization.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentSpecialization"
        "400":
          $ref: "#/components/responses/Problem"
    delete:
      operationId: deleteAgentSpecialization
      summary: Disable hub-wide default tenant agent specialization.
      responses:
        "204":
          description: Default specialization was disabled.
  /v1/codex-auth/status:
    get:
      operationId: getCodexAuthStatus
      summary: Return central hub Codex auth installation status.
      responses:
        "200":
          description: Central Codex auth status. Token material is never returned.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CodexAuthStatus"
  /v1/codex-auth:
    post:
      operationId: installCodexAuth
      summary: Install central hub Codex OAuth token material.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InstallCodexAuthRequest"
      responses:
        "201":
          description: Central Codex auth was installed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CodexAuthStatus"
  /v1/codex-auth/refresh:
    post:
      operationId: refreshCodexAuth
      summary: Refresh central hub Codex OAuth tokens.
      responses:
        "200":
          description: Central Codex auth was refreshed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CodexAuthStatus"
  /v1/codex-auth/device-code:
    post:
      operationId: startCodexAuthDeviceCode
      summary: Start a remote-safe OpenAI Codex device-code login for the hub.
      responses:
        "201":
          description: Device-code session was created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CodexAuthDeviceCodeSession"
  /v1/codex-auth/device-code/{sessionId}:
    parameters:
      - name: sessionId
        in: path
        required: true
        schema:
          type: string
    get:
      operationId: getCodexAuthDeviceCode
      summary: Poll central Codex device-code login status.
      responses:
        "200":
          description: Device-code session status.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CodexAuthDeviceCodeSession"
  /v1/tenant-runtime/{tenantId}/codex/{proxyPath}:
    parameters:
      - $ref: "#/components/parameters/TenantId"
      - name: proxyPath
        in: path
        required: true
        schema:
          type: string
    post:
      security: []
      operationId: proxyTenantCodexRequest
      summary: Internal tenant runtime proxy to hub-owned Codex auth.
      responses:
        "200":
          description: Upstream Codex response.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
  /v1/tenant-runtime/{tenantId}/hub-chat/outbound:
    parameters:
      - $ref: "#/components/parameters/TenantId"
    post:
      security: []
      operationId: acceptTenantHubChatOutbound
      summary: Internal tenant runtime callback for hub_chat replies.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/HubChatOutboundMessage"
      responses:
        "202":
          description: Outbound tenant chat message accepted.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HubChatMessage"
        "401":
          $ref: "#/components/responses/Problem"
  /v1/hermes-agent/releases:
    get:
      operationId: listHermesAgentReleases
      summary: List centrally installed Hermes Agent releases.
      responses:
        "200":
          description: Installed release list.
          content:
            application/json:
              schema:
                type: object
                required: [releases]
                properties:
                  releases:
                    type: array
                    items:
                      $ref: "#/components/schemas/HermesAgentRelease"
    post:
      operationId: createHermesAgentRelease
      summary: Stage, build, and register a central Hermes Agent release.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateHermesAgentReleaseRequest"
      responses:
        "201":
          description: Release was created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HermesAgentRelease"
        "400":
          $ref: "#/components/responses/Problem"
        "409":
          $ref: "#/components/responses/Problem"
  /v1/hermes-agent/releases/{releaseId}:activate:
    parameters:
      - $ref: "#/components/parameters/ReleaseId"
    post:
      operationId: activateHermesAgentRelease
      summary: Atomically promote a central Hermes Agent release.
      responses:
        "200":
          description: Activated release.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HermesAgentRelease"
        "404":
          $ref: "#/components/responses/Problem"
  /v1/hermes-agent/releases/{releaseId}:
    parameters:
      - $ref: "#/components/parameters/ReleaseId"
    get:
      operationId: getHermesAgentRelease
      summary: Return one centrally installed Hermes Agent release.
      responses:
        "200":
          description: Release metadata.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HermesAgentRelease"
        "404":
          $ref: "#/components/responses/Problem"
    delete:
      operationId: deleteHermesAgentRelease
      summary: Delete an inactive, unused Hermes Agent release.
      responses:
        "204":
          description: Release deleted.
        "404":
          $ref: "#/components/responses/Problem"
        "409":
          $ref: "#/components/responses/Problem"
  /v1/secrets:
    get:
      operationId: listSecrets
      summary: List global and tenant-scoped managed secret metadata.
      parameters:
        - name: scope
          in: query
          schema:
            type: string
            enum: [global, tenant]
        - name: tenantId
          in: query
          schema:
            type: string
        - name: dueOnly
          in: query
          schema:
            type: boolean
      responses:
        "200":
          description: Secret metadata list.
          content:
            application/json:
              schema:
                type: object
                required: [secrets]
                properties:
                  secrets:
                    type: array
                    items:
                      $ref: "#/components/schemas/ManagedSecret"
    post:
      operationId: createSecret
      summary: Register a managed OAuth2 secret without returning secret material.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateManagedSecretRequest"
      responses:
        "201":
          description: Secret metadata was created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ManagedSecret"
  /v1/secrets:refresh-due:
    post:
      operationId: refreshDueSecrets
      summary: Refresh all OAuth2 secrets whose next refresh time is due.
      responses:
        "200":
          description: Refresh result.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
  /v1/secrets/{secretId}:
    parameters:
      - name: secretId
        in: path
        required: true
        schema:
          type: string
    get:
      operationId: getSecret
      summary: Return managed secret metadata.
      responses:
        "200":
          description: Secret metadata.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ManagedSecret"
    delete:
      operationId: deleteSecret
      summary: Delete managed secret metadata and local secret material.
      responses:
        "204":
          description: Secret deleted.
  /v1/secrets/{secretId}:refresh:
    parameters:
      - name: secretId
        in: path
        required: true
        schema:
          type: string
    post:
      operationId: refreshSecret
      summary: Force-refresh one OAuth2 managed secret.
      responses:
        "200":
          description: Refreshed secret metadata.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ManagedSecret"
  /v1/config/agentgateway:
    get:
      operationId: getAgentgatewayConfig
      summary: Render agentgateway YAML for ready tenants.
      responses:
        "200":
          description: Rendered agentgateway YAML.
          content:
            application/yaml:
              schema:
                type: string
  /v1/tenants:
    get:
      operationId: listTenants
      summary: List tenants.
      responses:
        "200":
          description: Tenant list.
          content:
            application/json:
              schema:
                type: object
                required: [tenants]
                properties:
                  tenants:
                    type: array
                    items:
                      $ref: "#/components/schemas/Tenant"
    post:
      operationId: createTenant
      summary: Create and provision a tenant.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateTenantRequest"
      responses:
        "201":
          description: Tenant was created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Tenant"
        "400":
          $ref: "#/components/responses/Problem"
        "409":
          $ref: "#/components/responses/Problem"
  /v1/tenants/{tenantId}:
    parameters:
      - $ref: "#/components/parameters/TenantId"
    get:
      operationId: getTenant
      summary: Get one tenant.
      responses:
        "200":
          description: Tenant.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Tenant"
        "404":
          $ref: "#/components/responses/Problem"
    patch:
      operationId: updateTenant
      summary: Update tenant metadata and desired settings.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateTenantRequest"
      responses:
        "200":
          description: Updated tenant.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Tenant"
        "400":
          $ref: "#/components/responses/Problem"
        "404":
          $ref: "#/components/responses/Problem"
    delete:
      operationId: deleteTenant
      summary: Delete one tenant.
      responses:
        "204":
          description: Tenant was deleted.
        "404":
          $ref: "#/components/responses/Problem"
  /v1/tenants/{tenantId}/agent:
    parameters:
      - $ref: "#/components/parameters/TenantId"
    get:
      operationId: getTenantAgent
      summary: Get current tenant agent state.
      responses:
        "200":
          description: Agent state.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentStatus"
        "404":
          $ref: "#/components/responses/Problem"
  /v1/tenants/{tenantId}/agent/start:
    parameters:
      - $ref: "#/components/parameters/TenantId"
    post:
      operationId: startTenantAgent
      summary: Start or wake a tenant Hermes agent.
      responses:
        "200":
          description: Updated agent state.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentStatus"
        "404":
          $ref: "#/components/responses/Problem"
        "409":
          $ref: "#/components/responses/Problem"
  /v1/tenants/{tenantId}/agent/stop:
    parameters:
      - $ref: "#/components/parameters/TenantId"
    post:
      operationId: stopTenantAgent
      summary: Stop a tenant Hermes agent and return it to cold-idle.
      responses:
        "200":
          description: Updated agent state.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentStatus"
        "404":
          $ref: "#/components/responses/Problem"
  /v1/tenants/{tenantId}/agent/restart:
    parameters:
      - $ref: "#/components/parameters/TenantId"
    post:
      operationId: restartTenantAgent
      summary: Restart a tenant Hermes agent.
      responses:
        "200":
          description: Updated agent state.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentStatus"
        "404":
          $ref: "#/components/responses/Problem"
        "409":
          $ref: "#/components/responses/Problem"
  /v1/tenants/{tenantId}/agent-card:
    parameters:
      - $ref: "#/components/parameters/TenantId"
    get:
      operationId: getTenantAgentCard
      summary: Return the public A2A agent card for a tenant.
      responses:
        "200":
          description: A2A agent card.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentCard"
        "404":
          $ref: "#/components/responses/Problem"
  /v1/tenants/{tenantId}/a2a-credentials:
    parameters:
      - $ref: "#/components/parameters/TenantId"
    get:
      operationId: getTenantA2ACredentials
      summary: Return tenant A2A bearer credentials and agent card.
      responses:
        "200":
          description: Tenant A2A credentials.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/A2ACredentials"
        "404":
          $ref: "#/components/responses/Problem"
  /v1/tenants/{tenantId}/a2a-peers:
    parameters:
      - $ref: "#/components/parameters/TenantId"
    get:
      operationId: listTenantA2APeers
      summary: List introduced remote A2A peers for a tenant.
      responses:
        "200":
          description: Tenant A2A peer list.
          content:
            application/json:
              schema:
                type: object
                required: [tenantID, peers]
                properties:
                  tenantID:
                    type: string
                  peers:
                    type: array
                    items:
                      $ref: "#/components/schemas/A2APeer"
        "404":
          $ref: "#/components/responses/Problem"
    post:
      operationId: introduceTenantA2APeer
      summary: Introduce a remote A2A peer before communication.
      description: |
        Fetches and stores the remote agent card for this tenant. The optional
        access key is persisted for future back communication but is never
        returned by the API.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/IntroduceA2APeerRequest"
      responses:
        "200":
          description: Introduced peer metadata.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/A2APeer"
        "400":
          $ref: "#/components/responses/Problem"
        "404":
          $ref: "#/components/responses/Problem"
  /v1/tenants/{tenantId}/a2a-peers/{peerId}:
    parameters:
      - $ref: "#/components/parameters/TenantId"
      - $ref: "#/components/parameters/A2APeerId"
    get:
      operationId: getTenantA2APeer
      summary: Return one introduced A2A peer.
      responses:
        "200":
          description: Introduced peer metadata.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/A2APeer"
        "404":
          $ref: "#/components/responses/Problem"
    delete:
      operationId: deleteTenantA2APeer
      summary: Remove one introduced A2A peer.
      responses:
        "204":
          description: Peer was removed.
        "404":
          $ref: "#/components/responses/Problem"
  /v1/tenants/{tenantId}/artifacts:
    parameters:
      - $ref: "#/components/parameters/TenantId"
    get:
      operationId: getTenantArtifacts
      summary: Render host artifacts for one tenant.
      responses:
        "200":
          description: Rendered artifacts.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Artifacts"
        "400":
          $ref: "#/components/responses/Problem"
        "404":
          $ref: "#/components/responses/Problem"
  /v1/tenants/{tenantId}/runtime:
    parameters:
      - $ref: "#/components/parameters/TenantId"
    get:
      operationId: getTenantRuntimeInspection
      summary: Inspect resolved tenant runtime environment and artifact paths.
      description: |
        Returns safe path and environment metadata needed to verify tenant
        isolation and central release reuse. Secret values are never returned.
      responses:
        "200":
          description: Resolved runtime metadata.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TenantRuntimeInspection"
        "400":
          $ref: "#/components/responses/Problem"
        "404":
          $ref: "#/components/responses/Problem"
  /v1/tenants/{tenantId}/env:
    parameters:
      - $ref: "#/components/parameters/TenantId"
    get:
      operationId: listTenantEnvVars
      summary: List tenant-scoped Hermes secret environment keys.
      description: |
        Returns metadata only. Secret values from the tenant Hermes `.env` file
        are never returned.
      responses:
        "200":
          description: Tenant environment key metadata.
          content:
            application/json:
              schema:
                type: object
                required: [env]
                properties:
                  env:
                    type: array
                    items:
                      $ref: "#/components/schemas/TenantEnvVar"
        "404":
          $ref: "#/components/responses/Problem"
  /v1/tenants/{tenantId}/env/{name}:
    parameters:
      - $ref: "#/components/parameters/TenantId"
      - $ref: "#/components/parameters/TenantEnvName"
    put:
      operationId: setTenantEnvVar
      summary: Set one tenant-scoped Hermes secret environment key.
      description: |
        Writes the key into the tenant's `$HERMES_HOME/.env` file. Only
        secret-style credential names are accepted; Hub/system runtime keys are
        reserved. The response returns metadata only and never echoes the value.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SetTenantEnvVarRequest"
      responses:
        "200":
          description: Tenant environment key metadata.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TenantEnvVar"
        "400":
          $ref: "#/components/responses/Problem"
        "404":
          $ref: "#/components/responses/Problem"
    delete:
      operationId: deleteTenantEnvVar
      summary: Remove one tenant-scoped Hermes secret environment key.
      responses:
        "204":
          description: Tenant environment key was removed.
        "400":
          $ref: "#/components/responses/Problem"
        "404":
          $ref: "#/components/responses/Problem"
  /v1/tenants/{tenantId}/hub-chat/messages:
    parameters:
      - $ref: "#/components/parameters/TenantId"
    get:
      operationId: listTenantHubChatMessages
      summary: List outbound tenant chat messages received by Hub.
      parameters:
        - name: chatID
          in: query
          schema:
            type: string
        - name: after
          in: query
          schema:
            type: integer
            minimum: 0
      responses:
        "200":
          description: Outbound chat messages.
          content:
            application/json:
              schema:
                type: object
                required: [tenantID, messages]
                properties:
                  tenantID:
                    type: string
                  messages:
                    type: array
                    items:
                      $ref: "#/components/schemas/HubChatMessage"
        "404":
          $ref: "#/components/responses/Problem"
  /v1/tenants/{tenantId}/cron-jobs:
    parameters:
      - $ref: "#/components/parameters/TenantId"
    post:
      operationId: createTenantCronJob
      summary: Schedule a tenant cron-style wakeup job.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateCronJobRequest"
      responses:
        "201":
          description: Cron job was scheduled.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CronJob"
        "400":
          $ref: "#/components/responses/Problem"
        "404":
          $ref: "#/components/responses/Problem"
  /v1/tenants/{tenantId}/cron-jobs/{jobId}:
    parameters:
      - $ref: "#/components/parameters/TenantId"
      - name: jobId
        in: path
        required: true
        schema:
          type: string
          pattern: "^cron-[0-9]{6}$"
    get:
      operationId: getTenantCronJob
      summary: Return one tenant cron job.
      responses:
        "200":
          description: Cron job state.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CronJob"
        "404":
          $ref: "#/components/responses/Problem"
  /a2a/{tenantId}:
    parameters:
      - $ref: "#/components/parameters/TenantId"
    post:
      security:
        - TenantA2ABearer: []
      operationId: sendTenantA2AMessage
      summary: Send a non-streaming A2A 0.3 message to a tenant.
      description: |
        Callers must be introduced as A2A peers before Hub routes them to a
        tenant runtime. Introduced callers identify the registered peer using
        `X-Hermes-Peer-ID`,
        `X-Hermes-Peer-Agent-Card-URL`, or request metadata such as
        `params.metadata.peerId` / `params.metadata.peerAgentCardUrl`.
        Authenticated callers that are not introduced, including callers for a
        tenant with no peer mappings yet, still receive an A2A JSON-RPC task
        response, but Hub does not wake or route to the tenant runtime. The
        response text explains how to introduce the caller's agent card and
        retry with peer identity metadata.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/A2AJsonRPCRequest"
      responses:
        "200":
          description: A2A JSON-RPC response.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/A2AJsonRPCResponse"
        "401":
          $ref: "#/components/responses/Problem"
        "404":
          $ref: "#/components/responses/Problem"
  /a2a/{tenantId}/.well-known/agent.json:
    parameters:
      - $ref: "#/components/parameters/TenantId"
    get:
      security: []
      operationId: getTenantWellKnownAgentCardLegacy
      summary: Return the tenant A2A agent card at the legacy well-known path.
      responses:
        "200":
          description: A2A agent card.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentCard"
        "404":
          $ref: "#/components/responses/Problem"
  /a2a/{tenantId}/.well-known/agent-card.json:
    parameters:
      - $ref: "#/components/parameters/TenantId"
    get:
      security: []
      operationId: getTenantWellKnownAgentCard
      summary: Return the tenant A2A agent card at the well-known path.
      responses:
        "200":
          description: A2A agent card.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentCard"
        "404":
          $ref: "#/components/responses/Problem"
components:
  securitySchemes:
    ControlBearer:
      type: http
      scheme: bearer
      description: Bearer token configured with HERMES_HUB_API_TOKEN.
    TenantA2ABearer:
      type: http
      scheme: bearer
      description: Tenant-scoped A2A bearer key from `/v1/tenants/{tenantId}/a2a-credentials`.
  parameters:
    TenantId:
      name: tenantId
      in: path
      required: true
      schema:
        type: string
        pattern: "^[a-z][a-z0-9_]{2,62}$"
    ReleaseId:
      name: releaseId
      in: path
      required: true
      schema:
        type: string
        pattern: "^[A-Za-z0-9][A-Za-z0-9._+~-]{0,127}$"
    TenantEnvName:
      name: name
      in: path
      required: true
      schema:
        type: string
        pattern: "^[A-Z_][A-Z0-9_]{0,127}$"
        examples: [OPENROUTER_API_KEY]
    A2APeerId:
      name: peerId
      in: path
      required: true
      schema:
        type: string
        pattern: "^peer_[a-f0-9]{24}$"
  responses:
    Problem:
      description: Problem details.
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/Problem"
  schemas:
    HealthResponse:
      type: object
      required: [status]
      properties:
        status:
          type: string
          examples: [ok]
        version:
          type: string
        mode:
          type: string
        checks:
          type: object
          additionalProperties: true
    VersionResponse:
      type: object
      required: [version, mode]
      properties:
        version:
          type: string
        mode:
          type: string
          enum: [mock, systemd]
    AgentSpecialization:
      type: object
      required: [enabled, path, byteCount]
      properties:
        enabled:
          type: boolean
        path:
          type: string
        byteCount:
          type: integer
          format: int64
        content:
          type: string
          description: Markdown behavior guidance projected into tenant `agent.system_prompt`.
        updatedAt:
          type: string
          format: date-time
    UpdateAgentSpecializationRequest:
      type: object
      required: [content]
      additionalProperties: false
      properties:
        content:
          type: string
          maxLength: 65536
    CodexAuthStatus:
      type: object
      description: Central hub Codex auth status. Token material is never returned.
      required: [installed, byteCount]
      properties:
        installed:
          type: boolean
        byteCount:
          type: integer
          format: int64
    InstallCodexAuthRequest:
      type: object
      description: Write-only central Codex OAuth material.
      required: [accessToken, refreshToken]
      additionalProperties: false
      properties:
        accessToken:
          type: string
          writeOnly: true
        refreshToken:
          type: string
          writeOnly: true
        idToken:
          type: string
          writeOnly: true
    CodexAuthDeviceCodeSession:
      type: object
      required: [sessionId, status, expiresAt, pollInterval]
      properties:
        sessionId:
          type: string
        status:
          type: string
          enum: [pending, approved, expired, error]
        userCode:
          type: string
        verificationUrl:
          type: string
        expiresAt:
          type: string
          format: date-time
        pollInterval:
          type: integer
        error:
          type: string
        secretStatus:
          $ref: "#/components/schemas/CodexAuthStatus"
    HubChatOutboundMessage:
      type: object
      additionalProperties: false
      required: [chat_id, content]
      properties:
        tenant_id:
          type: string
        chat_id:
          type: string
        content:
          type: string
        message_id:
          type: string
        reply_to:
          type: string
        metadata:
          type: object
          additionalProperties: true
        created_at:
          type: number
    HubChatMessage:
      type: object
      required: [seq, tenantID, chatID, content, createdAt]
      properties:
        seq:
          type: integer
        tenantID:
          type: string
        chatID:
          type: string
        content:
          type: string
        messageID:
          type: string
        replyTo:
          type: string
        metadata:
          type: object
          additionalProperties: true
        createdAt:
          type: string
          format: date-time
    CreateTenantRequest:
      type: object
      required: [id]
      additionalProperties: false
      properties:
        id:
          type: string
          pattern: "^[a-z][a-z0-9_]{2,62}$"
          examples: [tenant_123]
        displayName:
          type: string
          maxLength: 120
        publicHost:
          type: string
          examples: [tenant-123.hermes.example.com]
        browser:
          $ref: "#/components/schemas/BrowserConfig"
        quotas:
          $ref: "#/components/schemas/Quotas"
        auth:
          $ref: "#/components/schemas/AuthConfig"
        agent:
          $ref: "#/components/schemas/AgentConfig"
    UpdateTenantRequest:
      type: object
      additionalProperties: false
      properties:
        displayName:
          type: string
          maxLength: 120
        publicHost:
          type: string
        disabled:
          type: boolean
        browser:
          $ref: "#/components/schemas/BrowserConfig"
        quotas:
          $ref: "#/components/schemas/Quotas"
        auth:
          $ref: "#/components/schemas/AuthConfig"
        agent:
          $ref: "#/components/schemas/AgentConfig"
    Tenant:
      type: object
      required:
        - id
        - displayName
        - linuxUser
        - homeDir
        - hermesHome
        - publicHost
        - status
        - agent
        - browser
        - quotas
        - auth
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
        displayName:
          type: string
        linuxUser:
          type: string
        homeDir:
          type: string
        hermesHome:
          type: string
        publicHost:
          type: string
        status:
          type: string
          enum: [ready, disabled, error]
        agent:
          $ref: "#/components/schemas/AgentStatus"
        browser:
          $ref: "#/components/schemas/BrowserConfig"
        quotas:
          $ref: "#/components/schemas/Quotas"
        auth:
          $ref: "#/components/schemas/AuthConfig"
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    AgentStatus:
      type: object
      required: [state, socketPath, serviceUnit, socketUnit]
      properties:
        state:
          type: string
          enum: [cold_idle, starting, running, stopping, failed]
        socketPath:
          type: string
        serviceUnit:
          type: string
        socketUnit:
          type: string
        version:
          type: string
        releaseId:
          type: string
        lastStartedAt:
          type: string
          format: date-time
        lastStoppedAt:
          type: string
          format: date-time
        lastError:
          type: string
    AgentConfig:
      type: object
      additionalProperties: false
      properties:
        version:
          type: string
          default: stable
        releaseId:
          type: string
          description: |
            Exact central release pin. Omit or use current to follow the active
            release.
          pattern: "^(current|[A-Za-z0-9][A-Za-z0-9._+~-]{0,127})$"
        idleTimeoutSeconds:
          type: integer
          minimum: 1
          default: 300
    AgentCard:
      type: object
      additionalProperties: true
      description: A2A agent card returned for the tenant.
      properties:
        protocolVersion:
          type: string
          enum: ["0.3"]
        capabilities:
          type: object
          description: Hub does not advertise A2A streaming.
          additionalProperties: true
    A2ACredentials:
      type: object
      required: [tenantID, a2aKey, agentCard]
      properties:
        tenantID:
          type: string
        tenantId:
          type: string
          description: Compatibility alias for tenantID.
        a2aKey:
          type: string
          writeOnly: true
          description: Tenant-scoped bearer key for `/a2a/{tenantId}`.
        key:
          type: string
          writeOnly: true
          description: Compatibility alias for a2aKey.
        agentCard:
          $ref: "#/components/schemas/AgentCard"
        codexAuthSecret:
          type: object
          additionalProperties: true
          description: Optional metadata only; token material is never returned.
    IntroduceA2APeerRequest:
      type: object
      additionalProperties: false
      required: [agentCardUrl]
      properties:
        agentCardUrl:
          type: string
          format: uri
          description: Remote peer well-known A2A agent card URL.
          examples:
            - https://rooms-api.wizeup.app/api/v1/rooms/32e574f0-948b-4d6f-b9b1-00176c991839/.well-known/agent.json
        accessKey:
          type: string
          writeOnly: true
          description: Optional remote peer bearer/access key. It is stored but never returned.
    A2APeer:
      type: object
      required:
        - id
        - tenantId
        - agentCardUrl
        - agentUrl
        - name
        - hasAccessKey
        - agentCard
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
          pattern: "^peer_[a-f0-9]{24}$"
        tenantId:
          type: string
        agentCardUrl:
          type: string
          format: uri
        agentUrl:
          type: string
          format: uri
        name:
          type: string
        protocolVersion:
          type: string
        hasAccessKey:
          type: boolean
        agentCard:
          $ref: "#/components/schemas/AgentCard"
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    A2AJsonRPCRequest:
      type: object
      additionalProperties: true
      required: [jsonrpc, method, params]
      properties:
        jsonrpc:
          type: string
          examples: ["2.0"]
        id:
          oneOf:
            - type: string
            - type: integer
            - type: "null"
        method:
          type: string
          examples: [message/send]
        params:
          type: object
          additionalProperties: true
    A2AJsonRPCResponse:
      type: object
      additionalProperties: false
      required: [jsonrpc, id, result]
      properties:
        jsonrpc:
          type: string
          examples: ["2.0"]
        id:
          oneOf:
            - type: string
            - type: integer
            - type: "null"
        result:
          type: object
          required: [kind, id, contextId, status, artifacts]
          properties:
            kind:
              type: string
              enum: [task]
            id:
              type: string
            contextId:
              type: string
            status:
              type: object
              required: [state, message]
              properties:
                state:
                  type: string
                  enum: [completed]
                message:
                  $ref: "#/components/schemas/A2AMessage"
            artifacts:
              type: array
              items:
                type: object
                required: [artifactId, parts]
                properties:
                  artifactId:
                    type: string
                  name:
                    type: string
                  parts:
                    type: array
                    items:
                      $ref: "#/components/schemas/A2ATextPart"
            metadata:
              type: object
              additionalProperties:
                type: string
    A2AMessage:
      type: object
      required: [kind, role, parts, messageId]
      properties:
        kind:
          type: string
          enum: [message]
        role:
          type: string
          enum: [agent]
        parts:
          type: array
          items:
            $ref: "#/components/schemas/A2ATextPart"
        messageId:
          type: string
        contextId:
          type: string
        taskId:
          type: string
        metadata:
          type: object
          additionalProperties:
            type: string
    A2ATextPart:
      type: object
      required: [kind, text]
      properties:
        kind:
          type: string
          enum: [text]
        text:
          type: string
    CreateCronJobRequest:
      type: object
      additionalProperties: false
      required: [delaySeconds]
      properties:
        delaySeconds:
          type: integer
          minimum: 0
          description: Delay before execution, in seconds.
        payload:
          description: JSON payload passed to the tenant runtime when the job fires.
    CronJob:
      type: object
      required: [id, tenantID, status]
      properties:
        id:
          type: string
          pattern: "^cron-[0-9]{6}$"
        tenantID:
          type: string
        status:
          type: string
          enum: [pending, executed, error]
        payload:
          description: Original scheduled payload.
        result:
          description: Execution result when status is executed.
        error:
          type: string
    CreateHermesAgentReleaseRequest:
      type: object
      additionalProperties: false
      description: |
        Register an existing central release by providing id and installPath, or
        build a new immutable release from sourcePath. When sourcePath is used,
        id, version, git metadata, and installPath can be derived by the hub.
      properties:
        id:
          type: string
          description: Explicit central release id. Derived from version and source metadata when omitted for source installs.
          pattern: "^[A-Za-z0-9][A-Za-z0-9._+~-]{0,127}$"
          examples: [0.12.0+5d3be898]
        releaseId:
          type: string
          description: Compatibility alias for id.
          pattern: "^[A-Za-z0-9][A-Za-z0-9._+~-]{0,127}$"
        version:
          type: string
          examples: [0.12.0]
        gitSha:
          type: string
        gitDescribe:
          type: string
          examples: [v2026.4.30-188-g5d3be898a]
        sourcePath:
          type: string
          description: Trusted Hermes Agent source checkout used to build the release.
          examples: [/home/hermes/src/hermes-agent]
        installPath:
          type: string
          description: Absolute path to the centrally installed immutable release. Defaults to `${HERMES_HUB_RELEASE_ROOT:-/opt/hermes-agent}/releases/{id}` for source installs.
          examples: [/opt/hermes-agent/releases/0.12.0+5d3be898]
        activate:
          type: boolean
          default: false
        manifest:
          type: object
          additionalProperties:
            type: string
    HermesAgentRelease:
      type: object
      required:
        - id
        - version
        - installPath
        - active
        - createdAt
        - artifactPaths
      properties:
        id:
          type: string
          pattern: "^[A-Za-z0-9][A-Za-z0-9._+~-]{0,127}$"
          examples: [0.12.0+5d3be898]
        version:
          type: string
          examples: [0.12.0]
        gitSha:
          type: string
        gitDescribe:
          type: string
          examples: [v2026.4.30-188-g5d3be898a]
        sourcePath:
          type: string
        installPath:
          type: string
          examples: [/opt/hermes-agent/releases/0.12.0+5d3be898]
        active:
          type: boolean
        createdAt:
          type: string
          format: date-time
        manifestChecksum:
          type: string
        artifactPaths:
          $ref: "#/components/schemas/HermesAgentReleaseArtifactPaths"
    HermesAgentReleaseArtifactPaths:
      type: object
      required:
        - venv
        - nodeModules
        - webNodeModules
        - tuiNodeModules
        - playwrightBrowsers
        - skills
        - optionalSkills
        - plugins
        - webDist
      properties:
        venv:
          type: string
        nodeModules:
          type: string
        webNodeModules:
          type: string
        tuiNodeModules:
          type: string
        playwrightBrowsers:
          type: string
        skills:
          type: string
        optionalSkills:
          type: string
        plugins:
          type: string
        webDist:
          type: string
    ManagedSecret:
      type: object
      description: Secret lifecycle metadata. Token material is never returned.
      required:
        - id
        - scope
        - name
        - provider
        - kind
        - status
        - refreshable
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
          examples: [tenant__tenant_123__google_oauth]
        scope:
          type: string
          enum: [global, tenant]
        tenantId:
          type: string
        name:
          type: string
        provider:
          type: string
          examples: [codex, google]
        kind:
          type: string
          enum: [oauth2]
        status:
          type: string
          enum: [active, refresh_due, refresh_failed]
        refreshable:
          type: boolean
        refreshBeforeSeconds:
          type: integer
        expiresAt:
          type: string
          format: date-time
        nextRefreshAt:
          type: string
          format: date-time
        lastRefreshedAt:
          type: string
          format: date-time
        lastError:
          type: string
        metadata:
          type: object
          additionalProperties:
            type: string
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    CreateManagedSecretRequest:
      type: object
      required: [scope, name, provider, kind, oauth2]
      additionalProperties: false
      properties:
        scope:
          type: string
          enum: [global, tenant]
        tenantId:
          type: string
          description: Required when scope is tenant.
        name:
          type: string
          pattern: "^[a-z][a-z0-9_-]{1,62}$"
        provider:
          type: string
          examples: [codex, google]
        kind:
          type: string
          enum: [oauth2]
        refreshBeforeSeconds:
          type: integer
          minimum: 1
          default: 300
        expiresAt:
          type: string
          format: date-time
        metadata:
          type: object
          additionalProperties:
            type: string
        oauth2:
          $ref: "#/components/schemas/OAuth2SecretMaterial"
    OAuth2SecretMaterial:
      type: object
      description: Write-only OAuth2 material.
      required: [tokenUrl, clientId, refreshToken]
      properties:
        tokenUrl:
          type: string
        clientId:
          type: string
        clientSecret:
          type: string
        refreshToken:
          type: string
        accessToken:
          type: string
        tokenType:
          type: string
        expiresAt:
          type: string
          format: date-time
        scopes:
          type: array
          items:
            type: string
    TenantRuntimeInspection:
      type: object
      required:
        - tenantId
        - releaseId
        - releasePath
        - environment
        - artifactPaths
      properties:
        tenantId:
          type: string
        releaseId:
          type: string
        releasePath:
          type: string
        environment:
          $ref: "#/components/schemas/TenantRuntimeEnvironment"
        artifactPaths:
          $ref: "#/components/schemas/TenantRuntimeArtifactPaths"
    TenantRuntimeEnvironment:
      type: object
      required:
        - HERMES_HOME
        - HOME
        - HERMES_OPTIONAL_SKILLS
        - HERMES_WEB_DIST
        - PLAYWRIGHT_BROWSERS_PATH
        - VIRTUAL_ENV
        - PATH
      properties:
        HERMES_HOME:
          type: string
        HOME:
          type: string
        HERMES_OPTIONAL_SKILLS:
          type: string
        HERMES_WEB_DIST:
          type: string
        PLAYWRIGHT_BROWSERS_PATH:
          type: string
        VIRTUAL_ENV:
          type: string
        PATH:
          type: string
    TenantRuntimeArtifactPaths:
      type: object
      required:
        - hermesHome
        - subprocessHome
        - chromeDebugProfile
        - browserAuth
        - camofoxState
      properties:
        hermesHome:
          type: string
        subprocessHome:
          type: string
        chromeDebugProfile:
          type: string
        browserAuth:
          type: string
        camofoxState:
          type: string
    TenantEnvVar:
      type: object
      required: [name, source]
      additionalProperties: false
      properties:
        name:
          type: string
          examples: [OPENROUTER_API_KEY]
        source:
          type: string
          enum: [tenant_dotenv]
    SetTenantEnvVarRequest:
      type: object
      required: [value]
      additionalProperties: false
      properties:
        value:
          type: string
          writeOnly: true
          minLength: 1
          description: Secret value to write into the tenant Hermes `.env` file.
    Artifacts:
      type: object
      required:
        - serviceUnitName
        - socketUnitName
        - servicePath
        - socketPath
        - envPath
        - tmpfilesPath
        - service
        - socket
        - environment
        - tmpfiles
      properties:
        serviceUnitName:
          type: string
        socketUnitName:
          type: string
        servicePath:
          type: string
        socketPath:
          type: string
        envPath:
          type: string
        tmpfilesPath:
          type: string
        service:
          type: string
        socket:
          type: string
        environment:
          type: string
        tmpfiles:
          type: string
    ReconcileResult:
      type: object
      required: [mode, tenants]
      properties:
        mode:
          type: string
          enum: [mock, systemd]
        tenants:
          type: array
          items:
            $ref: "#/components/schemas/TenantReconcileResult"
    TenantReconcileResult:
      type: object
      required: [id, action, status]
      properties:
        id:
          type: string
        action:
          type: string
          enum: [provision, stop, skip]
        status:
          type: string
          enum: [ok, error]
        error:
          type: string
    BrowserConfig:
      type: object
      additionalProperties: false
      properties:
        enabled:
          type: boolean
          default: false
        profileDir:
          type: string
    Quotas:
      type: object
      additionalProperties: false
      properties:
        memoryMax:
          type: string
          default: 1G
        cpuQuota:
          type: string
          default: 100%
    AuthConfig:
      type: object
      additionalProperties: false
      properties:
        jwtSubjects:
          type: array
          items:
            type: string
    Problem:
      type: object
      required: [type, title, status, detail, code]
      properties:
        type:
          type: string
        title:
          type: string
        status:
          type: integer
        detail:
          type: string
        code:
          type: string
        instance:
          type: string
