openapi: 3.1.2
info:
  title: Neotask Runner Control Plane
  version: 2.2.0
  description: >-
    Runner-only delivery protocol. Every request requires an Agent bearer
    token with neotask:runners:execute and a per-installation
    NEOTASK-RUNNER-HMAC-V1 proof.
servers:
  - url: https://neotask.ai/api/agent-runner/v1
security:
  - AgentBearer: []
    RunnerHmac: []
paths:
  /runners/{runnerId}/recovery/company-disposition:
    post:
      operationId: readAgentRunnerCompanyRecoveryDisposition
      summary: Read the authoritative retention disposition for a local company cache
      parameters: [{ $ref: '#/components/parameters/RunnerId' }]
      requestBody: { $ref: '#/components/requestBodies/RecoveryCompany' }
      responses:
        '200':
          description: A sealed or unresolved company is retained; only a committed erasure authorizes disposal.
          headers:
            Cache-Control: { schema: { type: string, const: no-store } }
            Pragma: { schema: { type: string, const: no-cache } }
          content: { application/json: { schema: { $ref: '#/components/schemas/CompanyRecoveryDisposition' } } }
        '400': { $ref: '#/components/responses/Error' }
        '401': { $ref: '#/components/responses/Error' }
        '403': { $ref: '#/components/responses/Error' }
        '404': { $ref: '#/components/responses/Error' }
        '503': { $ref: '#/components/responses/Error' }
  /runners/{runnerId}/recovery/company-export-authorize:
    post:
      operationId: authorizeAgentRunnerCompanyRecoveryExport
      summary: Check current authority for an attended local company recovery export
      parameters: [{ $ref: '#/components/parameters/RunnerId' }]
      requestBody: { $ref: '#/components/requestBodies/RecoveryCompany' }
      responses:
        '200':
          description: Current account and company owner/admin authority was verified; no content or execution grant is returned.
          headers:
            Cache-Control: { schema: { type: string, const: no-store } }
            Pragma: { schema: { type: string, const: no-cache } }
          content: { application/json: { schema: { $ref: '#/components/schemas/CompanyRecoveryExportAuthorization' } } }
        '400': { $ref: '#/components/responses/Error' }
        '401': { $ref: '#/components/responses/Error' }
        '403': { $ref: '#/components/responses/Error' }
        '404': { $ref: '#/components/responses/Error' }
        '503': { $ref: '#/components/responses/Error' }
  /runners/{runnerId}/recovery/export-authorize:
    post:
      operationId: authorizeAgentRunnerRecoveryExport
      summary: Check current authority for an attended local run recovery export
      parameters: [{ $ref: '#/components/parameters/RunnerId' }]
      requestBody: { $ref: '#/components/requestBodies/RecoveryBinding' }
      responses:
        '200':
          description: Current owner/admin authority was verified for the exact binding; local consent is still required.
          headers:
            Cache-Control: { schema: { type: string, const: no-store } }
            Pragma: { schema: { type: string, const: no-cache } }
          content: { application/json: { schema: { $ref: '#/components/schemas/RecoveryExportAuthorization' } } }
        '400': { $ref: '#/components/responses/Error' }
        '401': { $ref: '#/components/responses/Error' }
        '403': { $ref: '#/components/responses/Error' }
        '404': { $ref: '#/components/responses/Error' }
        '503': { $ref: '#/components/responses/Error' }
  /runners/{runnerId}/recovery/disposition:
    post:
      operationId: readAgentRunnerRecoveryDisposition
      summary: Read a local journal's exact erasure or delivered-result disposition
      parameters: [{ $ref: '#/components/parameters/RunnerId' }]
      requestBody: { $ref: '#/components/requestBodies/RecoveryBinding' }
      responses:
        '200':
          description: Missing rows, lease expiry and age return retain; a delivered result includes its immutable SHA-256 receipt.
          headers:
            Cache-Control: { schema: { type: string, const: no-store } }
            Pragma: { schema: { type: string, const: no-cache } }
          content: { application/json: { schema: { $ref: '#/components/schemas/RecoveryDisposition' } } }
        '400': { $ref: '#/components/responses/Error' }
        '401': { $ref: '#/components/responses/Error' }
        '403': { $ref: '#/components/responses/Error' }
        '404': { $ref: '#/components/responses/Error' }
        '503': { $ref: '#/components/responses/Error' }
  /runners/{runnerId}/confirm:
    post:
      operationId: confirmRunnerEnrollment
      parameters:
        - $ref: '#/components/parameters/RunnerId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [challenge]
              properties:
                challenge: { type: string, minLength: 1, maxLength: 4096 }
      responses: { '200': { $ref: '#/components/responses/Json' }, '401': { $ref: '#/components/responses/Error' }, '403': { $ref: '#/components/responses/Error' }, '409': { $ref: '#/components/responses/Error' }, '503': { $ref: '#/components/responses/Error' } }
  /runners/{runnerId}/heartbeat:
    post:
      operationId: heartbeatAgentRunner
      parameters: [{ $ref: '#/components/parameters/RunnerId' }]
      requestBody: { $ref: '#/components/requestBodies/Heartbeat' }
      responses: { '200': { $ref: '#/components/responses/Json' }, '400': { $ref: '#/components/responses/Error' }, '401': { $ref: '#/components/responses/Error' }, '403': { $ref: '#/components/responses/Error' }, '503': { $ref: '#/components/responses/Error' } }
  /runners/{runnerId}/lifecycle/drain:
    post:
      operationId: beginAgentRunnerDrain
      parameters: [{ $ref: '#/components/parameters/RunnerId' }]
      requestBody: { $ref: '#/components/requestBodies/BeginDrain' }
      responses: { '200': { $ref: '#/components/responses/Json' }, '400': { $ref: '#/components/responses/Error' }, '401': { $ref: '#/components/responses/Error' }, '403': { $ref: '#/components/responses/Error' }, '404': { $ref: '#/components/responses/Error' }, '409': { $ref: '#/components/responses/Error' }, '503': { $ref: '#/components/responses/Error' } }
  /runners/{runnerId}/lifecycle/drain/status:
    post:
      operationId: inspectAgentRunnerDrain
      parameters: [{ $ref: '#/components/parameters/RunnerId' }]
      requestBody: { $ref: '#/components/requestBodies/LifecycleFence' }
      responses: { '200': { $ref: '#/components/responses/Json' }, '400': { $ref: '#/components/responses/Error' }, '401': { $ref: '#/components/responses/Error' }, '403': { $ref: '#/components/responses/Error' }, '404': { $ref: '#/components/responses/Error' }, '409': { $ref: '#/components/responses/Error' }, '503': { $ref: '#/components/responses/Error' } }
  /runners/{runnerId}/lifecycle/drain/resume:
    post:
      operationId: resumeAgentRunnerAfterDrain
      parameters: [{ $ref: '#/components/parameters/RunnerId' }]
      requestBody: { $ref: '#/components/requestBodies/LifecycleFence' }
      responses: { '200': { $ref: '#/components/responses/Json' }, '400': { $ref: '#/components/responses/Error' }, '401': { $ref: '#/components/responses/Error' }, '403': { $ref: '#/components/responses/Error' }, '404': { $ref: '#/components/responses/Error' }, '409': { $ref: '#/components/responses/Error' }, '503': { $ref: '#/components/responses/Error' } }
  /runners/{runnerId}/lifecycle/drain/abort:
    post:
      operationId: abortAgentRunnerDrain
      parameters: [{ $ref: '#/components/parameters/RunnerId' }]
      requestBody: { $ref: '#/components/requestBodies/LifecycleFence' }
      responses: { '200': { $ref: '#/components/responses/Json' }, '400': { $ref: '#/components/responses/Error' }, '401': { $ref: '#/components/responses/Error' }, '403': { $ref: '#/components/responses/Error' }, '404': { $ref: '#/components/responses/Error' }, '409': { $ref: '#/components/responses/Error' }, '503': { $ref: '#/components/responses/Error' } }
  /runners/{runnerId}/leases/claim:
    post:
      operationId: claimAgentRunLease
      description: >-
        New selected-agent dispatches require agent-runtime-profile-v1 and
        agent-runtime-tool-policy-v1. Their version-2 runtime profile binds the
        current tool-policy revision, reviewed catalog digest, and runtime
        policy. Changed policy authority invalidates the retained profile.
      parameters: [{ $ref: '#/components/parameters/RunnerId' }]
      requestBody: { $ref: '#/components/requestBodies/Claim' }
      responses: { '200': { $ref: '#/components/responses/Json' }, '204': { description: No queued dispatch matches the runner. }, '401': { $ref: '#/components/responses/Error' }, '403': { $ref: '#/components/responses/Error' }, '503': { $ref: '#/components/responses/Error' } }
  /runners/{runnerId}/leases/{leaseId}/operation-authorize:
    post:
      operationId: authorizeAgentRunOperation
      summary: Authorize one live runner operation
      description: >-
        Online-only authorization for one fenced agent turn or tool execution.
        The runner must advertise live-operation-authorization-v2. The receipt
        is valid only for the exact operation, action, tool name, resource hash, dispatch,
        and lease fence supplied by the runner, and expires no later than five
        seconds after authorization or any earlier credential, license, or run
        lease expiry. For a selected-agent run, Site rereads the current runtime
        tool policy before issuing a receipt. A denied tool returns HTTP 403
        with tool_policy_denied and an optional source reason matching the
        prepared-run tool projection; a corrupt policy returns HTTP 503 without
        disclosing stored values. A changed profile or policy revision returns
        HTTP 409 with run_inactive and requires a new dispatch.
      parameters: [{ $ref: '#/components/parameters/RunnerId' }, { $ref: '#/components/parameters/LeaseId' }]
      requestBody: { $ref: '#/components/requestBodies/OperationAuthorization' }
      responses: { '200': { $ref: '#/components/responses/OperationAuthorization' }, '400': { $ref: '#/components/responses/Error' }, '401': { $ref: '#/components/responses/Error' }, '403': { $ref: '#/components/responses/Error' }, '404': { $ref: '#/components/responses/Error' }, '409': { $ref: '#/components/responses/Error' }, '503': { $ref: '#/components/responses/Error' } }
  /runners/{runnerId}/leases/{leaseId}/tool-inventory:
    post:
      operationId: reportAgentRunnerPreparedToolInventory
      summary: Report the selected root execution's prepared tool identities
      description: >-
        Stores a complete observation under the current lease and version-2
        selected profile. Site checks live authority in the write transaction.
        The observation grants no invocation rights and contains no schemas,
        descriptions, arguments or credentials. An identical current preparation
        is idempotent; altered content under the same preparation ID is rejected.
      parameters: [{ $ref: '#/components/parameters/RunnerId' }, { $ref: '#/components/parameters/LeaseId' }]
      requestBody: { $ref: '#/components/requestBodies/PreparedToolInventory' }
      responses:
        '200':
          description: The complete observed inventory was accepted under its current lease.
          headers:
            Cache-Control: { schema: { type: string, const: no-store } }
            Pragma: { schema: { type: string, const: no-cache } }
          content: { application/json: { schema: { $ref: '#/components/schemas/PreparedToolInventoryReceipt' } } }
        '400': { $ref: '#/components/responses/Error' }
        '401': { $ref: '#/components/responses/Error' }
        '403': { $ref: '#/components/responses/Error' }
        '404': { $ref: '#/components/responses/Error' }
        '409': { $ref: '#/components/responses/Error' }
        '503': { $ref: '#/components/responses/Error' }
  /runners/{runnerId}/leases/{leaseId}/prepared-tools/read:
    post:
      operationId: readAgentRunnerPreparedTools
      summary: Read the current tool view for this selected run
      description: >-
        Uses the runner bearer scope and installation HMAC. Site derives the
        agent and run references from the current dispatch, rechecks the lease,
        prepared capability, source access and approval policy, and returns the
        complete public prepared-run view. Send capabilityDigest when the run
        has a bound preparation. A stale binding fails closed. This read does
        not grant catalog scope, authorize an invocation or consume a delegation.
      parameters: [{ $ref: '#/components/parameters/RunnerId' }, { $ref: '#/components/parameters/LeaseId' }]
      requestBody: { $ref: '#/components/requestBodies/PreparedToolsRead' }
      responses:
        '200':
          description: Complete current prepared tools, instructions and approval policy for the selected run.
          headers:
            Cache-Control: { schema: { type: string, const: no-store } }
            Pragma: { schema: { type: string, const: no-cache } }
          content: { application/json: { schema: { $ref: '../../openapi.yaml#/components/schemas/AgentPreparedToolsView' } } }
        '400': { $ref: '#/components/responses/Error' }
        '401': { $ref: '#/components/responses/Error' }
        '403': { $ref: '#/components/responses/Error' }
        '404': { $ref: '#/components/responses/Error' }
        '409': { $ref: '#/components/responses/Error' }
        '503': { $ref: '#/components/responses/Error' }
  /runners/{runnerId}/leases/{leaseId}/approvals/request:
    post:
      operationId: requestAgentRunnerApproval
      summary: Request a human review for one runner operation
      description: >-
        Requires a claimed trusted agent account, current membership, and the
        exact dispatch, lease and selected profile. Only the signed-in human
        review route records a decision.
      parameters: [{ $ref: '#/components/parameters/RunnerId' }, { $ref: '#/components/parameters/LeaseId' }]
      requestBody: { $ref: '#/components/requestBodies/RunnerApproval' }
      responses:
        '200':
          description: Request a human review for one runner operation.
          headers:
            Cache-Control: { schema: { type: string, const: no-store } }
            Pragma: { schema: { type: string, const: no-cache } }
          content: { application/json: { schema: { $ref: '#/components/schemas/RunnerApprovalRequestResult' } } }
        '400': { $ref: '#/components/responses/Error' }
        '401': { $ref: '#/components/responses/Error' }
        '403': { $ref: '#/components/responses/Error' }
        '404': { $ref: '#/components/responses/Error' }
        '409': { $ref: '#/components/responses/Error' }
        '503': { $ref: '#/components/responses/Error' }
  /runners/{runnerId}/leases/{leaseId}/approvals/read:
    post:
      operationId: readAgentRunnerApproval
      summary: Read a runner approval under its current lease
      description: >-
        Requires a claimed trusted agent account, current membership, and the
        exact dispatch, lease and selected profile. Only the signed-in human
        review route records a decision.
      parameters: [{ $ref: '#/components/parameters/RunnerId' }, { $ref: '#/components/parameters/LeaseId' }]
      requestBody: { $ref: '#/components/requestBodies/RunnerApproval' }
      responses:
        '200':
          description: Read a runner approval under its current lease.
          headers:
            Cache-Control: { schema: { type: string, const: no-store } }
            Pragma: { schema: { type: string, const: no-cache } }
          content: { application/json: { schema: { $ref: '#/components/schemas/RunnerApprovalStatus' } } }
        '400': { $ref: '#/components/responses/Error' }
        '401': { $ref: '#/components/responses/Error' }
        '403': { $ref: '#/components/responses/Error' }
        '404': { $ref: '#/components/responses/Error' }
        '409': { $ref: '#/components/responses/Error' }
        '503': { $ref: '#/components/responses/Error' }
  /runners/{runnerId}/leases/{leaseId}/approvals/consume:
    post:
      operationId: consumeAgentRunnerApproval
      summary: Claim a human decision once for the waiting operation
      description: >-
        Requires a claimed trusted agent account, current membership, and the
        exact dispatch, lease and selected profile. Only the signed-in human
        review route records a decision. Never retry a lost consume response.
      parameters: [{ $ref: '#/components/parameters/RunnerId' }, { $ref: '#/components/parameters/LeaseId' }]
      requestBody: { $ref: '#/components/requestBodies/RunnerApproval' }
      responses:
        '200':
          description: Claim a human decision once for the waiting operation.
          headers:
            Cache-Control: { schema: { type: string, const: no-store } }
            Pragma: { schema: { type: string, const: no-cache } }
          content: { application/json: { schema: { $ref: '#/components/schemas/RunnerApprovalClaim' } } }
        '400': { $ref: '#/components/responses/Error' }
        '401': { $ref: '#/components/responses/Error' }
        '403': { $ref: '#/components/responses/Error' }
        '404': { $ref: '#/components/responses/Error' }
        '409': { $ref: '#/components/responses/Error' }
        '503': { $ref: '#/components/responses/Error' }
  /runners/{runnerId}/leases/{leaseId}/approvals/checkpoint:
    post:
      operationId: retainAgentRunnerApprovalCheckpoint
      summary: Retain the complete pending approval checkpoint for restart recovery
      description: >-
        Requires the current runner lease and a run waiting for approval.
        Every pending occurrence must match its stored approval and run binding.
        An identical retained checkpoint is idempotent; a different checkpoint
        conflicts. This route stores references and digests, never transcript
        content or a human decision. Only the recovery transaction can bind
        these occurrences to a successor lease.
      parameters: [{ $ref: '#/components/parameters/RunnerId' }, { $ref: '#/components/parameters/LeaseId' }]
      requestBody: { $ref: '#/components/requestBodies/RunnerApprovalCheckpoint' }
      responses:
        '200':
          description: The complete checkpoint was retained under the current lease.
          headers:
            Cache-Control: { schema: { type: string, const: no-store } }
            Pragma: { schema: { type: string, const: no-cache } }
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required: [schemaVersion, checkpoint]
                properties:
                  schemaVersion: { type: integer, const: 1 }
                  checkpoint: { $ref: '#/components/schemas/RunnerApprovalCheckpoint' }
        '400': { $ref: '#/components/responses/Error' }
        '401': { $ref: '#/components/responses/Error' }
        '403': { $ref: '#/components/responses/Error' }
        '404': { $ref: '#/components/responses/Error' }
        '409': { $ref: '#/components/responses/Error' }
        '503': { $ref: '#/components/responses/Error' }
  /runners/{runnerId}/controls/poll:
    post:
      operationId: pollAgentAutonomyRunControls
      parameters: [{ $ref: '#/components/parameters/RunnerId' }]
      requestBody: { $ref: '#/components/requestBodies/AutonomyControlPoll' }
      responses: { '200': { $ref: '#/components/responses/Json' }, '204': { description: No queued autonomy control matches the exact run fence. }, '401': { $ref: '#/components/responses/Error' }, '403': { $ref: '#/components/responses/Error' }, '409': { $ref: '#/components/responses/Error' }, '503': { $ref: '#/components/responses/Error' } }
  /runners/{runnerId}/controls/{commandId}/ack:
    post:
      operationId: acknowledgeAgentAutonomyRunControl
      parameters: [{ $ref: '#/components/parameters/RunnerId' }, { $ref: '#/components/parameters/CommandId' }]
      requestBody: { $ref: '#/components/requestBodies/AutonomyControlAck' }
      responses: { '200': { $ref: '#/components/responses/Json' }, '401': { $ref: '#/components/responses/Error' }, '403': { $ref: '#/components/responses/Error' }, '409': { $ref: '#/components/responses/Error' }, '503': { $ref: '#/components/responses/Error' } }
  /runners/{runnerId}/leases/{leaseId}/ack:
    post:
      operationId: acknowledgeAgentRunLease
      parameters: [{ $ref: '#/components/parameters/RunnerId' }, { $ref: '#/components/parameters/LeaseId' }]
      requestBody: { $ref: '#/components/requestBodies/Lease' }
      responses: { '200': { $ref: '#/components/responses/Json' }, '401': { $ref: '#/components/responses/Error' }, '403': { $ref: '#/components/responses/Error' }, '409': { $ref: '#/components/responses/Error' }, '503': { $ref: '#/components/responses/Error' } }
  /runners/{runnerId}/leases/{leaseId}/renew:
    post:
      operationId: renewAgentRunLease
      parameters: [{ $ref: '#/components/parameters/RunnerId' }, { $ref: '#/components/parameters/LeaseId' }]
      requestBody: { $ref: '#/components/requestBodies/Lease' }
      responses: { '200': { $ref: '#/components/responses/Json' }, '401': { $ref: '#/components/responses/Error' }, '403': { $ref: '#/components/responses/Error' }, '409': { $ref: '#/components/responses/Error' }, '503': { $ref: '#/components/responses/Error' } }
  /runners/{runnerId}/leases/{leaseId}/events:
    post:
      operationId: appendAgentRunEvents
      parameters: [{ $ref: '#/components/parameters/RunnerId' }, { $ref: '#/components/parameters/LeaseId' }]
      requestBody: { $ref: '#/components/requestBodies/Event' }
      responses: { '200': { $ref: '#/components/responses/Json' }, '401': { $ref: '#/components/responses/Error' }, '403': { $ref: '#/components/responses/Error' }, '409': { $ref: '#/components/responses/Error' }, '503': { $ref: '#/components/responses/Error' } }
  /runners/{runnerId}/leases/{leaseId}/result:
    post:
      operationId: completeAgentRunLease
      parameters: [{ $ref: '#/components/parameters/RunnerId' }, { $ref: '#/components/parameters/LeaseId' }]
      requestBody: { $ref: '#/components/requestBodies/Result' }
      responses: { '200': { $ref: '#/components/responses/Json' }, '401': { $ref: '#/components/responses/Error' }, '403': { $ref: '#/components/responses/Error' }, '409': { $ref: '#/components/responses/Error' }, '503': { $ref: '#/components/responses/Error' } }
  /runners/{runnerId}/leases/{leaseId}/abandon:
    post:
      operationId: abandonAgentRunLease
      parameters: [{ $ref: '#/components/parameters/RunnerId' }, { $ref: '#/components/parameters/LeaseId' }]
      requestBody: { $ref: '#/components/requestBodies/Lease' }
      responses: { '200': { $ref: '#/components/responses/Json' }, '401': { $ref: '#/components/responses/Error' }, '403': { $ref: '#/components/responses/Error' }, '409': { $ref: '#/components/responses/Error' }, '503': { $ref: '#/components/responses/Error' } }
