openapi: 3.1.0
info:
  title: Angareion Agent API
  version: 1.0.0
  description: |
    The agent-facing API for Angareion — autonomous-agent sensing and memory.
    Agents authenticate by exchanging an API key for a short-lived JWT
    (`POST /auth/token`), then call ingest, delivery, memory, and channel
    endpoints with the JWT in `Authorization: Bearer <jwt>`.

    Errors follow the canonical envelope (see `Error` schema). Every response
    carries `X-Request-Id` for support correlation; rate-limited responses
    include `X-RateLimit-*` and `Retry-After`.
  contact:
    name: Angareion Support
    url: https://angareion.com/support
servers:
  - url: https://api.angareion.com/v1
    description: Production
  - url: http://localhost:8080/v1
    description: Local development
security:
  - BearerAuth: []
tags:
  - name: Auth
    description: API key → JWT exchange.
  - name: Events
    description: Publish events into the sensing pipeline.
  - name: Delivery
    description: Poll and acknowledge deliveries.
  - name: DLQ
    description: Dead-letter queue inspection and replay.
  - name: Agents
    description: Agent registry CRUD and lifecycle.
  - name: Agent Keys
    description: Per-agent API key issuance and revocation.
  - name: Interests
    description: Per-agent interest declarations.
  - name: Channels
    description: Multi-participant channels, members, and feeds.
  - name: Memories
    description: Memory CRUD, scope promotion, and search.
