openapi: 3.0.3
info:
  title: SOAT Memories API
  version: 1.0.0
  description: API for managing individual memories within a memory store
  contact:
    name: SOAT Team
    url: https://github.com/ttoss/soat
servers:
  - url: '{baseUrl}'
    description: Base URL of your SOAT deployment
    variables:
      baseUrl:
        description: The base URL of your SOAT deployment
        default: http://localhost:5047
tags:
  - name: Memories
    description: Manage individual memories (the knowledge items stored in a memory store)
security:
  - bearerAuth: []
paths:
  /api/v1/memories:
    get:
      tags:
        - Memories
      summary: List memories
      description: Returns all memories in a memory store
      operationId: listMemories
      x-iam-action: memories:ListMemories
      parameters:
        - name: memory_store_id
          in: query
          required: true
          description: Memory store to list memories from (mstore_...)
          schema:
            type: string
            example: mstore_V1StGXR8Z5jdHi6B
        - $ref: './tags.yaml#/components/parameters/TagsQuery'
        - $ref: './pagination.yaml#/components/parameters/Limit'
        - $ref: './pagination.yaml#/components/parameters/Offset'
        - name: include_invalidated
          in: query
          required: false
          description: Include invalidated memories — superseded or retracted. They are excluded by default; set this to audit what a store once held.
          schema:
            type: boolean
            default: false
      responses:
        '200':
          description: List of memories
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - total
                  - limit
                  - offset
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Memory'
                  total:
                    type: integer
                  limit:
                    type: integer
                  offset:
                    type: integer
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: Memory store not found
        '500':
          description: Internal server error
    post:
      tags:
        - Memories
      summary: Create a memory
      description: >-
        Writes a fact to the specified memory store through the standard write
        algorithm: the content is embedded (or matched to text the store
        already holds), compared against the most similar currently-valid
        memory, and resolved to exactly one of three outcomes — `skipped` at or
        above `duplicate_threshold`, `superseded` at or above
        `supersede_threshold`, `created` below it. `supersedes` overrides that
        comparison entirely, retiring the memory it names. Every call records
        one assertion, whatever the outcome.
      operationId: createMemory
      x-soat-resource:
        kind: memory_store
        from: memory_store_id
      x-iam-action: memories:CreateMemory
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - memory_store_id
                - content
              additionalProperties: false
              properties:
                memory_store_id:
                  x-soat-ref: memory-stores
                  type: string
                  description: Memory store to add the memory to (mstore_...)
                  example: mstore_V1StGXR8Z5jdHi6B
                content:
                  type: string
                  description: The text content of the memory
                  example: The customer prefers email communication over phone calls
                source_type:
                  type: string
                  enum: [manual, conversation]
                  description: >-
                    Whether there is a source to point at. `conversation`
                    requires `source_id`; `manual` rejects it.
                  default: manual
                  example: manual
                source_id:
                  type: string
                  description: >-
                    The conversation this fact was learned in. Required when
                    `source_type` is `conversation`, and rejected otherwise.
                  example: conv_V1StGXR8Z5jdHi6B
                tags:
                  description: >-
                    Per-memory key-value tags, used for memory-granularity
                    filtering by `tags` in search-knowledge.
                  allOf:
                    - $ref: './tags.yaml#/components/schemas/TagBag'
                metadata:
                  description: Arbitrary structured metadata attached to the memory
                  example: { evidence: high, quarter: Q3 }
                  allOf:
                    - $ref: './metadata.yaml#/components/schemas/MetadataBag'
                supersedes:
                  type: string
                  description: >-
                    The memory this write replaces, named outright. The
                    declaration outranks the thresholds in both directions: the
                    target is retired and the write returns `superseded` however
                    similar or distant the two texts are. This is what reaches a
                    contradiction cosine cannot see ("The office is in Lisbon"
                    then "We closed the Lisbon office"). The target must be a
                    still-valid memory in the same memory store, and the caller
                    needs `memories:UpdateMemory` on it as well as
                    `memories:CreateMemory` on the store.
                  example: mem_V1StGXR8Z5jdHi6B
                duplicate_threshold:
                  type: number
                  description: >-
                    Cosine similarity at or above which the incoming content is
                    a duplicate of an existing memory and the write is skipped.
                    Overrides the store's `duplicate_threshold` for this call;
                    falls back to the store's value, then to `0.95`.
                  minimum: 0
                  maximum: 1
                  example: 0.95
                supersede_threshold:
                  type: number
                  description: >-
                    Cosine similarity at or above which the incoming content
                    restates a fact that has changed: the top match is
                    invalidated and a new memory replaces it. Overrides the
                    store's `supersede_threshold` for this call; falls back to
                    the store's value, then to `0.90`. The effective pair must
                    satisfy `supersede_threshold < duplicate_threshold`, and the
                    check runs against the effective pair — so overriding only
                    one value cannot invert it against the store's other one.
                  minimum: 0
                  maximum: 1
                  example: 0.9
      responses:
        '200':
          description: >-
            The write resolved against an existing memory — `action` is
            `skipped` (the fact was already known) or `superseded` (the fact
            had changed, and the returned memory is the replacement).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MemoryWriteResult'
        '201':
          description: Memory created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MemoryWriteResult'
        '400':
          description: >-
            Bad request — a missing required field, a threshold pair whose
            effective values are not `supersede_threshold <
            duplicate_threshold`, or a `supersedes` that is not a memory id, or
            names a memory in another memory store or one already superseded.
        '401':
          description: Unauthorized
        '403':
          description: >-
            Forbidden — the caller may not write to the memory store, or may not
            update the memory named by `supersedes`.
        '404':
          description: Memory store not found, or no memory matches `supersedes`
        '409':
          description: >-
            The project's `storage_bytes` quota is exceeded
            (`QUOTA_STORAGE_EXCEEDED`). Delete stored content, or raise the
            quota — no window reset clears a stored total, so no `Retry-After`
            is sent.
        '500':
          description: Internal server error
  /api/v1/memories/{memory_id}:
    get:
      tags:
        - Memories
      summary: Get a memory
      description: Returns a single memory by ID
      operationId: getMemory
      x-soat-resource:
        kind: memory
        from: memory_id
      x-iam-action: memories:GetMemory
      parameters:
        - name: memory_id
          in: path
          required: true
          schema:
            type: string
            example: mem_V1StGXR8Z5jdHi6B
      responses:
        '200':
          description: Memory found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Memory'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: Memory store or memory not found
        '500':
          description: Internal server error
    put:
      tags:
        - Memories
      summary: Update a memory
      description: Updates an existing memory. Regenerates the embedding if content changes.
      operationId: updateMemory
      x-soat-resource:
        kind: memory
        from: memory_id
      x-iam-action: memories:UpdateMemory
      parameters:
        - name: memory_id
          in: path
          required: true
          schema:
            type: string
            example: mem_V1StGXR8Z5jdHi6B
        - $ref: './concurrency.yaml#/components/parameters/IfMatchVersion'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                content:
                  type: string
                  description: Updated text content
                tags:
                  description: >-
                    Replaces the memory's tags. Pass null or an empty object to
                    clear.
                  allOf:
                    - $ref: './tags.yaml#/components/schemas/NullableTagBag'
                metadata:
                  description: Replaces the memory's metadata. Pass null to clear.
                  allOf:
                    - $ref: './metadata.yaml#/components/schemas/NullableMetadataBag'
                expected_version:
                  description: >-
                    Refuses the write unless the memory is at this version.
                  allOf:
                    - $ref: './concurrency.yaml#/components/schemas/ExpectedVersion'
      responses:
        '200':
          description: Memory updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Memory'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: Memory store or memory not found
        '500':
          description: Internal server error
        '409':
          $ref: './concurrency.yaml#/components/responses/VersionConflict'
    delete:
      tags:
        - Memories
      summary: Delete a memory
      description: Deletes a memory
      operationId: deleteMemory
      x-soat-resource:
        kind: memory
        from: memory_id
      x-iam-action: memories:DeleteMemory
      parameters:
        - name: memory_id
          in: path
          required: true
          schema:
            type: string
            example: mem_V1StGXR8Z5jdHi6B
      responses:
        '204':
          description: Memory deleted
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: Memory store or memory not found
        '500':
          description: Internal server error
  /api/v1/memories/{memory_id}/retract:
    post:
      tags:
        - Memories
      summary: Retract a memory
      description: >
        Retires a fact that stopped holding with nothing replacing it. The
        memory leaves the default listing, [knowledge
        search](/docs/api/knowledge/search-knowledge) and write deduplication,
        so restating the fact later lands as a new memory.


        It is an invalidation with no successor, which is what tells it apart
        from a supersede: `invalidated_at` is set and
        `superseded_by_memory_id` stays null. The retraction is appended to the
        assertion ledger with outcome `retracted`, so who withdrew the fact is
        part of the record.


        The memory stays readable by id, with its text and its assertions.
        `DELETE` remains the way to remove it outright.
      operationId: retractMemory
      x-soat-resource:
        kind: memory
        from: memory_id
      x-iam-action: memories:RetractMemory
      parameters:
        - name: memory_id
          in: path
          required: true
          schema:
            type: string
            example: mem_V1StGXR8Z5jdHi6B
        - $ref: './concurrency.yaml#/components/parameters/IfMatchVersion'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                expected_version:
                  description: >-
                    Refuses the retraction unless the memory is at this version.
                  allOf:
                    - $ref: './concurrency.yaml#/components/schemas/ExpectedVersion'
      responses:
        '200':
          description: The retracted memory
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Memory'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: Memory store or memory not found
        '409':
          description: >-
            The memory is already invalidated (`MEMORY_ALREADY_INVALIDATED`),
            or it has moved past the version this write read
            (`VERSION_CONFLICT`, with `meta.current_version`).
        '500':
          description: Internal server error
  /api/v1/memories/{memory_id}/assertions:
    get:
      tags:
        - Memories
      summary: List a memory's assertions
      description: >-
        Returns every write that resolved into this memory, oldest first: the
        one that created it, the duplicates it absorbed, and the assertion that
        superseded another memory in its favour. A superseded memory keeps its
        own assertions, so the chain can be walked in both directions.
      operationId: listMemoryAssertions
      x-soat-resource:
        kind: memory
        from: memory_id
      x-iam-action: memories:ListMemoryAssertions
      parameters:
        - name: memory_id
          in: path
          required: true
          schema:
            type: string
            example: mem_V1StGXR8Z5jdHi6B
        - $ref: './pagination.yaml#/components/parameters/Limit'
        - $ref: './pagination.yaml#/components/parameters/Offset'
      responses:
        '200':
          description: List of assertions
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - total
                  - limit
                  - offset
                properties:
                  data:
                    type: array
                    items:
                      $ref: './memory-assertions.yaml#/components/schemas/MemoryAssertion'
                  total:
                    type: integer
                  limit:
                    type: integer
                  offset:
                    type: integer
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: Memory store or memory not found
        '500':
          description: Internal server error
  /api/v1/memories/{memory_id}/tags:
    get:
      tags:
        - Memories
      summary: Get memory tags
      description: Returns all tags attached to the memory
      operationId: getMemoryTags
      x-soat-resource:
        kind: memory
        from: memory_id
      x-iam-action: memories:GetMemory
      parameters:
        - name: memory_id
          in: path
          required: true
          description: Memory ID
          schema:
            type: string
            example: mem_V1StGXR8Z5jdHi6B
      responses:
        '200':
          description: Memory tags
          content:
            application/json:
              schema:
                $ref: './tags.yaml#/components/schemas/TagBag'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: Memory store or memory not found
    put:
      tags:
        - Memories
      summary: Replace memory tags
      description: Replaces all tags on the memory with the provided tags (not merged)
      operationId: replaceMemoryTags
      x-soat-resource:
        kind: memory
        from: memory_id
      x-iam-action: memories:UpdateMemory
      parameters:
        - name: memory_id
          in: path
          required: true
          description: Memory ID
          schema:
            type: string
            example: mem_V1StGXR8Z5jdHi6B
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: './tags.yaml#/components/schemas/TagBag'
      responses:
        '200':
          description: Tags replaced
          content:
            application/json:
              schema:
                $ref: './tags.yaml#/components/schemas/TagBag'
        '400':
          description: Body is not an object of string values
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: Memory store or memory not found
    patch:
      tags:
        - Memories
      summary: Merge memory tags
      description: Merges provided tags into the memory's existing tags (existing tags are preserved unless overridden)
      operationId: mergeMemoryTags
      x-soat-resource:
        kind: memory
        from: memory_id
      x-iam-action: memories:UpdateMemory
      parameters:
        - name: memory_id
          in: path
          required: true
          description: Memory ID
          schema:
            type: string
            example: mem_V1StGXR8Z5jdHi6B
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: './tags.yaml#/components/schemas/TagBag'
      responses:
        '200':
          description: Tags merged
          content:
            application/json:
              schema:
                $ref: './tags.yaml#/components/schemas/TagBag'
        '400':
          description: Body is not an object of string values
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: Memory store or memory not found
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
  schemas:
    Memory:
      type: object
      properties:
        id:
          type: string
          example: mem_V1StGXR8Z5jdHi6B
        memory_store_id:
          x-soat-ref: memory-stores
          type: string
          example: mstore_V1StGXR8Z5jdHi6B
        content:
          type: string
          description: >-
            The text this memory currently holds. Stored once per distinct text
            per store and shared with every assertion that stated it, so a
            memory carries no copy of its own.
          example: The customer prefers email communication over phone calls
        source_type:
          type: string
          enum: [manual, conversation]
          description: >-
            Whether there is a source to point at. `conversation` means
            `source_id` names the conversation this fact was learned in;
            `manual` means there is nothing to point at — a direct API write,
            or an agent write made outside a conversation.
          example: manual
        source_id:
          type: string
          nullable: true
          description: >-
            The conversation this fact was learned in, when `source_type` is
            `conversation`; null when it is `manual`. Deliberately a loose
            pointer rather than a foreign key, so deleting the conversation
            leaves the id in place — the record of where the fact came from
            outlives its source.
          example: conv_V1StGXR8Z5jdHi6B
        tags:
          description: >-
            Per-memory key-value tags, matched at memory granularity by
            knowledge search.
          allOf:
            - $ref: './tags.yaml#/components/schemas/TagBag'
        metadata:
          description: Arbitrary structured metadata attached to the memory
          example: { evidence: high }
          allOf:
            - $ref: './metadata.yaml#/components/schemas/MetadataBag'
        invalidated_at:
          type: string
          format: date-time
          nullable: true
          description: >-
            When the memory stopped holding, whether a supersede replaced it or
            a retraction withdrew it. Null means the memory is currently valid.
            Invalidated memories are excluded from listing, from write
            deduplication, and from knowledge search, but remain readable by ID
            — with their original text — for audit.
        superseded_by_memory_id:
          type: string
          nullable: true
          description: The memory that replaced this one, when it was superseded. Null for a valid memory and for a retracted one, which nothing replaced.
          example: mem_V1StGXR8Z5jdHi6B
        version:
          type: integer
          description: >-
            The memory's write version, starting at 1 and incremented on every
            update. States a precondition against it with `expected_version`
            or `If-Match` on `PUT /memories/{memory_id}`.
          example: 1
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    MemoryWriteResult:
      allOf:
        - $ref: '#/components/schemas/Memory'
        - type: object
          properties:
            action:
              type: string
              enum: [created, superseded, skipped]
              description: >-
                The outcome of the write. `created` means the fact was distinct
                and a new memory holds it. `superseded` means it restated a
                fact that had changed: the matched memory was invalidated,
                points at the replacement, and the replacement is what is
                returned. `skipped` means the fact was already known and the
                existing memory is returned unchanged. Every outcome is
                recorded as an assertion, readable at
                `GET /api/v1/memories/{memory_id}/assertions`.