components:
  securitySchemes:
    AgentBearer: { type: http, scheme: bearer }
    RunnerHmac: { type: apiKey, in: header, name: x-neotask-signature, description: See control-plane.md for the six-header canonical proof. }
  parameters:
    RunnerId: { name: runnerId, in: path, required: true, schema: { type: string, minLength: 1, maxLength: 80 } }
    LeaseId: { name: leaseId, in: path, required: true, schema: { type: string, minLength: 1, maxLength: 80 } }
    CommandId: { name: commandId, in: path, required: true, schema: { type: string, minLength: 1, maxLength: 100 } }
  requestBodies:
    RecoveryBinding:
      required: true
      content: { application/json: { schema: { $ref: '#/components/schemas/RecoveryBinding' } } }
    RecoveryCompany:
      required: true
      content: { application/json: { schema: { $ref: '#/components/schemas/RecoveryCompany' } } }
    PreparedToolsRead:
      required: true
      content:
        application/json:
          schema:
            type: object
            additionalProperties: false
            required: [dispatchId, leaseEpoch, runtimeProfileDigest]
            properties:
              dispatchId: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$' }
              leaseEpoch: { type: integer, minimum: 1, maximum: 1000000 }
              runtimeProfileDigest: { type: string, pattern: '^[a-f0-9]{64}$' }
              capabilityDigest: { type: string, pattern: '^[a-f0-9]{64}$' }
    RunnerApprovalCheckpoint:
      required: true
      content:
        application/json:
          schema:
            type: object
            additionalProperties: false
            required: [dispatchId, leaseEpoch, checkpoint]
            properties:
              dispatchId: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$' }
              leaseEpoch: { type: integer, minimum: 1, maximum: 1000000 }
              runtimeProfileDigest: { type: string, pattern: '^[a-f0-9]{64}$' }
              checkpoint: { $ref: '#/components/schemas/RunnerApprovalCheckpoint' }
    PreparedToolInventory:
      required: true
      content:
        application/json:
          schema:
            type: object
            additionalProperties: false
            required: [dispatchId, leaseEpoch, runtimeProfileDigest, inventory]
            properties:
              dispatchId: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$' }
              leaseEpoch: { type: integer, minimum: 1, maximum: 1000000 }
              runtimeProfileDigest: { type: string, pattern: '^[a-f0-9]{64}$' }
              inventory: { $ref: '#/components/schemas/PreparedToolInventory' }
    Heartbeat:
      required: false
      content: { application/json: { schema: { $ref: '#/components/schemas/RunnerHeartbeat' } } }
    RunnerApproval:
      required: true
      content: { application/json: { schema: { $ref: '#/components/schemas/RunnerApprovalRequest' } } }
    Json:
      required: false
      content: { application/json: { schema: { type: object, additionalProperties: true } } }
    BeginDrain:
      required: true
      content:
        application/json:
          schema:
            type: object
            required: [expectedRunnerGeneration, expectedLeaseGeneration, intentId, purpose]
            additionalProperties: false
            properties:
              expectedRunnerGeneration: { type: integer, minimum: 1, maximum: 9007199254740990 }
              expectedLeaseGeneration: { type: integer, minimum: 1, maximum: 9007199254740990 }
              intentId: { type: string, minLength: 16, maxLength: 128, pattern: '^[A-Za-z0-9._~-]{16,128}$' }
              purpose: { type: string, enum: [service_lifecycle, electron_adoption] }
    LifecycleFence:
      required: true
      content:
        application/json:
          schema:
            type: object
            required: [runnerGeneration, leaseGeneration, intentId, lifecycleToken]
            additionalProperties: false
            properties:
              runnerGeneration: { type: integer, minimum: 1, maximum: 9007199254740990 }
              leaseGeneration: { type: integer, minimum: 1, maximum: 9007199254740990 }
              intentId: { type: string, minLength: 16, maxLength: 128, pattern: '^[A-Za-z0-9._~-]{16,128}$' }
              lifecycleToken: { type: string, pattern: '^rdr_[A-Za-z0-9_-]{43}$' }
    Claim:
      required: false
      content:
        application/json:
          schema:
            type: object
            properties:
              waitMs: { type: integer, minimum: 0, maximum: 25000, default: 25000 }
    OperationAuthorization:
      required: true
      content:
        application/json:
          schema:
            type: object
            required: [dispatchId, leaseEpoch, action, operationId, resourceHash, toolName]
            additionalProperties: false
            allOf: [{ $ref: '#/components/schemas/OperationToolIdentity' }]
            properties:
              dispatchId: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$' }
              leaseEpoch: { type: integer, minimum: 1, maximum: 1000000 }
              action: { type: string, enum: [agent.turn, tool.execute] }
              toolName: { type: [string, 'null'] }
              operationId: { type: string, minLength: 43, maxLength: 43, pattern: '^[A-Za-z0-9_-]{43}$' }
              resourceHash:
                type: string
                minLength: 64
                maxLength: 64
                pattern: '^[0-9a-f]{64}$'
                description: SHA-256 of Gateway canonical JSON for { toolName, resource }; private resource contents are not sent.
              runtimeProfileDigest: { type: string, pattern: '^[a-f0-9]{64}$' }
              capabilityDigest: { type: string, pattern: '^[a-f0-9]{64}$', description: Required when the dispatch requires agent-prepared-capability-binding-v1. Use the current prepared-inventory acknowledgement. }
    AutonomyControlPoll:
      required: true
      content:
        application/json:
          schema:
            type: object
            required: [dispatchId, leaseId, leaseEpoch, runId, stateSeq, taskId]
            additionalProperties: false
            properties:
              dispatchId: { type: string, minLength: 1, maxLength: 80 }
              leaseId: { type: string, minLength: 1, maxLength: 80 }
              leaseEpoch: { type: integer, minimum: 1, maximum: 1000000 }
              runId: { type: string, minLength: 1, maxLength: 256 }
              stateSeq: { type: integer, minimum: 0, maximum: 1000000 }
              taskId: { type: string, minLength: 1, maxLength: 200 }
    AutonomyControlAck:
      required: true
      content:
        application/json:
          schema:
            type: object
            required: [dispatchId, leaseId, leaseEpoch, runId, stateSeq, taskId, deliveryId, accepted, acknowledgement]
            additionalProperties: false
            properties:
              dispatchId: { type: string, minLength: 1, maxLength: 80 }
              leaseId: { type: string, minLength: 1, maxLength: 80 }
              leaseEpoch: { type: integer, minimum: 1, maximum: 1000000 }
              runId: { type: string, minLength: 1, maxLength: 256 }
              stateSeq: { type: integer, minimum: 0, maximum: 1000000 }
              taskId: { type: string, minLength: 1, maxLength: 200 }
              deliveryId: { type: string, minLength: 1, maxLength: 100 }
              accepted: { type: boolean }
              acknowledgement: { type: object, additionalProperties: true, nullable: true }
    Lease:
      required: true
      content:
        application/json:
          schema:
            type: object
            required: [leaseEpoch]
            properties:
              leaseEpoch: { type: integer, minimum: 1, maximum: 1000000 }
              accepted: { type: boolean }
    Event:
      required: true
      content:
        application/json:
          schema:
            type: object
            required: [leaseEpoch, eventKey, sequence, type]
            properties:
              leaseEpoch: { type: integer, minimum: 1, maximum: 1000000 }
              eventKey: { type: string, minLength: 1, maxLength: 200 }
              sequence: { type: integer, minimum: 1, maximum: 1000000 }
              type: { type: string, enum: [progress, approval_requested, approval_resolved, started, heartbeat] }
              occurredAt: { type: string, format: date-time }
              payload: {}
    Result:
      required: true
      content:
        application/json:
          schema:
            type: object
            required: [leaseEpoch]
            properties:
              leaseEpoch: { type: integer, minimum: 1, maximum: 1000000 }
              status: { type: string, enum: [succeeded, failed] }
              result: {}
  responses:
    Json:
      description: JSON control-plane response.
      content: { application/json: { schema: { type: object, additionalProperties: true } } }
    Error:
      description: Error response.
      content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } }
    OperationAuthorization:
      description: A fresh authorization receipt for one fenced live operation.
      headers:
        Cache-Control:
          schema: { type: string, const: no-store }
        Pragma:
          schema: { type: string, const: no-cache }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/OperationAuthorizationReceipt' }
  schemas:
    RecoveryIdentity:
      type: string
      minLength: 1
      maxLength: 256
      pattern: '^[A-Za-z0-9][A-Za-z0-9._:~-]*$'
    RecoveryBinding:
      type: object
      additionalProperties: false
      required: [dispatchId, runId, taskId, companyId, leaseId, leaseEpoch]
      properties:
        dispatchId: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$' }
        runId: { $ref: '#/components/schemas/RecoveryIdentity' }
        taskId: { $ref: '#/components/schemas/RecoveryIdentity' }
        companyId: { $ref: '#/components/schemas/RecoveryIdentity' }
        leaseId: { type: string, pattern: '^lease_[0-9a-f-]{36}$' }
        leaseEpoch: { type: integer, minimum: 1, maximum: 9007199254740991 }
    RecoveryCompany:
      type: object
      additionalProperties: false
      required: [companyId]
      properties:
        companyId: { type: string, pattern: '^[A-Za-z0-9_-]{1,128}$' }
    RecoveryExportAuthorization:
      type: object
      additionalProperties: false
      required: [schemaVersion, tenantId, runnerId, binding, authorized]
      properties:
        schemaVersion: { type: integer, const: 1 }
        tenantId: { type: string }
        runnerId: { type: string }
        binding: { $ref: '#/components/schemas/RecoveryBinding' }
        authorized: { type: boolean, const: true }
    CompanyRecoveryExportAuthorization:
      type: object
      additionalProperties: false
      required: [schemaVersion, tenantId, runnerId, companyId, authorized]
      properties:
        schemaVersion: { type: integer, const: 1 }
        tenantId: { type: string }
        runnerId: { type: string }
        companyId: { type: string, pattern: '^[A-Za-z0-9_-]{1,128}$' }
        authorized: { type: boolean, const: true }
    CompanyRecoveryDisposition:
      type: object
      additionalProperties: false
      required: [schemaVersion, tenantId, runnerId, companyId, disposition]
      properties:
        schemaVersion: { type: integer, const: 1 }
        tenantId: { type: string }
        runnerId: { type: string }
        companyId: { type: string, pattern: '^[A-Za-z0-9_-]{1,128}$' }
        disposition: { type: string, enum: [retain, company_erased] }
    RecoveryDisposition:
      type: object
      additionalProperties: false
      required: [schemaVersion, tenantId, runnerId, binding, disposition, resultHash]
      properties:
        schemaVersion: { type: integer, const: 1 }
        tenantId: { type: string }
        runnerId: { type: string }
        binding: { $ref: '#/components/schemas/RecoveryBinding' }
        disposition: { type: string, enum: [retain, company_erased, result_delivered] }
        resultHash: { type: [string, 'null'], pattern: '^[a-f0-9]{64}$' }
      oneOf:
        - properties: { disposition: { const: result_delivered }, resultHash: { type: string } }
        - properties: { disposition: { enum: [retain, company_erased] }, resultHash: { type: 'null' } }
    RunnerCheckpointReference:
      type: string
      minLength: 1
      maxLength: 256
      description: Exact reference without surrounding whitespace or ASCII controls.
      pattern: '^(?![\s\S]*[\u0000-\u001f\u007f])\S(?:[\s\S]*\S)?(?![\s\S])'
    RunnerApprovalCheckpoint:
      type: object
      additionalProperties: false
      required: [schemaVersion, checkpointId, sessionId, leafId, transcriptDigest, pending]
      x-max-json-utf8-bytes: 6144
      properties:
        schemaVersion: { type: integer, const: 1 }
        checkpointId: { type: string, pattern: '^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$' }
        sessionId: { $ref: '#/components/schemas/RunnerCheckpointReference' }
        leafId: { $ref: '#/components/schemas/RunnerCheckpointReference' }
        transcriptDigest: { type: string, pattern: '^[a-f0-9]{64}$' }
        pending:
          type: array
          minItems: 1
          maxItems: 16
          uniqueItems: true
          x-unique-by: [toolCallId, approvalId]
          description: Complete pending occurrences, with distinct toolCallId and approvalId values.
          items:
            type: object
            additionalProperties: false
            required: [approvalId, toolCallId, operationHash, requestDigest]
            properties:
              approvalId: { type: string, pattern: '^runner:[a-f0-9]{64}$' }
              toolCallId: { $ref: '#/components/schemas/RunnerCheckpointReference' }
              operationHash: { type: string, pattern: '^[a-f0-9]{64}$' }
              requestDigest: { type: string, pattern: '^sha256:[a-f0-9]{64}$' }
    PreparedToolIdentity:
      type: string
      minLength: 1
      maxLength: 512
      x-max-utf8-bytes: 512
      description: Exact case-sensitive name, without surrounding whitespace or ASCII controls.
      pattern: '^(?![\s\S]*[\u0000-\u001f\u007f])\S(?:[\s\S]*\S)?(?![\s\S])'
    PreparedTool:
      type: object
      additionalProperties: false
      required: [name, source, pluginId, serverId, originalName]
      properties:
        name: { $ref: '#/components/schemas/PreparedToolIdentity' }
        source: { type: string, enum: [core, plugin, mcp] }
        pluginId: { oneOf: [{ $ref: '#/components/schemas/PreparedToolIdentity' }, { type: 'null' }] }
        serverId: { oneOf: [{ $ref: '#/components/schemas/PreparedToolIdentity' }, { type: 'null' }] }
        originalName: { oneOf: [{ $ref: '#/components/schemas/PreparedToolIdentity' }, { type: 'null' }] }
      oneOf:
        - properties: { source: { const: core }, pluginId: { type: 'null' }, serverId: { type: 'null' }, originalName: { type: 'null' } }
        - properties: { source: { const: plugin }, pluginId: { type: string }, serverId: { type: 'null' }, originalName: { type: 'null' } }
        - properties: { source: { const: mcp }, pluginId: { type: string }, serverId: { type: string }, originalName: { type: string } }
    PreparedToolInventory:
      type: object
      additionalProperties: false
      required: [schemaVersion, preparationId, runtimeFlavor, catalogDigest, tools]
      x-max-json-utf8-bytes: 524288
      properties:
        schemaVersion: { type: integer, const: 1 }
        preparationId: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$' }
        runtimeFlavor: { type: string, enum: [electron-managed, standalone-agent-runner] }
        catalogDigest: { type: string, pattern: '^[a-f0-9]{64}$' }
        tools:
          type: array
          maxItems: 4096
          uniqueItems: true
          x-unique-by: name
          description: Complete prepared tools; duplicate exact names are invalid. An empty list records that no tools were prepared.
          items: { $ref: '#/components/schemas/PreparedTool' }
    PreparedToolInventoryReceipt:
      type: object
      additionalProperties: false
      required: [accepted, inventoryDigest]
      properties:
        accepted: { type: boolean, const: true }
        inventoryDigest: { type: string, pattern: '^[a-f0-9]{64}$' }
        capabilityDigest: { type: string, pattern: '^[a-f0-9]{64}$', description: Present when the dispatch requires agent-prepared-capability-binding-v1. Binds the complete current preparation and source decisions to the current run and lease. }
    RunnerHeartbeat:
      type: object
      description: >-
        Signed runner liveness and packaged-inventory report. Omitted metadata
        preserves the stored value. Tenant, principal, plan, scope, inventory
        digest and inventory generation are server-owned and cannot be supplied.
      properties:
        capabilities: { type: array, maxItems: 100, items: { type: string, minLength: 1, maxLength: 80 } }
        platform: { type: string, minLength: 1, maxLength: 32 }
        architecture: { type: string, minLength: 1, maxLength: 32 }
        runnerVersion: { type: string, minLength: 1, maxLength: 64 }
        activeRunIds: { type: array, maxItems: 100, items: { type: string, minLength: 1, maxLength: 256 } }
        runtimeToolInventory: { $ref: '#/components/schemas/RunnerToolInventoryReport' }
    RunnerToolInventoryReport:
      type: object
      additionalProperties: false
      required: [schemaVersion, runtimeFlavor, catalogDigest, toolNames]
      description: >-
        Packaged names only. Site intersects exact names with its reviewed
        catalog and enrolled runner type. Unknown names confer no access and
        are not retained. Neither this report nor its digest grants execution.
      properties:
        schemaVersion: { type: integer, const: 1 }
        runtimeFlavor: { type: string, enum: [electron-managed, standalone-agent-runner] }
        catalogDigest: { type: string, pattern: '^[a-f0-9]{64}$', description: Digest of the runner package catalog; Site retains its own reviewed catalog identity separately. }
        toolNames:
          type: array
          maxItems: 4096
          uniqueItems: true
          x-max-json-utf8-bytes: 262144
          description: An empty list removes every previously reported packaged name. The serialized list is limited to 256 KiB.
          items:
            type: string
            minLength: 1
            maxLength: 512
            x-max-utf8-bytes: 512
            pattern: '^(?![\s\S]*[\u0000-\u001f\u007f])\S(?:[\s\S]*\S)?(?![\s\S])'
    RunnerApprovalRequest:
      type: object
      additionalProperties: false
      description: Complete JSON body, limited to 768 KiB in UTF-8.
      required: [dispatchId, leaseEpoch, operation]
      properties:
        dispatchId: { type: string, pattern: '^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$' }
        leaseEpoch: { type: integer, minimum: 1, maximum: 1000000 }
        runtimeProfileDigest: { type: string, pattern: '^[a-f0-9]{64}$', description: Required for a selected-agent dispatch. }
        capabilityDigest: { type: string, pattern: '^[a-f0-9]{64}$', description: Required when the dispatch requires agent-prepared-capability-binding-v1. Use the current prepared-inventory acknowledgement. }
        operation: { $ref: '#/components/schemas/RunnerApprovalOperation' }
    RunnerApprovalOperation:
      type: object
      additionalProperties: false
      required: [toolCallId, kind, toolName, operationHash, title, parameters, riskLevel, allowedDecisions]
      properties:
        toolCallId: { type: string, minLength: 1, maxLength: 256 }
        kind: { type: string, enum: [plugin, exec] }
        toolName: { type: string, minLength: 1, maxLength: 256 }
        operationHash: { type: string, pattern: '^[a-f0-9]{64}$' }
        normalizedArgsDigest: { type: string, pattern: '^sha256:[a-f0-9]{64}$', description: Digest of complete canonical arguments before review redaction. Required for delegated approval authority. }
        title: { type: string, minLength: 1, maxLength: 524288 }
        description: { type: string, maxLength: 524288 }
        parameters: { type: string, maxLength: 524288, contentMediaType: application/json, description: Complete redacted JSON; never a clipped preview. }
        riskLevel: { type: string, enum: [low, medium, high, critical] }
        allowedDecisions:
          type: array
          minItems: 2
          maxItems: 3
          uniqueItems: true
          items: { type: string, enum: [allow-once, allow-always, deny] }
          contains: { const: deny }
    RunnerApprovalStatus:
      type: object
      additionalProperties: false
      required: [schemaVersion, approvalId, operationHash, status]
      properties:
        schemaVersion: { type: integer, const: 1 }
        approvalId: { type: string, pattern: '^runner:[a-f0-9]{64}$' }
        operationHash: { type: string, pattern: '^[a-f0-9]{64}$' }
        status: { type: string, enum: [pending, approved, denied] }
    RunnerApprovalRequestResult:
      oneOf:
        - type: object
          additionalProperties: false
          required: [schemaVersion, approvalId, operationHash, status, humanReviewUrl, handoffExpiresAt]
          properties:
            schemaVersion: { type: integer, const: 1 }
            approvalId: { type: string, pattern: '^runner:[a-f0-9]{64}$' }
            operationHash: { type: string, pattern: '^[a-f0-9]{64}$' }
            status: { type: string, const: pending }
            humanReviewUrl: { type: string, format: uri, description: Same-origin signed-in human review handoff. }
            handoffExpiresAt: { type: string, format: date-time }
        - allOf:
            - { $ref: '#/components/schemas/RunnerApprovalStatus' }
            - properties: { status: { enum: [approved, denied] } }
    RunnerApprovalClaim:
      type: object
      additionalProperties: false
      description: >-
        Single-use claim for the waiting operation, expiring within five
        seconds and capped by the current credential, license and lease.
        A claim is not an execution receipt. Never retry an uncertain claim.
      required: [schemaVersion, approvalId, operationHash, status, decision, authorizationExpiresAt]
      properties:
        schemaVersion: { type: integer, const: 1 }
        approvalId: { type: string, pattern: '^runner:[a-f0-9]{64}$' }
        operationHash: { type: string, pattern: '^[a-f0-9]{64}$' }
        status: { type: string, enum: [approved, denied] }
        decision: { type: string, enum: [allow-once, allow-always, deny] }
        authorizationExpiresAt: { type: string, format: date-time }
      oneOf:
        - properties: { status: { const: approved }, decision: { enum: [allow-once, allow-always] } }
        - properties: { status: { const: denied }, decision: { const: deny } }
    Error:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code]
          properties:
            code: { type: string }
            message: { type: string }
            reason: { $ref: '#/components/schemas/PreparedToolSourceDenialReason' }
    PreparedToolSourceDenialReason:
      type: string
      description: A server-authored source diagnostic. It grants no execution, retry, or approval authority.
      enum:
        - tool_inventory_unavailable
        - tool_not_prepared
        - runner_tool_unavailable
        - tool_source_unreviewed
        - runtime_profile_stale
        - tool_policy_denied
        - verified_user_claim_required
        - company_access_denied
        - company_context_required
        - hipaa_disabled
        - plan_feature_required
        - integration_scope_mismatch
        - integration_connection_required
        - integration_unavailable
        - integration_inventory_stale
    OperationToolIdentity:
      type: object
      required: [action, toolName]
      oneOf:
        - properties:
            action: { const: agent.turn }
            toolName: { type: 'null' }
        - properties:
            action: { const: tool.execute }
            toolName:
              type: string
              minLength: 1
              maxLength: 512
              x-max-utf8-bytes: 512
              pattern: '^(?![\s\S]*[\u0000-\u001f\u007f])\S(?:[\s\S]*\S)?(?![\s\S])'
              description: Exact runtime name, at most 512 UTF-8 bytes; no surrounding whitespace or ASCII controls. Preserve case, company suffixes and bundle aliases.
    OperationAuthorizationReceipt:
      type: object
      description: >-
        The receipt is fresh for one exact operation. authorizationExpiresAt
        is no later than five seconds after authorizedAt and is capped by the
        credential, license, dispatch lease, and run lease expiry.
      required: [authorized, operationId, action, resourceHash, toolName, authorizedAt, authorizationExpiresAt]
      additionalProperties: false
      allOf: [{ $ref: '#/components/schemas/OperationToolIdentity' }]
      properties:
        authorized: { type: boolean, const: true }
        operationId: { type: string, minLength: 43, maxLength: 43, pattern: '^[A-Za-z0-9_-]{43}$' }
        action: { type: string, enum: [agent.turn, tool.execute] }
        toolName: { type: [string, 'null'] }
        resourceHash: { type: string, minLength: 64, maxLength: 64, pattern: '^[0-9a-f]{64}$' }
        authorizedAt: { type: string, format: date-time }
        authorizationExpiresAt: { type: string, format: date-time }