paths:
  /auth/token:
    post:
      operationId: exchangeToken
      tags:
        - Auth
      summary: Exchange API key for short-lived JWT
      description: >
        Public endpoint. Accepts an API key in the request body and returns a
        1-hour

        JWT. Per PRD-03 §4.2. The API key in the body IS the credential — do not
        send

        an Authorization header.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TokenExchangeRequest'
            examples:
              standard:
                summary: Exchange a live API key
                value:
                  api_key: ak_live_AbCdEfGhIjKlMnOpQrStUvWxYz0123456
      responses:
        '200':
          description: JWT issued successfully
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenExchangeResponse'
              examples:
                standard:
                  summary: Successful exchange
                  value:
                    access_token: eyJhbGciOiJIUzI1NiIs.eyJzdWIiOiJhZ18.signature
                    token_type: Bearer
                    expires_at: '2026-05-28T15:30:00Z'
                    agent:
                      id: ag_01H7abc12345
                      name: production-classifier
                      team: platform
                      tenant_id: tn_01H7abc12345
        '400':
          description: Invalid request body
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                bad_body:
                  summary: Malformed JSON
                  value:
                    error:
                      code: invalid_key
                      message: invalid request body
                      request_id: req_01H7abc12345
        '401':
          description: API key invalid, expired, revoked, or agent inactive
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                invalid_key:
                  summary: >-
                    Key not found or revoked (anti-enumeration: same response
                    for both)
                  value:
                    error:
                      code: invalid_key
                      message: invalid API key
                      request_id: req_01H7abc12345
                key_expired:
                  summary: Key past expires_at
                  value:
                    error:
                      code: key_expired
                      message: API key has expired
                      request_id: req_01H7abc12345
                agent_inactive:
                  summary: Owning agent is not active
                  value:
                    error:
                      code: agent_inactive
                      message: agent is not active
                      request_id: req_01H7abc12345
  /events:
    post:
      operationId: publishEvent
      tags:
        - Events
      summary: Publish a CloudEvents-formatted event
      description: >
        Validates the CloudEvents envelope, deduplicates by `id`, publishes to
        the

        sensing pipeline, and returns 202 Accepted. Duplicates return 200 with

        `status: "duplicate"` and the original event_id. Per PRD-01.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Event'
            examples:
              standard:
                summary: Publish a custom event
                value:
                  specversion: '1.0'
                  type: com.example.order.created
                  source: //orders.example.com
                  id: evt_01H7abc12345
                  time: '2026-05-28T15:00:00Z'
                  datacontenttype: application/json
                  subject: order/12345
                  data:
                    order_id: '12345'
                    amount_cents: 4900
                    currency: USD
      responses:
        '200':
          description: Duplicate event (idempotent replay returns the original ID)
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EventAcceptedResponse'
              examples:
                duplicate:
                  value:
                    event_id: evt_01H7abc12345
                    status: duplicate
        '202':
          description: Event accepted for processing
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EventAcceptedResponse'
              examples:
                accepted:
                  value:
                    event_id: evt_01H7abc12345
                    status: accepted
        '400':
          description: Invalid CloudEvents envelope or missing required field
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                missing_required:
                  value:
                    error:
                      code: validation_error
                      message: type field is required
                      request_id: req_01H7abc12345
        '401':
          description: Missing or invalid Bearer token
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '502':
          description: Upstream publish failed
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                publish_failed:
                  value:
                    error:
                      code: publish_failed
                      message: failed to publish event
                      request_id: req_01H7abc12345
  /delivery/poll:
    get:
      operationId: pollDeliveries
      tags:
        - Delivery
      summary: Poll pending deliveries for the authenticated agent
      description: |
        Returns up to `limit` pending deliveries scoped to the calling agent's
        agent_id (extracted from the JWT). Per PRD-01.
      parameters:
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 10
          description: Max deliveries to return. Server clamps to 100.
      responses:
        '200':
          description: Pending deliveries (possibly empty)
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeliveryListResponse'
              examples:
                with_items:
                  value:
                    deliveries:
                      - id: dlv_01H7abc12345
                        event_id: evt_01H7abc12345
                        payload:
                          type: com.example.order.created
                          data:
                            order_id: '12345'
                        attempt_count: 1
                        created_at: '2026-05-28T15:00:00Z'
                    count: 1
                empty:
                  value:
                    deliveries: []
                    count: 0
        '401':
          description: Missing or invalid Bearer token
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /delivery/ack:
    post:
      operationId: ackDeliveries
      tags:
        - Delivery
      summary: Acknowledge one or more deliveries
      description: |
        Marks the specified deliveries as acknowledged for the calling agent.
        Up to 100 IDs per call. The server enforces agent-id scoping.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AckRequest'
            examples:
              standard:
                value:
                  delivery_ids:
                    - dlv_01H7abc12345
                    - dlv_01H7def67890
      responses:
        '200':
          description: Acknowledgement count
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AckResponse'
              examples:
                standard:
                  value:
                    acknowledged: 2
        '400':
          description: Invalid request (empty or >100 IDs)
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                empty:
                  value:
                    error:
                      code: validation_error
                      message: delivery_ids must contain at least 1 ID
                      request_id: req_01H7abc12345
  /delivery/dlq:
    get:
      operationId: listDLQ
      tags:
        - DLQ
      summary: List dead-letter queue items
      description: >
        Returns DLQ items scoped to the calling agent (agent JWT) or to the
        tenant

        (org-admin JWT). The agent SDK only uses the agent-scoped variant.
      parameters:
        - name: offset
          in: query
          schema:
            type: integer
            minimum: 0
            default: 0
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
      responses:
        '200':
          description: DLQ page
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DLQListResponse'
              examples:
                with_items:
                  value:
                    dlq_items:
                      - id: dlv_01H7abc12345
                        event_id: evt_01H7abc12345
                        agent_id: ag_01H7abc12345
                        attempt_count: 5
                        dlq_reason: max attempts exceeded
                        dlq_at: '2026-05-28T14:55:00Z'
                        created_at: '2026-05-28T14:00:00Z'
                    total: 1
  /delivery/dlq/{id}/replay:
    post:
      operationId: replayDLQItem
      tags:
        - DLQ
      summary: Replay a DLQ item back to pending
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          example: dlv_01H7abc12345
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          description: Replay accepted
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReplayResponse'
              examples:
                replayed:
                  value:
                    status: replayed
                    delivery_id: dlv_01H7abc12345
        '404':
          description: Delivery not found or not in DLQ
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                not_found:
                  value:
                    error:
                      code: dlq_item_not_found
                      message: delivery not found or not in DLQ
                      request_id: req_01H7abc12345
  /agents:
    get:
      operationId: listAgents
      tags:
        - Agents
      summary: List agents in the caller's tenant
      parameters:
        - name: offset
          in: query
          schema:
            type: integer
            minimum: 0
            default: 0
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
        - name: status
          in: query
          schema:
            type: string
            enum:
              - active
              - inactive
      responses:
        '200':
          description: Page of agents
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentListResponse'
              examples:
                with_items:
                  value:
                    agents:
                      - id: ag_01H7abc12345
                        tenant_id: tn_01H7abc12345
                        name: production-classifier
                        status: active
                        delivery_mode: poll
                        team_id: team_01H7abc12345
                        created_at: '2026-05-01T10:00:00Z'
                        updated_at: '2026-05-28T15:00:00Z'
                    total: 1
        '401':
          description: Unauthorized
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    post:
      operationId: createAgent
      tags:
        - Agents
      summary: Register a new agent
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateAgentRequest'
            examples:
              poll_mode:
                value:
                  name: production-classifier
                  description: Classifies incoming order events by priority
                  delivery_mode: poll
                  client_type: other
                  team_id: team_01H7abc12345
      responses:
        '201':
          description: Agent created
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Agent'
              examples:
                created:
                  value:
                    id: ag_01H7abc12345
                    tenant_id: tn_01H7abc12345
                    name: production-classifier
                    description: Classifies incoming order events by priority
                    status: active
                    delivery_mode: poll
                    team_id: team_01H7abc12345
                    created_at: '2026-05-28T15:00:00Z'
                    updated_at: '2026-05-28T15:00:00Z'
        '400':
          description: Validation error
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                missing_name:
                  value:
                    error:
                      code: validation_error
                      message: name is required
                      request_id: req_01H7abc12345
  /agents/{id}:
    get:
      operationId: getAgent
      tags:
        - Agents
      summary: Get an agent by id
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          example: ag_01H7abc12345
      responses:
        '200':
          description: Agent detail
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Agent'
              examples:
                detail:
                  value:
                    id: ag_01H7abc12345
                    tenant_id: tn_01H7abc12345
                    name: production-classifier
                    description: Classifies incoming order events by priority
                    status: active
                    delivery_mode: poll
                    team_id: team_01H7abc12345
                    created_at: '2026-05-01T10:00:00Z'
                    updated_at: '2026-05-28T15:00:00Z'
        '404':
          description: Agent not found
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                not_found:
                  value:
                    error:
                      code: agent_not_found
                      message: agent not found
                      request_id: req_01H7abc12345
    patch:
      operationId: updateAgent
      tags:
        - Agents
      summary: Update an agent's metadata or delivery settings
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateAgentRequest'
            examples:
              switch_to_push:
                value:
                  delivery_mode: push
                  delivery_endpoint: https://agent.example.com/webhook
      responses:
        '200':
          description: Agent updated
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Agent'
              examples:
                updated:
                  value:
                    id: ag_01H7abc12345
                    tenant_id: tn_01H7abc12345
                    name: production-classifier
                    description: Classifies incoming order events by priority
                    status: active
                    delivery_mode: push
                    delivery_endpoint: https://agent.example.com/webhook
                    team_id: team_01H7abc12345
                    created_at: '2026-05-01T10:00:00Z'
                    updated_at: '2026-05-28T15:05:00Z'
        '404':
          description: Agent not found
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /agents/{id}:deactivate:
    post:
      operationId: deactivateAgent
      tags:
        - Agents
      summary: Set agent status to inactive
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          description: Agent deactivated
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                type: object
                required:
                  - status
                properties:
                  status:
                    type: string
                    enum:
                      - inactive
              examples:
                done:
                  value:
                    status: inactive
        '404':
          description: Agent not found or not active
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /agents/{id}:reactivate:
    post:
      operationId: reactivateAgent
      tags:
        - Agents
      summary: Set agent status back to active
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          description: Agent reactivated
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                type: object
                required:
                  - status
                properties:
                  status:
                    type: string
                    enum:
                      - active
              examples:
                done:
                  value:
                    status: active
        '404':
          description: Agent not found or not inactive
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /agents/{id}/events:
    get:
      operationId: listAgentEvents
      tags:
        - Agents
      summary: List recent delivered events for an agent
      description: |
        Returns the latest N delivered (status=acknowledged) events for the
        specified agent. Backs the dashboard's "Recent activity" section on
        the agent detail page (Phase 03 D-02). Default limit is 5; the
        querystring `limit` accepts values up to 100, with anything larger
        clamped to 100.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          example: ag_01H7abc12345
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 5
          example: 5
      responses:
        '200':
          description: Recent events (may be empty)
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentEventListResponse'
              examples:
                with_events:
                  value:
                    events:
                      - event_id: evt_01H7abc12345
                        event_type: deploy.succeeded
                        delivered_at: '2026-06-01T15:30:00Z'
                      - event_id: evt_01H7abc12346
                        event_type: alert.fired
                        delivered_at: '2026-06-01T15:25:00Z'
                empty:
                  value:
                    events: []
        '404':
          description: Agent not found in the requesting tenant
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                not_found:
                  value:
                    error:
                      code: agent_not_found
                      message: agent not found
                      request_id: req_01H7abc12345
  /agents/{id}/test-event:
    post:
      operationId: postAgentTestEvent
      tags:
        - Agents
      summary: Send a synthetic dashboard test event to an agent
      description: |
        Manufactures a synthetic CloudEvent server-side
        (`type=test.dashboard`, `source=dashboard.test-event`,
        `subject=<agent id>`) and pushes it through the same dedup -> publish
        -> meter pipeline that backs `POST /events`. Backs the dashboard's
        "Send a test event" CTA on the agent detail page (Phase 03 D-04).
        Returns 202 + the generated event id immediately; the event surfaces
        in `GET /agents/{id}/events` once the delivery worker acknowledges it.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          example: ag_01H7abc12345
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '202':
          description: Test event accepted for asynchronous delivery
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                type: object
                required:
                  - event_id
                properties:
                  event_id:
                    type: string
                    description: Server-generated CloudEvent id (`evt_dash_` prefix).
              examples:
                accepted:
                  value:
                    event_id: evt_dash_AbCdEfGhIjKlMnOpQrStUvWx
        '404':
          description: Agent not found in the requesting tenant
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                not_found:
                  value:
                    error:
                      code: agent_not_found
                      message: agent not found
                      request_id: req_01H7abc12345
  /agents/{id}/keys:
    get:
      operationId: listAgentKeys
      tags:
        - Agent Keys
      summary: List API keys for an agent (no plaintext, no hash)
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Keys (without plaintext or hash)
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentKeyListResponse'
              examples:
                with_keys:
                  value:
                    keys:
                      - id: key_01H7abc12345
                        prefix: ak_live_AbCdEfGh
                        scopes:
                          - send
                          - receive
                        last_used_at: '2026-05-28T14:55:00Z'
                        created_at: '2026-05-01T10:00:00Z'
    post:
      operationId: createAgentKey
      tags:
        - Agent Keys
      summary: Mint a new API key (plaintext returned ONCE)
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateAgentKeyRequest'
            examples:
              standard_scopes:
                value:
                  scopes:
                    - send
                    - receive
                    - memory:read
                    - memory:write
                  expires_at: '2027-05-28T00:00:00Z'
      responses:
        '201':
          description: >
            Key created. The `key` field is the plaintext API key — store it
            securely.

            This is the ONLY time the plaintext is returned.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreatedAgentKey'
              examples:
                created:
                  value:
                    id: key_01H7abc12345
                    prefix: ak_live_AbCdEfGh
                    key: ak_live_AbCdEfGhIjKlMnOpQrStUvWxYz0123456
                    scopes:
                      - send
                      - receive
                      - memory:read
                      - memory:write
                    expires_at: '2027-05-28T00:00:00Z'
                    created_at: '2026-05-28T15:00:00Z'
        '400':
          description: Invalid scopes
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                invalid_scope:
                  value:
                    error:
                      code: invalid_scopes
                      message: 'invalid scope: bogus'
                      request_id: req_01H7abc12345
        '409':
          description: Maximum 5 active keys per agent
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                max_keys:
                  value:
                    error:
                      code: max_keys_reached
                      message: maximum of 5 active keys per agent
                      request_id: req_01H7abc12345
  /agents/{id}/keys/{key_id}:
    delete:
      operationId: revokeAgentKey
      tags:
        - Agent Keys
      summary: Revoke an agent's API key
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
        - name: key_id
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          description: Key revoked
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
        '404':
          description: Key not found or already revoked
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                not_found:
                  value:
                    error:
                      code: key_not_found
                      message: key not found or already revoked
                      request_id: req_01H7abc12345
  /agents/{id}/interests:
    get:
      operationId: listInterests
      tags:
        - Interests
      summary: List interest declarations for an agent
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Interests (no pagination)
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InterestListResponse'
              examples:
                with_items:
                  value:
                    interests:
                      - id: int_01H7abc12345
                        tenant_id: tn_01H7abc12345
                        agent_id: ag_01H7abc12345
                        event_type: com.example.order.*
                        filter_expression: amount_cents > 10000
                        priority: 10
                        enrichment_depth: light
                        active: true
                        created_at: '2026-05-01T10:00:00Z'
                        updated_at: '2026-05-01T10:00:00Z'
        '403':
          description: Caller is an agent attempting to manage another agent's interests
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                cross_agent:
                  value:
                    error:
                      code: forbidden
                      message: agents can only manage their own interests
                      request_id: req_01H7abc12345
    post:
      operationId: createInterest
      tags:
        - Interests
      summary: Create an interest declaration for an agent
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateInterestRequest'
            examples:
              standard:
                value:
                  event_type: com.example.order.*
                  filter_expression: amount_cents > 10000
                  priority: 10
                  enrichment_depth: light
      responses:
        '201':
          description: Interest created
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Interest'
              examples:
                created:
                  value:
                    id: int_01H7abc12345
                    tenant_id: tn_01H7abc12345
                    agent_id: ag_01H7abc12345
                    event_type: com.example.order.*
                    filter_expression: amount_cents > 10000
                    priority: 10
                    enrichment_depth: light
                    active: true
                    created_at: '2026-05-28T15:00:00Z'
                    updated_at: '2026-05-28T15:00:00Z'
        '400':
          description: Invalid glob pattern or enrichment_depth
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                bad_pattern:
                  value:
                    error:
                      code: invalid_pattern
                      message: glob pattern not allowed
                      request_id: req_01H7abc12345
        '409':
          description: >-
            An interest with the same event_type already exists for this agent
            (Phase 03 D-14)
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                duplicate:
                  value:
                    error:
                      code: interest_already_exists
                      message: an interest with this event_type already exists
                      request_id: req_01H7abc12345
  /agents/{id}/interests/{interest_id}:
    delete:
      operationId: deleteInterest
      tags:
        - Interests
      summary: Delete an interest declaration
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
        - name: interest_id
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          description: Interest deleted
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
        '404':
          description: Interest not found or already inactive
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /channels:
    get:
      operationId: listChannels
      tags:
        - Channels
      summary: List channels in the caller's tenant
      parameters:
        - name: offset
          in: query
          schema:
            type: integer
            minimum: 0
            default: 0
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
      responses:
        '200':
          description: Channels page
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChannelListResponse'
              examples:
                with_items:
                  value:
                    channels:
                      - id: ch_01H7abc12345
                        tenant_id: tn_01H7abc12345
                        name: ops-alerts
                        description: Operational alerts for production
                        type: open
                        created_by_type: agent
                        created_by_id: ag_01H7abc12345
                        created_at: '2026-05-01T10:00:00Z'
                        updated_at: '2026-05-01T10:00:00Z'
                    total: 1
    post:
      operationId: createChannel
      tags:
        - Channels
      summary: Create a channel
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateChannelRequest'
            examples:
              open_channel:
                value:
                  name: ops-alerts
                  description: Operational alerts for production
                  type: open
      responses:
        '201':
          description: Channel created
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Channel'
              examples:
                created:
                  value:
                    id: ch_01H7abc12345
                    tenant_id: tn_01H7abc12345
                    name: ops-alerts
                    description: Operational alerts for production
                    type: open
                    created_by_type: agent
                    created_by_id: ag_01H7abc12345
                    created_at: '2026-05-28T15:00:00Z'
                    updated_at: '2026-05-28T15:00:00Z'
        '409':
          description: Channel name already in use
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                taken:
                  value:
                    error:
                      code: channel_name_taken
                      message: channel name already exists in this tenant
                      request_id: req_01H7abc12345
  /channels/{id}:
    get:
      operationId: getChannel
      tags:
        - Channels
      summary: Get a channel by id
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Channel detail
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Channel'
              examples:
                detail:
                  value:
                    id: ch_01H7abc12345
                    tenant_id: tn_01H7abc12345
                    name: ops-alerts
                    description: Operational alerts for production
                    type: open
                    created_by_type: agent
                    created_by_id: ag_01H7abc12345
                    created_at: '2026-05-01T10:00:00Z'
                    updated_at: '2026-05-01T10:00:00Z'
        '404':
          description: Channel not found
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /channels/{id}/members:
    get:
      operationId: listChannelMembers
      tags:
        - Channels
      summary: List members of a channel
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Members (no pagination)
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChannelMemberListResponse'
              examples:
                with_members:
                  value:
                    members:
                      - id: mbr_01H7abc12345
                        channel_id: ch_01H7abc12345
                        participant_type: agent
                        participant_id: ag_01H7abc12345
                        role: owner
                        joined_at: '2026-05-01T10:00:00Z'
    post:
      operationId: addChannelMember
      tags:
        - Channels
      summary: Add a member to a channel
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AddMemberRequest'
            examples:
              add_agent:
                value:
                  participant_type: agent
                  participant_id: ag_01H7def67890
                  role: member
      responses:
        '201':
          description: Member added
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChannelMember'
              examples:
                added:
                  value:
                    id: mbr_01H7def67890
                    channel_id: ch_01H7abc12345
                    participant_type: agent
                    participant_id: ag_01H7def67890
                    role: member
                    joined_at: '2026-05-28T15:00:00Z'
        '409':
          description: Already a member
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                already:
                  value:
                    error:
                      code: already_member
                      message: participant is already a member of this channel
                      request_id: req_01H7abc12345
  /channels/{id}/members/{participant_type}/{participant_id}:
    delete:
      operationId: removeChannelMember
      tags:
        - Channels
      summary: Remove a member from a channel
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
        - name: participant_type
          in: path
          required: true
          schema:
            type: string
            enum:
              - agent
              - org_admin
        - name: participant_id
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          description: Member removed
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
        '404':
          description: Member not found
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /channels/{id}/feeds:
    get:
      operationId: listChannelFeeds
      tags:
        - Channels
      summary: List feeds (event subscriptions) for a channel
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Feeds (no pagination)
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChannelFeedListResponse'
              examples:
                with_feeds:
                  value:
                    feeds:
                      - id: feed_01H7abc12345
                        channel_id: ch_01H7abc12345
                        event_type_glob: com.example.order.*
                        filter_expression: amount_cents > 10000
                        active: true
                        created_at: '2026-05-01T10:00:00Z'
    post:
      operationId: createChannelFeed
      tags:
        - Channels
      summary: Subscribe a channel to an event glob (create feed)
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateFeedRequest'
            examples:
              orders_only:
                value:
                  event_type_glob: com.example.order.*
                  filter_expression: amount_cents > 10000
      responses:
        '201':
          description: Feed created
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChannelFeed'
              examples:
                created:
                  value:
                    id: feed_01H7abc12345
                    channel_id: ch_01H7abc12345
                    event_type_glob: com.example.order.*
                    filter_expression: amount_cents > 10000
                    active: true
                    created_at: '2026-05-28T15:00:00Z'
        '400':
          description: Invalid glob pattern
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /channels/{id}/feeds/{feed_id}:
    delete:
      operationId: deleteChannelFeed
      tags:
        - Channels
      summary: Delete a channel feed
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
        - name: feed_id
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          description: Feed deleted
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
        '404':
          description: Feed not found or already inactive
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /memories:
    get:
      operationId: listMemories
      tags:
        - Memories
      summary: List memories accessible to the caller
      parameters:
        - name: offset
          in: query
          schema:
            type: integer
            minimum: 0
            default: 0
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
        - name: type
          in: query
          schema:
            type: string
            enum:
              - episodic
              - semantic
              - procedural
              - entity
              - reflection
              - reasoning
        - name: scope
          in: query
          schema:
            type: string
            enum:
              - agent
              - team
              - institutional
        - name: search
          in: query
          schema:
            type: string
          description: Substring match against title and content (handler-side LIKE).
      responses:
        '200':
          description: Memories page
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MemoryListResponse'
              examples:
                with_items:
                  value:
                    memories:
                      - id: mem_01H7abc12345
                        tenant_id: tn_01H7abc12345
                        agent_id: ag_01H7abc12345
                        scope: agent
                        type: semantic
                        status: active
                        title: Customer prefers async support
                        content: >-
                          Customer cs_42 historically responds to email within
                          6h...
                        confidence: 0.85
                        importance: 0.6
                        metadata: {}
                        created_at: '2026-05-01T10:00:00Z'
                        updated_at: '2026-05-01T10:00:00Z'
                        accessed_at: '2026-05-28T14:00:00Z'
                    total: 1
    post:
      operationId: createMemory
      tags:
        - Memories
      summary: Create a memory
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateMemoryRequest'
            examples:
              semantic:
                value:
                  title: Customer prefers async support
                  content: Customer cs_42 historically responds to email within 6h...
                  type: semantic
                  scope: agent
                  confidence: 0.85
                  importance: 0.6
                  source_event_id: evt_01H7abc12345
      responses:
        '201':
          description: Memory created
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Memory'
              examples:
                created:
                  value:
                    id: mem_01H7abc12345
                    tenant_id: tn_01H7abc12345
                    agent_id: ag_01H7abc12345
                    scope: agent
                    type: semantic
                    status: active
                    title: Customer prefers async support
                    content: Customer cs_42 historically responds to email within 6h...
                    confidence: 0.85
                    importance: 0.6
                    metadata: {}
                    source_event_id: evt_01H7abc12345
                    created_at: '2026-05-28T15:00:00Z'
                    updated_at: '2026-05-28T15:00:00Z'
                    accessed_at: '2026-05-28T15:00:00Z'
        '400':
          description: Validation error (e.g., invalid type, missing title, content >32KB)
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                bad_type:
                  value:
                    error:
                      code: validation_error
                      message: >-
                        type must be one of: episodic, semantic, procedural,
                        entity, reflection, reasoning
                      request_id: req_01H7abc12345
  /memories/{id}:
    get:
      operationId: getMemory
      tags:
        - Memories
      summary: Get a memory by id
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          example: mem_01H7abc12345
      responses:
        '200':
          description: Memory detail
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Memory'
              examples:
                detail:
                  value:
                    id: mem_01H7abc12345
                    tenant_id: tn_01H7abc12345
                    agent_id: ag_01H7abc12345
                    scope: agent
                    type: semantic
                    status: active
                    title: Customer prefers async support
                    content: Customer cs_42 historically responds to email within 6h...
                    confidence: 0.85
                    importance: 0.6
                    metadata: {}
                    created_at: '2026-05-01T10:00:00Z'
                    updated_at: '2026-05-01T10:00:00Z'
                    accessed_at: '2026-05-28T14:00:00Z'
        '400':
          description: Invalid memory ID format
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Memory not found
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                not_found:
                  value:
                    error:
                      code: memory_not_found
                      message: memory not found
                      request_id: req_01H7abc12345
    patch:
      operationId: updateMemory
      tags:
        - Memories
      summary: Update mutable fields of a memory
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateMemoryRequest'
            examples:
              update_confidence:
                value:
                  confidence: 0.9
                  importance: 0.7
      responses:
        '200':
          description: Memory updated
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Memory'
              examples:
                updated:
                  value:
                    id: mem_01H7abc12345
                    tenant_id: tn_01H7abc12345
                    agent_id: ag_01H7abc12345
                    scope: agent
                    type: semantic
                    status: active
                    title: Customer prefers async support
                    content: Customer cs_42 historically responds to email within 6h...
                    confidence: 0.9
                    importance: 0.7
                    metadata: {}
                    created_at: '2026-05-01T10:00:00Z'
                    updated_at: '2026-05-28T15:05:00Z'
                    accessed_at: '2026-05-28T15:05:00Z'
        '400':
          description: Validation error (no fields supplied, content >32KB, etc.)
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Memory not found
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    delete:
      operationId: deleteMemory
      tags:
        - Memories
      summary: Soft-delete a memory
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '204':
          description: Memory soft-deleted
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
        '404':
          description: Memory not found
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /memories/search:
    post:
      operationId: searchMemories
      tags:
        - Memories
      summary: Semantic search across memories
      description: >
        Per PRD-02. Performs scope-aware semantic search across the caller's

        accessible memories (agent → team → institutional ascending). Results
        are

        ranked by composite score combining semantic similarity, confidence, and

        recency. The exact ranking signals may evolve; the request/response keys

        documented here are stable.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MemorySearchRequest'
            examples:
              standard:
                value:
                  query: customer support preferences
                  scope: agent
                  type: semantic
                  limit: 10
                  min_confidence: 0.5
      responses:
        '200':
          description: Ranked search results
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MemorySearchResponse'
              examples:
                with_results:
                  value:
                    results:
                      - memory:
                          id: mem_01H7abc12345
                          tenant_id: tn_01H7abc12345
                          agent_id: ag_01H7abc12345
                          scope: agent
                          type: semantic
                          status: active
                          title: Customer prefers async support
                          content: >-
                            Customer cs_42 historically responds to email within
                            6h...
                          confidence: 0.85
                          importance: 0.6
                          metadata: {}
                          created_at: '2026-05-01T10:00:00Z'
                          updated_at: '2026-05-01T10:00:00Z'
                          accessed_at: '2026-05-28T14:00:00Z'
                        score: 0.92
                    total: 1
        '400':
          description: Invalid search request (e.g., empty query)
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /memories/{id}/relationships:
    post:
      operationId: createMemoryRelationship
      tags:
        - Memories
      summary: Create a directed relationship between two memories
      description: >
        Creates a directed relationship from the memory in the path (`id`) to

        another memory (`target_memory_id`). The server enforces a closed enum

        for `relationship_type` — values outside the enum are rejected with

        HTTP 400 `validation_error`. Backed by
        `MemoryHandler.CreateRelationship`

        in `api/internal/handlers/memory.go`.


        Valid `relationship_type` values:
          - caused_by
          - related_to
          - contradicts
          - supports
          - derived_from
          - part_of

        `strength` and `metadata` are optional. If `strength` is omitted the

        server applies a default; if `metadata` is omitted it is stored as null.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Source memory id.
          example: mem_01H7abc12345
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateMemoryRelationshipRequest'
            examples:
              derived_from_with_strength:
                summary: Mark target as the source this memory was derived from
                value:
                  target_memory_id: mem_neighbor1234
                  relationship_type: derived_from
                  strength: 0.85
                  metadata:
                    confidence: 0.9
                    source: manual
              minimal_caused_by:
                summary: Minimal payload (only required fields)
                value:
                  target_memory_id: mem_neighbor1234
                  relationship_type: caused_by
      responses:
        '201':
          description: Relationship created
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MemoryRelationship'
              examples:
                created:
                  summary: Newly created relationship
                  value:
                    id: rel_01H7abc12345
                    tenant_id: ten_01H7abc12345
                    source_memory_id: mem_01H7abc12345
                    target_memory_id: mem_neighbor1234
                    relationship_type: derived_from
                    strength: 0.85
                    metadata:
                      confidence: 0.9
                      source: manual
                    created_at: '2026-06-01T12:34:56Z'
        '400':
          description: >-
            Validation error (e.g., missing target_memory_id, invalid
            relationship_type)
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Forbidden
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Source or target memory not found
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: Relationship already exists
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Semantic constraint violated (e.g., self-referential relationship)
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    get:
      operationId: listMemoryRelationships
      tags:
        - Memories
      summary: List relationships for a memory (graph traversal)
      description: >
        Performs a breadth-first traversal of the memory graph starting at `id`

        and returns each reached memory along with its hop depth and the

        cumulative product of edge strengths along the traversal path.


        Backed by `MemoryHandler.ListRelationships` in

        `api/internal/handlers/memory.go`. Returns the items the caller's tenant

        is permitted to see; tenant scoping is enforced server-side.


        The `count` field is the number of items returned, not a paginated
        total.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Source memory id (UUID).
          example: mem_01H7abc12345
        - name: depth
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 3
            default: 1
          description: |
            Number of relationship hops to traverse. Server clamps to [1, 3];
            values <= 0 default to 1, values > 3 are capped at 3.
          example: 1
      responses:
        '200':
          description: Traversal result (zero or more reachable memories)
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MemoryRelationshipListResponse'
              examples:
                with_items:
                  value:
                    relationships:
                      - memory_id: mem_neighbor1234
                        depth: 1
                        strength_product: 0.85
                      - memory_id: mem_far_neighb56
                        depth: 2
                        strength_product: 0.42
                    count: 2
                empty:
                  value:
                    relationships: []
                    count: 0
        '400':
          description: Validation error (e.g., id is not a UUID)
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                invalid_id:
                  value:
                    error:
                      code: validation_error
                      message: invalid memory ID format
                      request_id: req_01H7abc12345
        '401':
          description: Unauthorized
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                unauthorized:
                  value:
                    error:
                      code: authentication_failed
                      message: missing or invalid bearer token
                      request_id: req_01H7abc12345
        '501':
          description: Relationship API not available on this deployment
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                disabled:
                  value:
                    error:
                      code: service_unavailable
                      message: relationship API not available
                      request_id: req_01H7abc12345
  /memories/{id}/relationships/{rel_id}:
    delete:
      operationId: deleteMemoryRelationship
      tags:
        - Memories
      summary: Delete a memory relationship
      description: |
        Removes a single directed relationship by its server-assigned id.
        Backed by `MemoryHandler.DeleteRelationship` in
        `api/internal/handlers/memory.go`. Tenant scoping is enforced
        server-side; the caller must be authenticated as an agent in the
        owning tenant.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Source memory id (UUID).
          example: mem_01H7abc12345
        - name: rel_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Relationship id returned by createMemoryRelationship.
          example: rel_01H7abc12345
      responses:
        '204':
          description: Relationship deleted
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
        '400':
          description: Validation error (id or rel_id is not a UUID)
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                invalid_rel_id:
                  value:
                    error:
                      code: validation_error
                      message: invalid relationship ID format
                      request_id: req_01H7abc12345
        '401':
          description: Unauthorized
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                unauthorized:
                  value:
                    error:
                      code: authentication_failed
                      message: missing or invalid bearer token
                      request_id: req_01H7abc12345
        '404':
          description: Relationship not found (or already deleted)
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                not_found:
                  value:
                    error:
                      code: not_found
                      message: relationship not found
                      request_id: req_01H7abc12345
        '501':
          description: Relationship API not available on this deployment
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                disabled:
                  value:
                    error:
                      code: service_unavailable
                      message: relationship API not available
                      request_id: req_01H7abc12345
  /memories/{id}/promote:
    post:
      operationId: promoteMemory
      tags:
        - Memories
      summary: Promote a memory's scope (agent → team or institutional)
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PromoteMemoryRequest'
            examples:
              to_team:
                value:
                  scope: team
                  team_id: team_01H7abc12345
              to_institutional:
                value:
                  scope: institutional
      responses:
        '200':
          description: Memory promoted (returns the updated memory)
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Memory'
              examples:
                promoted_to_team:
                  value:
                    id: mem_01H7abc12345
                    tenant_id: tn_01H7abc12345
                    agent_id: ag_01H7abc12345
                    scope: team
                    team_id: team_01H7abc12345
                    type: semantic
                    status: active
                    title: Customer prefers async support
                    content: Customer cs_42 historically responds to email within 6h...
                    confidence: 0.85
                    importance: 0.6
                    metadata: {}
                    created_at: '2026-05-01T10:00:00Z'
                    updated_at: '2026-05-28T15:10:00Z'
                    accessed_at: '2026-05-28T15:10:00Z'
        '400':
          description: Validation error (e.g., team_id missing for team scope)
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                missing_team_id:
                  value:
                    error:
                      code: validation_error
                      message: team_id required for team scope promotion
                      request_id: req_01H7abc12345
        '403':
          description: Promotion not allowed (e.g., agent does not own the memory)
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                forbidden:
                  value:
                    error:
                      code: forbidden
                      message: promotion not allowed
                      request_id: req_01H7abc12345
        '404':
          description: Memory not found
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
  schemas:
    TokenExchangeRequest:
      type: object
      required:
        - api_key
      properties:
        api_key:
          type: string
          minLength: 40
          maxLength: 40
          pattern: ^ak_live_[A-Za-z0-9]{32}$
          description: >-
            API key starting with `ak_live_` followed by 32 base62 chars (40
            total).
          example: ak_live_AbCdEfGhIjKlMnOpQrStUvWxYz0123456
    TokenExchangeResponse:
      type: object
      required:
        - access_token
        - token_type
        - expires_at
        - agent
      properties:
        access_token:
          type: string
          description: Signed JWT (HS256). Lifetime 1 hour.
        token_type:
          type: string
          enum:
            - Bearer
        expires_at:
          type: string
          format: date-time
        agent:
          type: object
          required:
            - id
            - name
            - tenant_id
          properties:
            id:
              type: string
              example: ag_01H7abc12345
            name:
              type: string
            team:
              type: string
            tenant_id:
              type: string
              example: tn_01H7abc12345
    Event:
      type: object
      description: >
        CloudEvents 1.0 envelope. The fields below are the spec required+core
        set.

        `data` is provider-defined.
      required:
        - specversion
        - type
        - source
        - id
      properties:
        specversion:
          type: string
          enum:
            - '1.0'
        type:
          type: string
          description: Reverse-DNS event type (e.g., `com.example.order.created`).
          example: com.example.order.created
        source:
          type: string
          description: URI identifying the source system.
          example: //orders.example.com
        id:
          type: string
          description: Unique event ID (used for dedup).
          example: evt_01H7abc12345
        time:
          type: string
          format: date-time
        datacontenttype:
          type: string
          example: application/json
        subject:
          type: string
        data:
          description: Provider-defined event payload.
    EventAcceptedResponse:
      type: object
      required:
        - event_id
        - status
      properties:
        event_id:
          type: string
          example: evt_01H7abc12345
        status:
          type: string
          enum:
            - accepted
            - duplicate
    Delivery:
      type: object
      required:
        - id
        - event_id
        - payload
        - attempt_count
        - created_at
      properties:
        id:
          type: string
          example: dlv_01H7abc12345
        event_id:
          type: string
          example: evt_01H7abc12345
        payload:
          description: The full enriched event payload delivered to the agent.
        attempt_count:
          type: integer
          minimum: 1
        created_at:
          type: string
          format: date-time
    DeliveryListResponse:
      type: object
      required:
        - deliveries
        - count
      properties:
        deliveries:
          type: array
          items:
            $ref: '#/components/schemas/Delivery'
        count:
          type: integer
          minimum: 0
    AckRequest:
      type: object
      required:
        - delivery_ids
      properties:
        delivery_ids:
          type: array
          minItems: 1
          maxItems: 100
          items:
            type: string
            example: dlv_01H7abc12345
    AckResponse:
      type: object
      required:
        - acknowledged
      properties:
        acknowledged:
          type: integer
          minimum: 0
    DLQItem:
      type: object
      required:
        - id
        - event_id
        - agent_id
        - attempt_count
        - created_at
      properties:
        id:
          type: string
          example: dlv_01H7abc12345
        event_id:
          type: string
          example: evt_01H7abc12345
        agent_id:
          type: string
          example: ag_01H7abc12345
        attempt_count:
          type: integer
        dlq_reason:
          type: string
        dlq_at:
          type: string
          format: date-time
        created_at:
          type: string
          format: date-time
    DLQListResponse:
      type: object
      required:
        - dlq_items
        - total
      properties:
        dlq_items:
          type: array
          items:
            $ref: '#/components/schemas/DLQItem'
        total:
          type: integer
          minimum: 0
    ReplayResponse:
      type: object
      required:
        - status
        - delivery_id
      properties:
        status:
          type: string
          enum:
            - replayed
        delivery_id:
          type: string
    Agent:
      type: object
      required:
        - id
        - tenant_id
        - name
        - status
        - delivery_mode
        - created_at
        - updated_at
      properties:
        id:
          type: string
          example: ag_01H7abc12345
        tenant_id:
          type: string
          example: tn_01H7abc12345
        name:
          type: string
        description:
          type:
            - string
            - 'null'
        status:
          type: string
          enum:
            - active
            - inactive
        delivery_mode:
          type: string
          enum:
            - poll
            - push
            - sse
        client_type:
          type: string
          enum:
            - claude-desktop
            - claude-code
            - chatgpt
            - gemini
            - cursor
            - other
          description: >-
            Which chat client this agent represents. Optional in response for
            SDK back-compat; backend always returns it.
        delivery_endpoint:
          type:
            - string
            - 'null'
          format: uri
        team_id:
          type:
            - string
            - 'null'
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    AgentListResponse:
      type: object
      required:
        - agents
        - total
      properties:
        agents:
          type: array
          items:
            $ref: '#/components/schemas/Agent'
        total:
          type: integer
          minimum: 0
    CreateAgentRequest:
      type: object
      required:
        - name
        - delivery_mode
        - client_type
      properties:
        name:
          type: string
          minLength: 1
        description:
          type:
            - string
            - 'null'
        delivery_mode:
          type: string
          enum:
            - poll
            - push
            - sse
        client_type:
          type: string
          enum:
            - claude-desktop
            - claude-code
            - chatgpt
            - gemini
            - cursor
            - other
          description: Which chat client this agent represents. Closed 6-value enum.
        team_id:
          type:
            - string
            - 'null'
    UpdateAgentRequest:
      type: object
      description: All fields optional; at least one must be provided.
      properties:
        name:
          type: string
        description:
          type:
            - string
            - 'null'
        delivery_mode:
          type: string
          enum:
            - poll
            - push
            - sse
        delivery_endpoint:
          type:
            - string
            - 'null'
          format: uri
    AgentKey:
      type: object
      description: API key metadata (no plaintext, no hash).
      required:
        - id
        - prefix
        - scopes
        - created_at
      properties:
        id:
          type: string
          example: key_01H7abc12345
        prefix:
          type: string
          description: First 16 chars (`ak_live_` + 8 random) for identification.
          example: ak_live_AbCdEfGh
        scopes:
          type: array
          items:
            type: string
        last_used_at:
          type:
            - string
            - 'null'
          format: date-time
        expires_at:
          type:
            - string
            - 'null'
          format: date-time
        revoked_at:
          type:
            - string
            - 'null'
          format: date-time
        created_at:
          type: string
          format: date-time
    CreatedAgentKey:
      type: object
      description: Returned ONLY at creation time. Includes the plaintext `key`.
      required:
        - id
        - prefix
        - key
        - scopes
        - created_at
      properties:
        id:
          type: string
        prefix:
          type: string
        key:
          type: string
          description: >
            Plaintext API key (`ak_live_` + 32 base62 chars). Stored hashed
            server-side.

            This is the ONLY response that includes the plaintext.
        scopes:
          type: array
          items:
            type: string
        expires_at:
          type:
            - string
            - 'null'
          format: date-time
        created_at:
          type: string
          format: date-time
    AgentKeyListResponse:
      type: object
      required:
        - keys
      properties:
        keys:
          type: array
          items:
            $ref: '#/components/schemas/AgentKey'
    CreateAgentKeyRequest:
      type: object
      required:
        - scopes
      properties:
        scopes:
          type: array
          minItems: 1
          items:
            type: string
            enum:
              - send
              - receive
              - memory:read
              - memory:write
              - admin
              - channel:read
              - channel:write
              - interests:read
              - interests:write
        expires_at:
          type:
            - string
            - 'null'
          format: date-time
    AgentEvent:
      type: object
      required:
        - event_id
        - event_type
        - delivered_at
      description: |
        Minimal recent-activity row returned by `GET /agents/{id}/events`.
        Phase 03 D-02 / D-04 fields only — full event payloads are out of
        scope for the dashboard's first-impression view.
      properties:
        event_id:
          type: string
          description: CloudEvents id of the delivered event.
          example: evt_01H7abc12345
        event_type:
          type: string
          description: CloudEvents type field, copied from the original event payload.
          example: deploy.succeeded
        delivered_at:
          type: string
          format: date-time
          description: RFC3339 timestamp when the delivery row was created.
          example: '2026-06-01T15:30:00Z'
    AgentEventListResponse:
      type: object
      required:
        - events
      properties:
        events:
          type: array
          items:
            $ref: '#/components/schemas/AgentEvent'
    Interest:
      type: object
      required:
        - id
        - tenant_id
        - agent_id
        - event_type
        - priority
        - enrichment_depth
        - active
        - created_at
        - updated_at
      properties:
        id:
          type: string
          example: int_01H7abc12345
        tenant_id:
          type: string
        agent_id:
          type: string
        event_type:
          type: string
          description: Glob pattern (e.g., `com.example.order.*`).
        filter_expression:
          type:
            - string
            - 'null'
          description: Optional `key op value` filter (e.g., `amount_cents > 10000`).
        priority:
          type: integer
        enrichment_depth:
          type: string
          enum:
            - none
            - light
            - full
        active:
          type: boolean
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    InterestListResponse:
      type: object
      required:
        - interests
      properties:
        interests:
          type: array
          items:
            $ref: '#/components/schemas/Interest'
    CreateInterestRequest:
      type: object
      required:
        - event_type
        - priority
        - enrichment_depth
      properties:
        event_type:
          type: string
          minLength: 1
        filter_expression:
          type:
            - string
            - 'null'
        priority:
          type: integer
          minimum: 0
        enrichment_depth:
          type: string
          enum:
            - none
            - light
            - full
    Channel:
      type: object
      required:
        - id
        - tenant_id
        - name
        - type
        - created_by_type
        - created_by_id
        - created_at
        - updated_at
      properties:
        id:
          type: string
          example: ch_01H7abc12345
        tenant_id:
          type: string
        name:
          type: string
        description:
          type:
            - string
            - 'null'
        type:
          type: string
          enum:
            - open
            - private
            - direct
        created_by_type:
          type: string
          enum:
            - agent
            - org_admin
        created_by_id:
          type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    ChannelListResponse:
      type: object
      required:
        - channels
        - total
      properties:
        channels:
          type: array
          items:
            $ref: '#/components/schemas/Channel'
        total:
          type: integer
          minimum: 0
    CreateChannelRequest:
      type: object
      required:
        - name
        - type
      properties:
        name:
          type: string
          minLength: 1
        description:
          type:
            - string
            - 'null'
        type:
          type: string
          enum:
            - open
            - private
            - direct
    ChannelMember:
      type: object
      required:
        - id
        - channel_id
        - participant_type
        - participant_id
        - role
        - joined_at
      properties:
        id:
          type: string
          example: mbr_01H7abc12345
        channel_id:
          type: string
        participant_type:
          type: string
          enum:
            - agent
            - org_admin
        participant_id:
          type: string
        role:
          type: string
          enum:
            - owner
            - admin
            - member
        joined_at:
          type: string
          format: date-time
    ChannelMemberListResponse:
      type: object
      required:
        - members
      properties:
        members:
          type: array
          items:
            $ref: '#/components/schemas/ChannelMember'
    AddMemberRequest:
      type: object
      required:
        - participant_type
        - participant_id
      properties:
        participant_type:
          type: string
          enum:
            - agent
            - org_admin
        participant_id:
          type: string
        role:
          type: string
          enum:
            - owner
            - admin
            - member
          default: member
    ChannelFeed:
      type: object
      required:
        - id
        - channel_id
        - event_type_glob
        - active
        - created_at
      properties:
        id:
          type: string
          example: feed_01H7abc12345
        channel_id:
          type: string
        event_type_glob:
          type: string
        filter_expression:
          type:
            - string
            - 'null'
        active:
          type: boolean
        created_at:
          type: string
          format: date-time
    ChannelFeedListResponse:
      type: object
      required:
        - feeds
      properties:
        feeds:
          type: array
          items:
            $ref: '#/components/schemas/ChannelFeed'
    CreateFeedRequest:
      type: object
      required:
        - event_type_glob
      properties:
        event_type_glob:
          type: string
          minLength: 1
        filter_expression:
          type:
            - string
            - 'null'
    Memory:
      type: object
      required:
        - id
        - tenant_id
        - agent_id
        - scope
        - type
        - status
        - title
        - content
        - confidence
        - importance
        - metadata
        - created_at
        - updated_at
        - accessed_at
      properties:
        id:
          type: string
          format: uuid
          example: mem_01H7abc12345
        tenant_id:
          type: string
        agent_id:
          type: string
        project_id:
          type:
            - string
            - 'null'
        scope:
          type: string
          enum:
            - agent
            - team
            - institutional
        team_id:
          type:
            - string
            - 'null'
        type:
          type: string
          enum:
            - episodic
            - semantic
            - procedural
            - entity
            - reflection
            - reasoning
        status:
          type: string
          enum:
            - active
            - deleted
        title:
          type: string
          minLength: 1
        content:
          type: string
          minLength: 1
          maxLength: 32768
        confidence:
          type: number
          format: float
          minimum: 0
          maximum: 1
        importance:
          type: number
          format: float
          minimum: 0
          maximum: 1
        metadata:
          type: object
          additionalProperties: true
        source_type:
          type:
            - string
            - 'null'
        source_id:
          type:
            - string
            - 'null'
        source_url:
          type:
            - string
            - 'null'
          format: uri
        source_event_id:
          type:
            - string
            - 'null'
        freshness_date:
          type:
            - string
            - 'null'
          format: date-time
        expires_at:
          type:
            - string
            - 'null'
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        accessed_at:
          type: string
          format: date-time
    MemoryListResponse:
      type: object
      required:
        - memories
        - total
      properties:
        memories:
          type: array
          items:
            $ref: '#/components/schemas/Memory'
        total:
          type: integer
          minimum: 0
    CreateMemoryRequest:
      type: object
      required:
        - title
        - content
        - type
        - scope
      properties:
        title:
          type: string
          minLength: 1
        content:
          type: string
          minLength: 1
          maxLength: 32768
        type:
          type: string
          enum:
            - episodic
            - semantic
            - procedural
            - entity
            - reflection
            - reasoning
        scope:
          type: string
          enum:
            - agent
            - team
            - institutional
        team_id:
          type:
            - string
            - 'null'
          description: Required if scope=team and not in JWT context.
        project_id:
          type:
            - string
            - 'null'
        confidence:
          type: number
          minimum: 0
          maximum: 1
        importance:
          type: number
          minimum: 0
          maximum: 1
        metadata:
          type: object
          additionalProperties: true
        source_type:
          type:
            - string
            - 'null'
        source_id:
          type:
            - string
            - 'null'
        source_url:
          type:
            - string
            - 'null'
          format: uri
        source_event_id:
          type:
            - string
            - 'null'
        freshness_date:
          type:
            - string
            - 'null'
          format: date-time
        expires_at:
          type:
            - string
            - 'null'
          format: date-time
    UpdateMemoryRequest:
      type: object
      description: All fields optional; at least one must be provided.
      properties:
        title:
          type: string
        content:
          type: string
          maxLength: 32768
        confidence:
          type: number
          minimum: 0
          maximum: 1
        importance:
          type: number
          minimum: 0
          maximum: 1
        metadata:
          type: object
          additionalProperties: true
        expires_at:
          type:
            - string
            - 'null'
          format: date-time
        freshness_date:
          type:
            - string
            - 'null'
          format: date-time
    MemorySearchRequest:
      type: object
      required:
        - query
      properties:
        query:
          type: string
          minLength: 1
          description: Natural-language search query.
        scope:
          type: string
          enum:
            - agent
            - team
            - institutional
          description: >-
            Limit results to a specific scope. Default searches all accessible
            scopes.
        type:
          type: string
          enum:
            - episodic
            - semantic
            - procedural
            - entity
            - reflection
            - reasoning
        limit:
          type: integer
          minimum: 1
          maximum: 100
          default: 10
        min_confidence:
          type: number
          minimum: 0
          maximum: 1
          description: Minimum confidence score (0..1) for returned memories.
    MemorySearchResult:
      type: object
      required:
        - memory
        - score
      properties:
        memory:
          $ref: '#/components/schemas/Memory'
        score:
          type: number
          format: float
          minimum: 0
          maximum: 1
          description: Composite ranking score (semantic + confidence + recency).
    MemorySearchResponse:
      type: object
      required:
        - results
        - total
      properties:
        results:
          type: array
          items:
            $ref: '#/components/schemas/MemorySearchResult'
        total:
          type: integer
          minimum: 0
    PromoteMemoryRequest:
      type: object
      required:
        - scope
      properties:
        scope:
          type: string
          enum:
            - team
            - institutional
          description: Target scope. Cannot be "agent" (only promotion supported).
        team_id:
          type:
            - string
            - 'null'
          description: Required when scope=team.
    CreateMemoryRelationshipRequest:
      type: object
      required:
        - target_memory_id
        - relationship_type
      additionalProperties: false
      properties:
        target_memory_id:
          type: string
          format: uuid
          description: >-
            Target memory id (UUID). The source memory id is taken from the path
            parameter.
          example: mem_neighbor1234
        relationship_type:
          type: string
          description: |
            Closed enum. The server validates this value against the list below
            and rejects any other value with HTTP 400 validation_error.
          enum:
            - caused_by
            - related_to
            - contradicts
            - supports
            - derived_from
            - part_of
          example: derived_from
        strength:
          type: number
          format: double
          minimum: 0
          maximum: 1
          description: >
            Optional edge weight in [0, 1]. Server applies a default if omitted

            (handler stores `nil` and the persistence layer chooses the
            default).
          example: 0.85
        metadata:
          type:
            - object
            - 'null'
          additionalProperties: true
          description: |
            Optional freeform JSON object attached to the edge. May be omitted
            (sent as null) — the server stores either the object or no metadata.
          example:
            confidence: 0.9
            source: manual
    MemoryRelationship:
      type: object
      required:
        - id
        - tenant_id
        - source_memory_id
        - target_memory_id
        - relationship_type
        - strength
        - metadata
        - created_at
      properties:
        id:
          type: string
          format: uuid
          description: Server-assigned relationship id.
          example: rel_01H7abc12345
        tenant_id:
          type: string
          format: uuid
          description: Tenant that owns this relationship (server-enforced).
          example: ten_01H7abc12345
        source_memory_id:
          type: string
          format: uuid
          description: Source memory id (the memory in the request path on create).
          example: mem_01H7abc12345
        target_memory_id:
          type: string
          format: uuid
          description: Target memory id.
          example: mem_neighbor1234
        relationship_type:
          type: string
          description: |
            Closed enum (server-validated on create). Same value as written by
            the caller — the server does not normalize.
          enum:
            - caused_by
            - related_to
            - contradicts
            - supports
            - derived_from
            - part_of
          example: derived_from
        strength:
          type: number
          format: double
          minimum: 0
          maximum: 1
          description: Edge weight in [0, 1]. Always present in responses.
          example: 0.85
        metadata:
          type:
            - object
            - 'null'
          additionalProperties: true
          description: |
            Freeform JSON attached to the edge. May be `null` when the caller
            omitted it on create (json.RawMessage emits null in that case).
            Required in the response payload (always serialized) but the value
            itself may be null.
          example:
            confidence: 0.9
            source: manual
        created_at:
          type: string
          format: date-time
          description: ISO-8601 timestamp.
          example: '2026-06-01T12:34:56Z'
    MemoryRelationshipTraversalItem:
      type: object
      required:
        - memory_id
        - depth
        - strength_product
      properties:
        memory_id:
          type: string
          description: Id of the memory reached by traversing from the source.
          example: mem_neighbor1234
        depth:
          type: integer
          minimum: 1
          maximum: 3
          description: Number of relationship hops from the source memory.
          example: 1
        strength_product:
          type: number
          format: double
          description: |
            Product of edge strengths along the traversal path (in [0,1]).
            A direct hop has strength_product equal to that edge's strength.
          example: 0.85
    MemoryRelationshipListResponse:
      type: object
      required:
        - relationships
        - count
      properties:
        relationships:
          type: array
          items:
            $ref: '#/components/schemas/MemoryRelationshipTraversalItem'
        count:
          type: integer
          minimum: 0
          description: Number of items in `relationships` (not a paginated total).
          example: 2
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              description: Machine-readable error code from a fixed enum.
              enum:
                - validation_error
                - validation_failed
                - invalid_event
                - invalid_key
                - key_expired
                - agent_inactive
                - authentication_failed
                - unauthorized
                - forbidden
                - insufficient_scope
                - not_found
                - agent_not_found
                - memory_not_found
                - interest_not_found
                - channel_not_found
                - key_not_found
                - dlq_item_not_found
                - feed_not_found
                - member_not_found
                - conflict
                - already_member
                - max_keys_reached
                - channel_name_taken
                - payload_too_large
                - business_rule_violation
                - invalid_pattern
                - invalid_scopes
                - rate_limited
                - internal_error
                - service_unavailable
                - publish_failed
            message:
              type: string
              description: Human-readable explanation. Safe to surface to end users.
            request_id:
              type: string
              description: Correlates with the X-Request-Id response header.
              example: req_01H7abc12345
            details:
              type: object
              additionalProperties: true
              description: Optional structured details (e.g., validation field map).
      example:
        error:
          code: validation_error
          message: name is required
          request_id: req_01H7abc12345
          details:
            field: name
  headers:
    XRequestId:
      description: Per-request correlation ID. Echo this in support tickets.
      schema:
        type: string
        example: req_01H7abc12345
    XRateLimitLimit:
      description: Rate-limit ceiling for this credential (calls per window).
      schema:
        type: integer
        example: 100
    XRateLimitRemaining:
      description: Calls remaining in the current rate-limit window.
      schema:
        type: integer
        example: 87
    XRateLimitReset:
      description: Unix epoch seconds when the rate-limit window resets.
      schema:
        type: integer
        example: 1735689600
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      schema:
        type: string
        maxLength: 255
      description: >
        Client-supplied key making POST/PATCH operations idempotent (per PRD-03
        §4.1).

        Repeated requests with the same key return the original response without

        executing again. Server retains the key for at least 24 hours.
      example: idem_01H7abc12345
